aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values19
-rw-r--r--.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body1
-rw-r--r--.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values11
-rw-r--r--.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values2
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/body25
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/body28
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/body25
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/body93
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/body32
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/body73
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/body70
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/body15
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values11
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/body95
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/body87
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/body37
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/body33
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/body27
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/body22
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/body26
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values14
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/values17
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/body47
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values14
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/body26
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values14
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/body20
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values14
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/body19
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values14
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/body37
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values14
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/body20
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values11
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values20
-rw-r--r--.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values4
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/body23
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/body47
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/body25
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/body50
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/body23
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/body123
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/body47
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/body35
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/body93
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/body32
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values11
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/body45
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/body43
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values11
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/body43
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values14
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values20
-rw-r--r--.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values2
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/body10
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values8
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/body16
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values11
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/body5
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values11
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/body8
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values8
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/body10
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values11
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/body2
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values11
-rw-r--r--.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/values20
-rw-r--r--.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/body24
-rw-r--r--.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/values11
-rw-r--r--.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/body33
-rw-r--r--.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/values11
-rw-r--r--.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values17
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/values11
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/values11
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/values11
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/values11
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/b1a772a0-241f-42fc-8209-765162485b0a/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/values11
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/values8
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/body1
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/values8
-rw-r--r--.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/values20
-rw-r--r--.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values25
-rw-r--r--.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values19
-rw-r--r--.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values2
-rw-r--r--.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/body20
-rw-r--r--.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values11
-rw-r--r--.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/body9
-rw-r--r--.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values8
-rw-r--r--.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/values17
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/body36
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/body115
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/body58
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/body96
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/body52
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/body38
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/body58
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/body14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/body51
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/body30
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/body37
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/body25
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/body102
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/body18
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values11
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/body72
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/body88
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/body55
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/body44
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values14
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/values17
-rw-r--r--.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values19
-rw-r--r--.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values19
-rw-r--r--.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values2
-rw-r--r--.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values2
-rw-r--r--.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values2
-rw-r--r--.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values2
-rw-r--r--.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/body2
-rw-r--r--.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/values8
-rw-r--r--.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values4
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/body26
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/body35
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values11
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/body77
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/body62
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/body52
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/body32
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/body30
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/body25
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/body33
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/body69
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values14
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/values20
-rw-r--r--.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values2
-rw-r--r--.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/body43
-rw-r--r--.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values (renamed from .be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/b1a772a0-241f-42fc-8209-765162485b0a/values)2
-rw-r--r--.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/values17
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/body19
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values14
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/body27
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values14
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/body27
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values14
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/body58
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values14
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/body26
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values14
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/body38
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values11
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/body10
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values14
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values20
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body8
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values11
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body1
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values11
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body5
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values11
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body1
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values11
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body5
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values8
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values17
-rw-r--r--.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values2
-rw-r--r--.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values2
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values19
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values19
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values19
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body1
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values8
-rw-r--r--.be/settings4
-rw-r--r--AUTHORS1
l---------Bugs-Everywhere-Web/libbe1
-rw-r--r--COPYING28
-rw-r--r--Makefile24
-rw-r--r--README18
-rw-r--r--README.dev10
-rwxr-xr-xbe111
-rw-r--r--becommands/assign.py44
-rw-r--r--becommands/close.py35
-rw-r--r--becommands/comment.py132
-rw-r--r--becommands/commit.py78
-rw-r--r--becommands/depend.py344
-rw-r--r--becommands/diff.py88
-rw-r--r--becommands/help.py29
-rw-r--r--becommands/html.py38
-rw-r--r--becommands/init.py35
-rw-r--r--becommands/list.py43
-rw-r--r--becommands/merge.py45
-rw-r--r--becommands/new.py54
-rw-r--r--becommands/open.py36
-rw-r--r--becommands/remove.py38
-rw-r--r--becommands/set.py88
-rw-r--r--becommands/severity.py42
-rw-r--r--becommands/show.py94
-rw-r--r--becommands/status.py84
-rw-r--r--becommands/subscribe.py370
-rw-r--r--becommands/tag.py51
-rw-r--r--becommands/target.py50
-rw-r--r--doc/module.mk18
-rw-r--r--interfaces/README34
-rwxr-xr-xinterfaces/email/catmutt (renamed from xml/catmutt)0
-rw-r--r--interfaces/email/interactive/README145
-rw-r--r--interfaces/email/interactive/_procmailrc22
-rwxr-xr-xinterfaces/email/interactive/be-handle-mail893
l---------interfaces/email/interactive/becommands1
-rw-r--r--interfaces/email/interactive/examples/blank (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe)0
-rw-r--r--interfaces/email/interactive/examples/comment11
-rw-r--r--interfaces/email/interactive/examples/failing_multiples16
-rw-r--r--interfaces/email/interactive/examples/invalid_command11
-rw-r--r--interfaces/email/interactive/examples/invalid_subject9
-rw-r--r--interfaces/email/interactive/examples/list11
-rw-r--r--interfaces/email/interactive/examples/missing_command11
-rw-r--r--interfaces/email/interactive/examples/multiple_commands14
-rw-r--r--interfaces/email/interactive/examples/new19
-rw-r--r--interfaces/email/interactive/examples/new_with_comment13
-rw-r--r--interfaces/email/interactive/examples/show11
-rw-r--r--interfaces/email/interactive/examples/unicode11
l---------interfaces/email/interactive/libbe1
-rw-r--r--interfaces/email/interactive/send_pgp_mime.py611
-rwxr-xr-xinterfaces/gui/beg/beg (renamed from misc/gui/beg)0
-rw-r--r--interfaces/gui/beg/table.py (renamed from misc/gui/table.py)0
-rwxr-xr-xinterfaces/gui/wxbe/wxbe (renamed from misc/gui/wxbe)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe (renamed from Bugs-Everywhere-Web/beweb/__init__.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt (renamed from Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/README.txt (renamed from Bugs-Everywhere-Web/README.txt)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/__init__.py (renamed from Bugs-Everywhere-Web/beweb/templates/__init__.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/app.cfg (renamed from Bugs-Everywhere-Web/beweb/app.cfg)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/config.py.example (renamed from Bugs-Everywhere-Web/beweb/config.py.example)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/config/app.cfg (renamed from Bugs-Everywhere-Web/beweb/config/app.cfg)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/config/log.cfg (renamed from Bugs-Everywhere-Web/beweb/config/log.cfg)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py (renamed from Bugs-Everywhere-Web/beweb/controllers.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/formatting.py (renamed from Bugs-Everywhere-Web/beweb/formatting.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/json.py (renamed from Bugs-Everywhere-Web/beweb/json.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/model.py (renamed from Bugs-Everywhere-Web/beweb/model.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/prest.py (renamed from Bugs-Everywhere-Web/beweb/prest.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/release.py (renamed from Bugs-Everywhere-Web/beweb/release.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/css/style.css (renamed from Bugs-Everywhere-Web/beweb/static/css/style.css)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-b.png)bin213 -> 213 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-bl.png)bin327 -> 327 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-br.png)bin365 -> 365 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-l.png)bin197 -> 197 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-r.png)bin214 -> 214 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-t.png)bin200 -> 200 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-tl.png)bin240 -> 240 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds-tr.png)bin311 -> 311 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds2-b.png)bin206 -> 206 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ds2-r.png)bin204 -> 204 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico (renamed from Bugs-Everywhere-Web/beweb/static/images/favicon.ico)bin318 -> 318 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png (renamed from Bugs-Everywhere-Web/beweb/static/images/favicon.png)bin267 -> 267 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png (renamed from Bugs-Everywhere-Web/beweb/static/images/half-spiral.png)bin1112 -> 1112 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.png (renamed from Bugs-Everywhere-Web/beweb/static/images/header_inner.png)bin37537 -> 37537 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png (renamed from Bugs-Everywhere-Web/beweb/static/images/info.png)bin2889 -> 2889 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-b.png)bin200 -> 200 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-bl.png)bin408 -> 408 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-br.png)bin304 -> 304 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-l.png)bin214 -> 214 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-r.png)bin197 -> 197 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-t.png)bin213 -> 213 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-tl.png)bin413 -> 413 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.png (renamed from Bugs-Everywhere-Web/beweb/static/images/is-tr.png)bin414 -> 414 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png (renamed from Bugs-Everywhere-Web/beweb/static/images/ok.png)bin25753 -> 25753 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png (renamed from Bugs-Everywhere-Web/beweb/static/images/shadows.png)bin3960 -> 3960 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png (renamed from Bugs-Everywhere-Web/beweb/static/images/spiral.png)bin2120 -> 2120 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png (renamed from Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png)bin4010 -> 4010 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png (renamed from Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png)bin2667 -> 2667 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/__init__.py (renamed from Bugs-Everywhere-Web/beweb/tests/__init__.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/about.kid (renamed from Bugs-Everywhere-Web/beweb/templates/about.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/bugs.kid (renamed from Bugs-Everywhere-Web/beweb/templates/bugs.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid (renamed from Bugs-Everywhere-Web/beweb/templates/edit_bug.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid (renamed from Bugs-Everywhere-Web/beweb/templates/edit_comment.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/error.kid (renamed from Bugs-Everywhere-Web/beweb/templates/error.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/login.kid (renamed from Bugs-Everywhere-Web/beweb/templates/login.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/master.kid (renamed from Bugs-Everywhere-Web/beweb/templates/master.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/projects.kid (renamed from Bugs-Everywhere-Web/beweb/templates/projects.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/welcome.kid (renamed from Bugs-Everywhere-Web/beweb/templates/welcome.kid)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_controllers.py (renamed from Bugs-Everywhere-Web/beweb/tests/test_controllers.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_model.py (renamed from Bugs-Everywhere-Web/beweb/tests/test_model.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/dev.cfg (renamed from Bugs-Everywhere-Web/dev.cfg)0
l---------interfaces/web/Bugs-Everywhere-Web/libbe1
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/prod.cfg (renamed from Bugs-Everywhere-Web/prod.cfg)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/sample-prod.cfg (renamed from Bugs-Everywhere-Web/sample-prod.cfg)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/server.log (renamed from Bugs-Everywhere-Web/server.log)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/setup-tables.py (renamed from Bugs-Everywhere-Web/setup-tables.py)0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/setup.py (renamed from Bugs-Everywhere-Web/setup.py)0
-rwxr-xr-xinterfaces/web/Bugs-Everywhere-Web/start-beweb.py (renamed from Bugs-Everywhere-Web/start-beweb.py)0
-rwxr-xr-xinterfaces/xml/be-mbox-to-xml128
-rwxr-xr-xinterfaces/xml/be-xml-to-mbox (renamed from xml/be-xml-to-mbox)185
-rw-r--r--libbe/arch.py60
-rw-r--r--libbe/beuuid.py23
-rw-r--r--libbe/bug.py150
-rw-r--r--libbe/bugdir.py444
-rw-r--r--libbe/bzr.py63
-rw-r--r--libbe/cmdutil.py109
-rw-r--r--libbe/comment.py444
-rw-r--r--libbe/config.py23
-rw-r--r--libbe/darcs.py82
-rw-r--r--libbe/diff.py492
-rw-r--r--libbe/editor.py29
-rw-r--r--libbe/encoding.py28
-rw-r--r--libbe/git.py71
-rw-r--r--libbe/hg.py52
-rw-r--r--libbe/mapfile.py27
-rw-r--r--libbe/plugin.py23
-rw-r--r--libbe/properties.py90
-rw-r--r--libbe/rcs.py151
-rw-r--r--libbe/settings_object.py83
-rw-r--r--libbe/tree.py56
-rw-r--r--libbe/utility.py66
-rw-r--r--misc/completion/be.bash (renamed from completion/be.bash)0
-rwxr-xr-xsetup.py8
-rwxr-xr-xtest_usage.sh15
-rwxr-xr-xupdate_copyright.sh57
378 files changed, 10106 insertions, 1670 deletions
diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values
index a68a21d..4b8a017 100644
--- a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values
+++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values
@@ -1,21 +1,8 @@
+Content-type: text/plain
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Fri, 18 Apr 2008 11:21:03 +0000
-
-
-
-
-
-
-From=benf
+Date: Fri, 18 Apr 2008 11:21:03 +0000
+From: benf
diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body
new file mode 100644
index 0000000..19b1cf5
--- /dev/null
+++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body
@@ -0,0 +1 @@
+We could add this functionality to update_copyright.sh
diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values
new file mode 100644
index 0000000..0b164a1
--- /dev/null
+++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 14:08:45 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 4be73baf-e46b-4acb-a58e-4719e57c550b
+
diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values
index a2d65ff..2094f46 100644
--- a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values
+++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values
@@ -7,7 +7,7 @@ creator: benf
severity: minor
-status: closed
+status: fixed
summary: Address is outdated for FSF offices
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/body
new file mode 100644
index 0000000..d3f00e7
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/body
@@ -0,0 +1,25 @@
+Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+
+> the basic idea is to take a look at all public branches (for exaple
+> all on lp/bitbucket/github) in order to tell the user of a
+> webinterface that bug foo is fixed in branch xyz, and if its merged to
+> the main branch
+
+I don't understand. The state of the bug in the main branch is right
+there in the main branch; if it's not fixed there, it's not fixed there.
+If it's merged in from a different branch, the bug state follows all the
+other changes when they come in.
+
+Can you give an example of what would be done differently?
+
+--
+ \ “The basic fact about human existence is not that it is a |
+ `\ tragedy, but that it is a bore.” —Henry L. Mencken |
+_o__) |
+Ben Finney
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values
new file mode 100644
index 0000000..c3d2045
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values
@@ -0,0 +1,14 @@
+Alt-id: <874otjmjhr.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 23:34:08 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/body
new file mode 100644
index 0000000..1f6d84b
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/body
@@ -0,0 +1,28 @@
+On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote:
+> 1. is there any way to aggregate over multiple public branches in order
+> to get the complete bug state
+
+Keeping the bug data with the source helps synchronize bug state and
+source code. Bug state in branch A may not apply to branch B. Some
+people like to weaken this source-bug linkage by keeping their bugs in
+a branch all by themselves (ditz [http://ditz.rubyforge.org/]
+currently supports this workflow). It sounds like you want to move
+from "bugs with code" to "bugs and code in separate branches". We
+don't have an easy way to do that in BE at the moment, since
+version-control systems like Git have a single working branch at a
+time (I think :p). What VCS are you using as a backend?
+
+> 2. is there any model for storing bigger files at a central place (for
+> some of my bugs i have multi-megabyte tarballs attached)
+
+ be comment ID "See the tarball at http://yourpage/something.tar.gz"
+Then to grab the tarball, you'd use:
+ wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'`
+to grab it.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values
new file mode 100644
index 0000000..ed9c16f
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values
@@ -0,0 +1,14 @@
+Alt-id: <20090711125030.GA18185@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 08:50:30 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/body
new file mode 100644
index 0000000..bd9e63a
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/body
@@ -0,0 +1,25 @@
+Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+
+> 1. is there any way to aggregate over multiple public branches in
+> order to get the complete bug state
+
+The bug state is as complete as the source code state. It's exactly as
+aggregated as the rest of the source code; the “complete bug state”
+would be the integration branch where you merge all the feature branches
+and bug-fix branches together.
+
+If instead you want bugs to *not* be tightly linked with the rest of the
+source code state, it seems you don't want a distributed bug tracker
+like Bugs Everywhere.
+
+--
+ \ “I cannot conceive that anybody will require multiplications at |
+ `\ the rate of 40,000 or even 4,000 per hour …” —F. H. Wales, 1936 |
+_o__) |
+Ben Finney
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values
new file mode 100644
index 0000000..6958136
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values
@@ -0,0 +1,14 @@
+Alt-id: <878wivmjm1.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 23:31:34 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/body
new file mode 100644
index 0000000..11f344c
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/body
@@ -0,0 +1,93 @@
+On Mon, Jul 13, 2009 at 09:05:34AM +0200, Ronny Pfannschmidt wrote:
+> On Sun, 2009-07-12 at 19:55 -0400, W. Trevor King wrote:
+> > On Sun, Jul 12, 2009 at 11:20:10PM +0200, Ronny Pfannschmidt wrote:
+> > > On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote:
+> > > > On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote:
+> > > > > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote:
+> > > > > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote:
+> > > > > > > 2. is there any model for storing bigger files at a central place (for
+> > > > > > > some of my bugs i have multi-megabyte tarballs attached)
+> > > > > >
+> > > > > > be comment ID "See the tarball at http://yourpage/something.tar.gz"
+> > > > > > Then to grab the tarball, you'd use:
+> > > > > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'`
+> > > > > > to grab it.
+> > > > >
+> > > > > so the basic idea is to do it completely self-managed
+> > > > > and have have heterogenous sources of extended data?
+> > > >
+> > > > I assume "extended data" here refers to your tarballs. What sort of
+> > > > homogenous source did you have in mind? The comment body is currently
+> > > > just a binary blob for non-text/* types, otherwise it's text in
+> > > > whatever encoding you've configured.
+> > >
+> > > some kind of common upload target for a single project in order to have
+> > > more reliable sources of stuff thats related to bugs but doesnt fit into
+> > > the normal repository
+> >
+> > Sorry, I'm still having trouble with "doesn't fit into the normal
+> > repository". It's going to be large wherever you keep it. You
+> > worried about multiple branches all having these big tarballs in them
+> > and want a "lightweight" checkout without all the big
+> > tarballs/whatever? I still think having some sort of "resources"
+> > directory on an http server somewhere that you link to from comments
+> > is the best plan. If you use the
+> > be show --xml ID | be-xml-to-mbox | catmutt
+> > approach, you can even write your comments in text/html and get
+> > clickable links ;). A "push big file to remote and commit comment
+> > linking to it" script would be pretty simple and keep everything
+> > consistent.
+>
+> thats probably what i want to do
+
+#!/bin/bash
+REMOTE_DIR="you@webhost:./public_html/bigfiles"
+REMOTE_LINK="http://www.webhost.com/bigfiles"
+if [ $# -ne 2 ]; then
+ echo "usage: $0 ID BIGFILE"
+ exit 1
+fi
+ID="$1"
+BIGFILE="$2"
+be comment "$ID" "Large file stored at ${REMOTE_LINK}/${BIGFILE}" && scp "$BIGFILE" "${REMOTE_DIR}"
+
+> > > > On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote:
+> > > > > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+> > > > >
+> > > > > > i want to see the combination of the bug data of all branches
+> > > > >
+> > > > > How is a tool to determine the set of “all branches”? The distributed
+> > > > > VCS model means that set is indeterminate.
+> > > >
+> > > > He could just make a list of branches he likes.
+> > > >
+> > > > Ronny, are you looking to check bug status across several repos on the
+> > > > fly, or periodically run something (with cron, etc.) to update a
+> > > > static multi-repo summary?
+> > >
+> > > on the fly access
+> >
+> > Then listing bugs in a remote repo will either involve httping tons of
+> > tiny values files for each bug (slow?) or running some hypothetical
+> > BE-server locally for each repo speaking some BE-specific protocol
+> > (complicated?). And how would you handle e.g. headless git repos,
+> > where nothing's even checked out?
+> >
+> > You could always run the cron job every 15 minutes, and rely on
+> > whatever VCS you're using having some intelligent protocol/procedure
+> > to keep bandwidth down ;). If you need faster / more-efficient
+> > updates, you'll probably need to throw out polling altogether and
+> > setup all involved repos with a "push to central-repo on commit" hook
+> > or some such.
+>
+> its intended to run on the place where i publish the repositories anyway
+
+Oh, you mean all the repos you want to cover are all _already_ on the
+same host?
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values
new file mode 100644
index 0000000..d95deb9
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values
@@ -0,0 +1,14 @@
+Alt-id: <20090713104715.GA13723@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 13 Jul 2009 06:47:15 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 6dcc910a-ce15-4eeb-b49b-4747719748ed
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/body
new file mode 100644
index 0000000..cf3c990
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/body
@@ -0,0 +1,32 @@
+On Sat, Jul 11, 2009 at 11:25:07AM -0400, W. Trevor King wrote:
+> The easiest implementation I can think of would be to keep local
+> branches (on whatever computer is hosting your web interface)
+> following your favorite repos.
+> proxectX/
+> |-- repoA
+> |-- repoB
+> `-- repoC
+> You'd pull upstream changes with a cron job.
+> Listing bugs would be something along the lines of
+> projectX$ for repo in *
+> do
+> pushd $repo
+> be list
+> popd
+> done | sort | uniq
+> ...
+
+I've reworked option handling for be, so my branch now supports
+ projectX$ for repo in *
+ do
+ be --dir $repo list
+ done | sort | uniq
+etc. This also makes it easy to use your uninstalled development
+version of be on any bug directory on your local machine.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values
new file mode 100644
index 0000000..1c7b2bf
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values
@@ -0,0 +1,14 @@
+Alt-id: <20090713115734.GA13788@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 13 Jul 2009 07:57:34 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: bd98f525-95ec-446a-84e8-34c7d6fa5b40
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/body
new file mode 100644
index 0000000..c22de06
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/body
@@ -0,0 +1,73 @@
+On Sun, Jul 12, 2009 at 11:20:10PM +0200, Ronny Pfannschmidt wrote:
+> On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote:
+> > On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote:
+> > > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote:
+> > > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote:
+> > > > > 2. is there any model for storing bigger files at a central place (for
+> > > > > some of my bugs i have multi-megabyte tarballs attached)
+> > > >
+> > > > be comment ID "See the tarball at http://yourpage/something.tar.gz"
+> > > > Then to grab the tarball, you'd use:
+> > > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'`
+> > > > to grab it.
+> > >
+> > > so the basic idea is to do it completely self-managed
+> > > and have have heterogenous sources of extended data?
+> >
+> > I assume "extended data" here refers to your tarballs. What sort of
+> > homogenous source did you have in mind? The comment body is currently
+> > just a binary blob for non-text/* types, otherwise it's text in
+> > whatever encoding you've configured.
+>
+> some kind of common upload target for a single project in order to have
+> more reliable sources of stuff thats related to bugs but doesnt fit into
+> the normal repository
+
+Sorry, I'm still having trouble with "doesn't fit into the normal
+repository". It's going to be large wherever you keep it. You
+worried about multiple branches all having these big tarballs in them
+and want a "lightweight" checkout without all the big
+tarballs/whatever? I still think having some sort of "resources"
+directory on an http server somewhere that you link to from comments
+is the best plan. If you use the
+ be show --xml ID | be-xml-to-mbox | catmutt
+approach, you can even write your comments in text/html and get
+clickable links ;). A "push big file to remote and commit comment
+linking to it" script would be pretty simple and keep everything
+consistent.
+
+> > On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote:
+> > > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+> > >
+> > > > i want to see the combination of the bug data of all branches
+> > >
+> > > How is a tool to determine the set of “all branches”? The distributed
+> > > VCS model means that set is indeterminate.
+> >
+> > He could just make a list of branches he likes.
+> >
+> > Ronny, are you looking to check bug status across several repos on the
+> > fly, or periodically run something (with cron, etc.) to update a
+> > static multi-repo summary?
+>
+> on the fly access
+
+Then listing bugs in a remote repo will either involve httping tons of
+tiny values files for each bug (slow?) or running some hypothetical
+BE-server locally for each repo speaking some BE-specific protocol
+(complicated?). And how would you handle e.g. headless git repos,
+where nothing's even checked out?
+
+You could always run the cron job every 15 minutes, and rely on
+whatever VCS you're using having some intelligent protocol/procedure
+to keep bandwidth down ;). If you need faster / more-efficient
+updates, you'll probably need to throw out polling altogether and
+setup all involved repos with a "push to central-repo on commit" hook
+or some such.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values
new file mode 100644
index 0000000..89f2724
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values
@@ -0,0 +1,14 @@
+Alt-id: <20090712235502.GA10782@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 19:55:02 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 8ffc90d7-0be7-4b00-88e6-9ae1b65f7957
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/body
new file mode 100644
index 0000000..6b7d3eb
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/body
@@ -0,0 +1,70 @@
+On Sun, 2009-07-12 at 19:55 -0400, W. Trevor King wrote:
+> On Sun, Jul 12, 2009 at 11:20:10PM +0200, Ronny Pfannschmidt wrote:
+> > On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote:
+> > > On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote:
+> > > > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote:
+> > > > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote:
+> > > > > > 2. is there any model for storing bigger files at a central place (for
+> > > > > > some of my bugs i have multi-megabyte tarballs attached)
+> > > > >
+> > > > > be comment ID "See the tarball at http://yourpage/something.tar.gz"
+> > > > > Then to grab the tarball, you'd use:
+> > > > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'`
+> > > > > to grab it.
+> > > >
+> > > > so the basic idea is to do it completely self-managed
+> > > > and have have heterogenous sources of extended data?
+> > >
+> > > I assume "extended data" here refers to your tarballs. What sort of
+> > > homogenous source did you have in mind? The comment body is currently
+> > > just a binary blob for non-text/* types, otherwise it's text in
+> > > whatever encoding you've configured.
+> >
+> > some kind of common upload target for a single project in order to have
+> > more reliable sources of stuff thats related to bugs but doesnt fit into
+> > the normal repository
+>
+> Sorry, I'm still having trouble with "doesn't fit into the normal
+> repository". It's going to be large wherever you keep it. You
+> worried about multiple branches all having these big tarballs in them
+> and want a "lightweight" checkout without all the big
+> tarballs/whatever? I still think having some sort of "resources"
+> directory on an http server somewhere that you link to from comments
+> is the best plan. If you use the
+> be show --xml ID | be-xml-to-mbox | catmutt
+> approach, you can even write your comments in text/html and get
+> clickable links ;). A "push big file to remote and commit comment
+> linking to it" script would be pretty simple and keep everything
+> consistent.
+thats probably what i want to do
+
+>
+> > > On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote:
+> > > > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+> > > >
+> > > > > i want to see the combination of the bug data of all branches
+> > > >
+> > > > How is a tool to determine the set of “all branches”? The distributed
+> > > > VCS model means that set is indeterminate.
+> > >
+> > > He could just make a list of branches he likes.
+> > >
+> > > Ronny, are you looking to check bug status across several repos on the
+> > > fly, or periodically run something (with cron, etc.) to update a
+> > > static multi-repo summary?
+> >
+> > on the fly access
+>
+> Then listing bugs in a remote repo will either involve httping tons of
+> tiny values files for each bug (slow?) or running some hypothetical
+> BE-server locally for each repo speaking some BE-specific protocol
+> (complicated?). And how would you handle e.g. headless git repos,
+> where nothing's even checked out?
+>
+> You could always run the cron job every 15 minutes, and rely on
+> whatever VCS you're using having some intelligent protocol/procedure
+> to keep bandwidth down ;). If you need faster / more-efficient
+> updates, you'll probably need to throw out polling altogether and
+> setup all involved repos with a "push to central-repo on commit" hook
+> or some such.
+its intended to run on the place where i publish the repositories anyway
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values
new file mode 100644
index 0000000..867700a
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values
@@ -0,0 +1,14 @@
+Alt-id: <1247468734.7189.1.camel@localhost>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 13 Jul 2009 09:05:34 +0200
+
+
+From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+
+
+In-reply-to: 4d192c6c-a4a8-4844-b083-2dd5926bd2d9
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/body
new file mode 100644
index 0000000..2f2c16e
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/body
@@ -0,0 +1,15 @@
+Hi,
+
+1. is there any way to aggregate over multiple public branches in order
+to get the complete bug state
+
+2. is there any model for storing bigger files at a central place (for
+some of my bugs i have multi-megabyte tarballs attached)
+
+Regards Ronny
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values
new file mode 100644
index 0000000..38b8aa1
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values
@@ -0,0 +1,11 @@
+Alt-id: <1247313294.7701.60.camel@localhost>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 13:54:54 +0200
+
+
+From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/body
new file mode 100644
index 0000000..debd486
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/body
@@ -0,0 +1,95 @@
+On Sat, 2009-07-11 at 11:25 -0400, W. Trevor King wrote:
+> On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote:
+> > On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote:
+> > > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote:
+> > > > 1. is there any way to aggregate over multiple public branches in order
+> > > > to get the complete bug state
+> > >
+> > > Keeping the bug data with the source helps synchronize bug state and
+> > > source code. Bug state in branch A may not apply to branch B. Some
+> > > people like to weaken this source-bug linkage by keeping their bugs in
+> > > a branch all by themselves (ditz [http://ditz.rubyforge.org/]
+> > > currently supports this workflow). It sounds like you want to move
+> > > from "bugs with code" to "bugs and code in separate branches". We
+> > > don't have an easy way to do that in BE at the moment, since
+> > > version-control systems like Git have a single working branch at a
+> > > time (I think :p). What VCS are you using as a backend?
+> >
+> > the basic idea is to take a look at all public branches (for exaple all
+> > on lp/bitbucket/github) in order to tell the user of a webinterface that
+> > bug foo is fixed in branch xyz, and if its merged to the main branch
+>
+> Hmm.
+>
+> > > > 2. is there any model for storing bigger files at a central place (for
+> > > > some of my bugs i have multi-megabyte tarballs attached)
+> > >
+> > > be comment ID "See the tarball at http://yourpage/something.tar.gz"
+> > > Then to grab the tarball, you'd use:
+> > > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'`
+> > > to grab it.
+> > so the basic idea is to do it completely self-managed
+>
+> Well, it's going to be managed by somebody ;). So far I'm not
+> convinced enough for the manager to be me, so I'm suggesting it be you
+> :p.
+>
+> > and have have heterogenous sources of extended data?
+>
+> I assume "extended data" here refers to your tarballs. What sort of
+> homogenous source did you have in mind? The comment body is currently
+> just a binary blob for non-text/* types, otherwise it's text in
+> whatever encoding you've configured.
+some kind of common upload target for a single project in order to have
+more reliable sources of stuff thats related to bugs but doesnt fit into
+the normal repository
+
+
+>
+> On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote:
+> > Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+> >
+> > > i want to see the combination of the bug data of all branches
+> >
+> > How is a tool to determine the set of “all branches”? The distributed
+> > VCS model means that set is indeterminate.
+>
+> He could just make a list of branches he likes.
+>
+> Ronny, are you looking to check bug status across several repos on the
+> fly, or periodically run something (with cron, etc.) to update a
+> static multi-repo summary?
+on the fly access
+
+>
+> The easiest implementation I can think of would be to keep local
+> branches (on whatever computer is hosting your web interface)
+> following your favorite repos.
+> proxectX/
+> |-- repoA
+> |-- repoB
+> `-- repoC
+> You'd pull upstream changes with a cron job.
+> Listing bugs would be something along the lines of
+> projectX$ for repo in *
+> do
+> pushd $repo
+> be list
+> popd
+> done | sort | uniq
+> Then to show bug status you would have something like
+> projectX$ for repo in *
+> do
+> echo $repo
+> pushd $repo
+> be show ${BUGID}
+> popd
+> done
+> For a web frontend, you'd want to translate that to python/libbe.
+>
+
+yes, the idea is to get a web fontend for multiple branches
+and maybe a local gtk fontend for local multi-branch setups
+
+for some of my projects i have n local and m remote repos, and merging
+is not always intended soonish
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values
new file mode 100644
index 0000000..de585ee
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values
@@ -0,0 +1,14 @@
+Alt-id: <1247433610.14803.3.camel@localhost>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 23:20:10 +0200
+
+
+From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+
+
+In-reply-to: bd98f525-95ec-446a-84e8-34c7d6fa5b40
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/body
new file mode 100644
index 0000000..5f55127
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/body
@@ -0,0 +1,87 @@
+On Sat, Jul 11, 2009 at 03:13:05PM +0200, Ronny Pfannschmidt wrote:
+> On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote:
+> > On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote:
+> > > 1. is there any way to aggregate over multiple public branches in order
+> > > to get the complete bug state
+> >
+> > Keeping the bug data with the source helps synchronize bug state and
+> > source code. Bug state in branch A may not apply to branch B. Some
+> > people like to weaken this source-bug linkage by keeping their bugs in
+> > a branch all by themselves (ditz [http://ditz.rubyforge.org/]
+> > currently supports this workflow). It sounds like you want to move
+> > from "bugs with code" to "bugs and code in separate branches". We
+> > don't have an easy way to do that in BE at the moment, since
+> > version-control systems like Git have a single working branch at a
+> > time (I think :p). What VCS are you using as a backend?
+>
+> the basic idea is to take a look at all public branches (for exaple all
+> on lp/bitbucket/github) in order to tell the user of a webinterface that
+> bug foo is fixed in branch xyz, and if its merged to the main branch
+
+Hmm.
+
+> > > 2. is there any model for storing bigger files at a central place (for
+> > > some of my bugs i have multi-megabyte tarballs attached)
+> >
+> > be comment ID "See the tarball at http://yourpage/something.tar.gz"
+> > Then to grab the tarball, you'd use:
+> > wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'`
+> > to grab it.
+> so the basic idea is to do it completely self-managed
+
+Well, it's going to be managed by somebody ;). So far I'm not
+convinced enough for the manager to be me, so I'm suggesting it be you
+:p.
+
+> and have have heterogenous sources of extended data?
+
+I assume "extended data" here refers to your tarballs. What sort of
+homogenous source did you have in mind? The comment body is currently
+just a binary blob for non-text/* types, otherwise it's text in
+whatever encoding you've configured.
+
+On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote:
+> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+>
+> > i want to see the combination of the bug data of all branches
+>
+> How is a tool to determine the set of “all branches”? The distributed
+> VCS model means that set is indeterminate.
+
+He could just make a list of branches he likes.
+
+Ronny, are you looking to check bug status across several repos on the
+fly, or periodically run something (with cron, etc.) to update a
+static multi-repo summary?
+
+The easiest implementation I can think of would be to keep local
+branches (on whatever computer is hosting your web interface)
+following your favorite repos.
+ proxectX/
+ |-- repoA
+ |-- repoB
+ `-- repoC
+You'd pull upstream changes with a cron job.
+Listing bugs would be something along the lines of
+ projectX$ for repo in *
+ do
+ pushd $repo
+ be list
+ popd
+ done | sort | uniq
+Then to show bug status you would have something like
+ projectX$ for repo in *
+ do
+ echo $repo
+ pushd $repo
+ be show ${BUGID}
+ popd
+ done
+For a web frontend, you'd want to translate that to python/libbe.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values
new file mode 100644
index 0000000..2792f2b
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values
@@ -0,0 +1,14 @@
+Alt-id: <20090711152507.GA18461@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 11:25:07 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: e520239c-8d69-4ff6-b1bd-0c2f74366200
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/body
new file mode 100644
index 0000000..cc3cff3
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/body
@@ -0,0 +1,37 @@
+On Sun, Jul 12, 2009 at 12:57:35AM +1000, Ben Finney wrote:
+> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+>
+> > i want to see the combination of the bug data of all branches
+>
+> What is your definition of ???all branches???? When I'm working with
+> distributed VCS, I create branches wherever I feel like, and the VCS
+> tool doesn't have some central registry of branches to keep up to date.
+>
+> How is a tool to determine the set of ???all branches???? The distributed
+> VCS model means that set is indeterminate.
+
+In the first main Ronny spoke about "public" branches. To me it means that
+if a branch is public, he should like to have a status of that branch.
+
+We all agree (probably ;-) ) that tha main branch is the "right" branch, but
+as I see it, Ronny's question has some logic.
+I'd like to know that a certain bug is fixed in a certain branch, also if it
+is still not merged in the main branch, for various reason (ie I am interested
+in the solution since the bug stop my work)
+
+Imagine it like a rss feed aggregator: in one place there are all the bugs of
+all the branches that the developers make avaible to the public with
+a repository. This can make easier the life to who want to try a something
+since he know what branch he must check out, instead of checking all the
+branch he can find to test if he get what is looking for.
+
+Unluckyly I have no idea how to solve it. :-(
+
+bye
+Gianluca
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values
new file mode 100644
index 0000000..5e3db52
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values
@@ -0,0 +1,14 @@
+Alt-id: <20090713085859.GA21800@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 13 Jul 2009 10:58:59 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: e520239c-8d69-4ff6-b1bd-0c2f74366200
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/body
new file mode 100644
index 0000000..93f082b
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/body
@@ -0,0 +1,33 @@
+On Sat, 2009-07-11 at 23:34 +1000, Ben Finney wrote:
+> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+>
+> > the basic idea is to take a look at all public branches (for exaple
+> > all on lp/bitbucket/github) in order to tell the user of a
+> > webinterface that bug foo is fixed in branch xyz, and if its merged to
+> > the main branch
+>
+> I don't understand. The state of the bug in the main branch is right
+> there in the main branch; if it's not fixed there, it's not fixed there.
+> If it's merged in from a different branch, the bug state follows all the
+> other changes when they come in.
+>
+> Can you give an example of what would be done differently?
+>
+i want to see the combination of the bug data of all branches
+
+for example
+
+i got bug
+its fixed in the branch "something"
+its not fixed/merged to "main"
+
+now something like a website should tell me, this bug has been fixed in
+branch xyz and the fix is not yet merged into main
+
+
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values
new file mode 100644
index 0000000..789fd7f
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values
@@ -0,0 +1,14 @@
+Alt-id: <1247320857.7701.67.camel@localhost>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 16:00:57 +0200
+
+
+From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+
+
+In-reply-to: 0f60a148-7024-44bd-bbed-377cbece9d1b
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/body
new file mode 100644
index 0000000..3b417be
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/body
@@ -0,0 +1,27 @@
+On Sat, 2009-07-11 at 08:50 -0400, W. Trevor King wrote:
+> On Sat, Jul 11, 2009 at 01:54:54PM +0200, Ronny Pfannschmidt wrote:
+> > 1. is there any way to aggregate over multiple public branches in order
+> > to get the complete bug state
+>
+> Keeping the bug data with the source helps synchronize bug state and
+> source code. Bug state in branch A may not apply to branch B. Some
+> people like to weaken this source-bug linkage by keeping their bugs in
+> a branch all by themselves (ditz [http://ditz.rubyforge.org/]
+> currently supports this workflow). It sounds like you want to move
+> from "bugs with code" to "bugs and code in separate branches". We
+> don't have an easy way to do that in BE at the moment, since
+> version-control systems like Git have a single working branch at a
+> time (I think :p). What VCS are you using as a backend?
+the basic idea is to take a look at all public branches (for exaple all
+on lp/bitbucket/github) in order to tell the user of a webinterface that
+bug foo is fixed in branch xyz, and if its merged to the main branch
+>
+> > 2. is there any model for storing bigger files at a central place (for
+> > some of my bugs i have multi-megabyte tarballs attached)
+>
+> be comment ID "See the tarball at http://yourpage/something.tar.gz"
+> Then to grab the tarball, you'd use:
+> wget `be show COMMENT-ID | sed -n 's/ *See the tarball at //p'`
+> to grab it.
+so the basic idea is to do it completely self-managed and have have
+heterogenous sources of extended data?
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values
new file mode 100644
index 0000000..43173a4
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values
@@ -0,0 +1,14 @@
+Alt-id: <1247317985.7701.63.camel@localhost>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 15:13:05 +0200
+
+
+From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+
+
+In-reply-to: 13012b22-2d02-444c-87c0-8cf0f17137ae
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/body
new file mode 100644
index 0000000..0263fbb
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/body
@@ -0,0 +1,22 @@
+Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+
+> i want to see the combination of the bug data of all branches
+
+What is your definition of “all branches”? When I'm working with
+distributed VCS, I create branches wherever I feel like, and the VCS
+tool doesn't have some central registry of branches to keep up to date.
+
+How is a tool to determine the set of “all branches”? The distributed
+VCS model means that set is indeterminate.
+
+--
+ \ “Pinky, are you pondering what I'm pondering?” “I think so, |
+ `\ Brain, but I find scratching just makes it worse.” —_Pinky and |
+_o__) The Brain_ |
+Ben Finney
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values
new file mode 100644
index 0000000..351fdb9
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values
@@ -0,0 +1,14 @@
+Alt-id: <87zlbbl128.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 00:57:35 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/body b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/body
new file mode 100644
index 0000000..9fb10bc
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/body
@@ -0,0 +1,26 @@
+On Sat, Jul 11, 2009 at 11:31:34PM +1000, Ben Finney wrote:
+> Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de> writes:
+>
+> > 1. is there any way to aggregate over multiple public branches in
+> > order to get the complete bug state
+>
+> The bug state is as complete as the source code state. It's exactly as
+> aggregated as the rest of the source code; the ???complete bug state???
+> would be the integration branch where you merge all the feature branches
+> and bug-fix branches together.
+>
+> If instead you want bugs to *not* be tightly linked with the rest of the
+> source code state, it seems you don't want a distributed bug tracker
+> like Bugs Everywhere.
+
+"the complete bug state" probably means that he want to know (and in some way
+to publish it) that the bug "xyz" is fixed and merged in main while bug "abc"
+is fixed but only in branch "123" and bug "def" is still open in branch "456"
+
+bye
+Gianluca
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values
new file mode 100644
index 0000000..a7c438b
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values
@@ -0,0 +1,14 @@
+Alt-id: <20090713090341.GB21800@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 13 Jul 2009 11:03:41 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: 1f9f60de-ba37-42bc-a1c0-dc062ef255e1
+
diff --git a/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/values b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/values
new file mode 100644
index 0000000..2a3c4f3
--- /dev/null
+++ b/.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/values
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+
+
+severity: wishlist
+
+
+status: unconfirmed
+
+
+summary: Bug aggregation. Multi-repo meta-BE?
+
+
+time: Tue, 21 Jul 2009 18:32:12 +0000
+
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/body
new file mode 100644
index 0000000..c799630
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/body
@@ -0,0 +1,47 @@
+On Sunday 19 July 2009 00:00:46 Chris Ball wrote:
+> Hi,
+>
+> > For example, let's assume we have target a, b, c There is a way
+> > to know that "a" is a past target, "b" is the current target and
+> > "c" is a future target ?
+>
+> We could add a "date due" field for each target.
+
+Good idea
+
+> > More: there is a way to know if a target is closed or open ?
+>
+> We could add a "target close" operation that moves all open bugs
+> assigned to one target to the next date-due target.
+
+Nice. But instead of moving all bugs to the next date-due target, I'd prefer
+to leave the choice to the user
+
+
+> I see problems with these ideas in general, because we're assuming
+> agreement by all parties/branches on when a target's date due is.
+> Maybe it's okay to demand that social conventions be used to handle
+> such a disagreement, or maybe not.
+
+I don't see these as problems per se. We can have two cases:
+
+1) a personal branch (like my html output or Trevor's email interface). In
+this case there is not any problem to decide the due date
+
+2) a branch with a group of delopers (let it be the canonical branch o an
+experimental branch): in this case I suppose that working together means to be
+able to agree on some things
+
+In any case, having the possibility to set a due date does not means that it
+is obligatory to do it and should be a good idea to offer as many possibilities
+as we can to the users of BE
+
+bye
+Gianluca
+
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values
new file mode 100644
index 0000000..db30358
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values
@@ -0,0 +1,14 @@
+Alt-id: <200907202259.11774.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 20 Jul 2009 22:59:11 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: 6555a651-5a7f-4a8a-9793-47ad1315e9e8
+
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/body
new file mode 100644
index 0000000..ef09dc0
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/body
@@ -0,0 +1,26 @@
+Hi,
+
+ > For example, let's assume we have target a, b, c There is a way
+ > to know that "a" is a past target, "b" is the current target and
+ > "c" is a future target ?
+
+We could add a "date due" field for each target.
+
+ > More: there is a way to know if a target is closed or open ?
+
+We could add a "target close" operation that moves all open bugs
+assigned to one target to the next date-due target.
+
+I see problems with these ideas in general, because we're assuming
+agreement by all parties/branches on when a target's date due is.
+Maybe it's okay to demand that social conventions be used to handle
+such a disagreement, or maybe not.
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values
new file mode 100644
index 0000000..cdf7754
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values
@@ -0,0 +1,14 @@
+Alt-id: <m3skgt648h.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 18 Jul 2009 18:00:46 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: b9865d8b-46ae-4169-bc83-d75a98164729
+
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/body
new file mode 100644
index 0000000..874d906
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/body
@@ -0,0 +1,20 @@
+On Monday 20 July 2009 23:03:18 Chris Ball wrote:
+> Hi Gianluca,
+>
+> > In any case, having the possibility to set a due date does not
+> > means that it is obligatory to do it and should be a good idea to
+> > offer as many possibilities as we can to the users of BE
+>
+> Okay, sounds reasonable. Would you like to write a patch for
+> associating due dates and open/closed with a target?
+
+Ok. As soon as I finish a basic implementation of the html export, I will be
+glad to try to write a patch.
+
+bye
+Gianluca
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values
new file mode 100644
index 0000000..d830558
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values
@@ -0,0 +1,14 @@
+Alt-id: <200907202340.39963.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 20 Jul 2009 23:40:39 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: 777182da-a216-45c7-bf4d-42c84e511c66
+
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/body
new file mode 100644
index 0000000..13505c1
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/body
@@ -0,0 +1,19 @@
+Hi Gianluca,
+
+ > In any case, having the possibility to set a due date does not
+ > means that it is obligatory to do it and should be a good idea to
+ > offer as many possibilities as we can to the users of BE
+
+Okay, sounds reasonable. Would you like to write a patch for
+associating due dates and open/closed with a target?
+
+Thanks,
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values
new file mode 100644
index 0000000..a14e287
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values
@@ -0,0 +1,14 @@
+Alt-id: <m3hbx72hk9.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 20 Jul 2009 17:03:18 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: 4952e1c7-e035-42f1-882b-6b5264481d0a
+
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/body
new file mode 100644
index 0000000..a916904
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/body
@@ -0,0 +1,37 @@
+On Sat, Jul 18, 2009 at 06:00:46PM -0400, Chris Ball wrote:
+> > For example, let's assume we have target a, b, c There is a way
+> > to know that "a" is a past target, "b" is the current target and
+> > "c" is a future target ?
+>
+> We could add a "date due" field for each target.
+
+Another option would be a "blocked by" field, since you might miss
+deadlines, or have parallel targeted branches. Or just pick target
+names following some scheme so the alphanumeric-sort is also a
+dependency-order sort ;).
+
+> > More: there is a way to know if a target is closed or open ?
+
+There's also
+ $ be list --target 0.1
+If there are active bugs, the target is open. Otherwise, you must have
+made it ;).
+
+> We could add a "target close" operation that moves all open bugs
+> assigned to one target to the next date-due target.
+
+for bug in `be list --target 0.1 --uuids`; do
+ be target $bug $NEXT_TARGET
+done
+
+To avoid the loop, we could change status, severity, target, etc from
+ be COMMAND BUG ARG
+to
+ be COMMAND ARG BUG [MORE BUGS ...]
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values
new file mode 100644
index 0000000..b6c0979
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values
@@ -0,0 +1,14 @@
+Alt-id: <20090718222701.GA304@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 18 Jul 2009 18:27:01 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 6555a651-5a7f-4a8a-9793-47ad1315e9e8
+
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/body
new file mode 100644
index 0000000..7382bae
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/body
@@ -0,0 +1,20 @@
+Hello
+
+Just a question and only for curiosity: there is an easy way to determine the
+target succession ?
+
+For example, let's assume we have target a, b, c
+There is a way to know that "a" is a past target, "b" is the current target
+and "c" is a future target ? More: there is a way to know if a target is
+closed or open ?
+
+thanks
+
+bye
+Gianluca
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values
new file mode 100644
index 0000000..4972040
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values
@@ -0,0 +1,11 @@
+Alt-id: <200907182351.03217.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 18 Jul 2009 23:51:03 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values
new file mode 100644
index 0000000..1485877
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values
@@ -0,0 +1,20 @@
+assigned: Gianluca Montecchi <gian@grys.it>
+
+
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: Gianluca Montecchi <gian@grys.it>
+
+
+severity: wishlist
+
+
+status: assigned
+
+
+summary: Sorting targets chronologically
+
+
+time: Tue, 21 Jul 2009 18:34:25 +0000
+
diff --git a/.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values b/.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values
index 0a47ab5..c2861d0 100644
--- a/.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values
+++ b/.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values
@@ -7,10 +7,10 @@ reporter: gianluca <gian@galactica>
severity: minor
-status: closed
+status: fixed
-summary: Use the get_parser
+summary: Use the get_parser in becommands/html.py
time: Wed, 08 Jul 2009 21:27:37 +0000
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/body
new file mode 100644
index 0000000..5ce4f1c
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/body
@@ -0,0 +1,23 @@
+"W. Trevor King" <wking@drexel.edu> writes:
+
+> On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote:
+> > Instead of a separate command for each output format, could we have
+> > a single "produce a static report of the bug database" command, and
+> > specify output format as an option?
+> > […]
+>
+> Do people like this architecture better than my be-xml-to-mbox
+> approach?
+
+I think this question is illuminated by the related question: Is mbox
+output a static report, or another read-write data store?
+
+It can technically be both, of course, which is why the question may be
+helpful: it may help show what is the *conceptual* purpose of the mbox
+output format for Bugs Everywhere.
+
+--
+ \ “Time is the great legalizer, even in the field of morals.” |
+ `\ —Henry L. Mencken |
+_o__) |
+Ben Finney
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values
new file mode 100644
index 0000000..b83f4a6
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values
@@ -0,0 +1,14 @@
+Alt-id: <87hbxqrckv.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 06 Jul 2009 08:26:24 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/body
new file mode 100644
index 0000000..dbf3b1b
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/body
@@ -0,0 +1,47 @@
+Gianluca Montecchi <gian@grys.it> writes:
+
+> 1) is it ok to develop this command ? I know that this is not a fully
+> featured web interface, but I am sure that it can be usefull.
+
+Yes, definitely. I can see it being a very easy way to put one's bug
+database online for browsing.
+
+> I am open to suggestion about it of course.
+
+Instead of a separate command for each output format, could we have a
+single “produce a static report of the bug database” command, and
+specify output format as an option?
+
+How about:
+
+ be report
+ be report --format ascii
+ be report --format rst
+ be report --format html
+
+Where the ‘--format’ option has a default of, e.g., “ascii”.
+
+This would mean that you are implementing the ‘html’ format of this
+putative command.
+
+> 2) I see that every command is implemented with a python file in the
+> becommand dir. For a better code, I'd like to split the command
+> implementation into two files: a file that contain the actual code and
+> a second file that have the html related part, any problem with this ?
+
+This sounds quite sensible to me. The existence of a command implies a
+module of the same name in ‘becommand’, but there's no necessary
+implication that that module can't import modules from elsewhere to do
+its work.
+
+--
+ \ “It ain't so much the things we don't know that get us in |
+ `\ trouble. It's the things we know that ain't so.” —Artemus Ward |
+_o__) (1834–1867), U.S. journalist |
+Ben Finney
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values
new file mode 100644
index 0000000..4ef9544
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values
@@ -0,0 +1,14 @@
+Alt-id: <87y6r5qoyw.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 04 Jul 2009 10:19:35 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/body
new file mode 100644
index 0000000..4276b9c
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/body
@@ -0,0 +1,25 @@
+Gianluca Montecchi <gian@grys.it> writes:
+
+> On Monday 06 July 2009 12:48:39 W. Trevor King wrote:
+> > Gianluca is clearly thinking about a static report [for a collection
+> > of HTML files as output]:
+>
+> You are right, static, but not exactly a report as I think Ben is
+> thinking
+
+I think it exactly is a report: multiple, static, browseable pages
+reporting the state of the database at a point in time.
+
+What makes you think that term doesn't apply?
+
+--
+ \ “The problem with television is that the people must sit and |
+ `\ keep their eyes glued on a screen: the average American family |
+_o__) hasn't time for it.” —_The New York Times_, 1939 |
+Ben Finney
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values
new file mode 100644
index 0000000..7dee5d6
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values
@@ -0,0 +1,14 @@
+Alt-id: <87skh9p8ax.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 07 Jul 2009 11:53:58 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/body
new file mode 100644
index 0000000..8451bd7
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/body
@@ -0,0 +1,50 @@
+On Mon, Jul 06, 2009 at 08:26:24AM +1000, Ben Finney wrote:
+> "W. Trevor King" <wking@drexel.edu> writes:
+>
+> > On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote:
+> > > Instead of a separate command for each output format, could we have
+> > > a single "produce a static report of the bug database" command, and
+> > > specify output format as an option?
+> >
+> > Do people like this architecture better than my be-xml-to-mbox
+> > approach?
+>
+> I think this question is illuminated by the related question: Is mbox
+> output a static report, or another read-write data store?
+
+Gianluca is clearly thinking about a static report:
+
+On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote:
+> The goal is to be able to do something like "be html /web/page" to have in the
+> /web/page directory some static html pages that basically are the dump of the
+> be repository, much like ditz have
+
+I think truly interactive frontends like Steve's working on need to be
+build on top of libbe directly, since they'll need to make lots of
+small changes to the database, and it's to slow to be reloading the
+database for every change. Static dumps like my mbox or Gianluca's
+html could just parse the xml output of `be list' and other be
+commands.
+
+There should also be an xml import for `be new' and `be comment' so
+you could import new bugs/comments from whatever format after writing
+a whatever->xml converter. This would allow you to email new bugs and
+comments to the database (e.g. via some procmail-spawned
+be-parse-email script) which would give you some level of
+interactivity, but you'd have to regenerate your mbox to see your new
+comments in your mail reader.
+
+I think interactive use that gives you live-updates in your mail
+reader isn't worth the trouble, since you'd need to teach BE imap or
+smtp+mbox-locking. Hmm, maybe it smtp+mbox-locking wouldn't be so bad,
+but that would be a distinct frontend project like Steve's, not part
+of the becommands.
+
+Trevor
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values
new file mode 100644
index 0000000..6f9ecf7
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values
@@ -0,0 +1,14 @@
+Alt-id: <20090706104839.GA19537@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 6 Jul 2009 06:48:39 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 074ef29a-3f1d-46dc-8561-7a56af7e6d67
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/body
new file mode 100644
index 0000000..3b53533
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/body
@@ -0,0 +1,23 @@
+On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote:
+> Instead of a separate command for each output format, could we have a
+> single "produce a static report of the bug database" command, and
+> specify output format as an option?
+>
+> How about:
+>
+> be report
+> be report --format ascii
+> be report --format rst
+> be report --format html
+
+Do people like this architecture better than my be-xml-to-mbox
+approach? I think the tradeoff is easy output format implementation
+vs cluttered core codebase. Should we use both, depending on how
+useful people think the output format will be?
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values
new file mode 100644
index 0000000..3452022
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values
@@ -0,0 +1,14 @@
+Alt-id: <20090705143108.GB10709@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 5 Jul 2009 10:31:08 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 1dba8196-654b-4ca0-9a95-fb334af81863
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/body
new file mode 100644
index 0000000..9bf3851
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/body
@@ -0,0 +1,123 @@
+On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote:
+>
+> Hello to everyone
+>
+> As i said in a previous mail, I am working on a "html" command for be.
+> The goal is to be able to do something like "be html /web/page" to have in the
+> /web/page directory some static html pages that basically are the dump of the
+> be repository, much like ditz have
+> This will enable a simple and fast publish of the bus list and details on the
+> web, at least in read only mode.
+>
+> So I'd like to ask some question:
+> 1) is it ok to develop this command ? I know that this is not a fully featured
+> web interface, but I am sure that it can be usefull.
+>
+> I am open to suggestion about it of course.
+>
+> 2) I see that every command is implemented with a python file in the becommand
+> dir. For a better code, I'd like to split the command implementation into two
+> files: a file that contain the actual code and a second file that have the html
+> related part, any problem with this ? I don't like to have the html part and
+> the code part in one big and unreadable file.
+>
+> I'd like to hear other opinion about this.
+>
+> Thanks for now
+> bye
+> Gianluca
+>
+>
+> _______________________________________________
+> Be-devel mailing list
+> Be-devel@bugseverywhere.org
+> http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
+
+On Mon, Jul 06, 2009 at 10:18:33PM +0200, Gianluca Montecchi wrote:
+> This sound like an interesting idea, but what i'd like to do is not, strictly
+> speaking, a report. It is a full tree of html pages that are browseable, both
+> on line and offline
+
+I'm not sure what distinction you're making about "report". You're
+just producing a static snapshot of the current database status,
+right? The number of pages and completeness of coverage are nice, but
+it's still a static entity generated from a particular snapshot, which
+is what I mean by "report" ;).
+
+> > > 2) I see that every command is implemented with a python file in the
+> > > becommand dir. For a better code, I'd like to split the command
+> > > implementation into two files: a file that contain the actual code and
+> > > a second file that have the html related part, any problem with this ?
+> >
+> > This sounds quite sensible to me. The existence of a command implies a
+> > module of the same name in ‘becommand’, but there's no necessary
+> > implication that that module can't import modules from elsewhere to do
+> > its work.
+>
+> The "elsewhere" for now is the same directory, just another module
+>
+
+On Mon, Jul 06, 2009 at 10:38:56PM +0200, Gianluca Montecchi wrote:
+> > On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote:
+> > > The goal is to be able to do something like "be html /web/page" to have
+> > > in the /web/page directory some static html pages that basically are the
+> > > dump of the be repository, much like ditz have
+> >
+> > I think truly interactive frontends like Steve's working on need to be
+> > build on top of libbe directly, since they'll need to make lots of
+> > small changes to the database, and it's to slow to be reloading the
+> > database for every change. Static dumps like my mbox or Gianluca's
+> > html could just parse the xml output of `be list' and other be
+> > commands.
+>
+> Ok, but if I want to have an html dump that is browseable, I need to parse the
+> xml. Am I correct ?
+> If yes, should not be easiear to use directly the libbe ?
+
+Using libbe directly is easier, but also more tightly tied to the be
+internals which could weigh down future refactoring. Partly I'm
+afraid of our 2.5 different html-output mechanisms. Either their
+should be a single Right Way that tries to satisfy everyone, or a
+smorgasbord of loosely coupled translators, so it's not so painful to
+kill them if/when they go out of style :p.
+
+On Mon, Jul 06, 2009 at 10:46:54PM +0200, Gianluca Montecchi wrote:
+> On Saturday 04 July 2009 02:31:26 Chris Ball wrote:
+> > It might be a good idea for "be html" to use the CherryPy web interface
+> > that Steve is working on. The command could start up the CherryPy app
+> > and scrape all of the available pages to get a stand-alone dump; this
+> > would avoid having to keep two (okay, more than two at this point)
+> > separate sets of HTML templates in the source tree. What do you think?
+>
+> It can be do, but this implies that CherryPy must be installed and configured,
+> a thing that I don't want to impose. My idea is to offer a simpler way to have
+> some html pages, where you just need to have BE installed.
+
+I agree that not needing CherryPy for a static html dump is good.
+Also, read-only templates will look different from the CherryPy
+interactive templates. +1 for another quasi-redundant template set
+;).
+
+> > > 2) I see that every command is implemented with a python file in
+> > > the becommand dir. For a better code, I'd like to split the
+> > > command implementation into two files: a file that contain the
+> > > actual code and a second file that have the html related part,
+> > > any problem with this ? I don't like to have the html part and
+> > > the code part in one big and unreadable file.
+> >
+> > I agree that becommands/*.py commands should not contain any HTML
+> > layout code. Putting it somewhere else instead sounds fine.
+>
+> I am in doubt with the "somewhere else", since for now I put the html template
+> into a separate file in the same directory. Suggestion ?
+
+I think that only code intended only for command line use only should
+go into becommands, but really, just dump it anywhere and we can shift
+it around later :p.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values
new file mode 100644
index 0000000..ac3b5ab
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values
@@ -0,0 +1,14 @@
+Alt-id: <20090707013454.GA3721@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 6 Jul 2009 21:34:54 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: da97e18f-33d6-469e-9d93-6457b9a6bfca
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/body
new file mode 100644
index 0000000..2301eba
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/body
@@ -0,0 +1,47 @@
+On Saturday 04 July 2009 02:19:35 Ben Finney wrote:
+> Gianluca Montecchi <gian@grys.it> writes:
+
+>
+> > I am open to suggestion about it of course.
+>
+> Instead of a separate command for each output format, could we have a
+> single “produce a static report of the bug database” command, and
+> specify output format as an option?
+>
+> How about:
+>
+> be report
+> be report --format ascii
+> be report --format rst
+> be report --format html
+>
+> Where the ‘--format’ option has a default of, e.g., “ascii”.
+>
+> This would mean that you are implementing the ‘html’ format of this
+> putative command.
+>
+
+This sound like an interesting idea, but what i'd like to do is not, strictly
+speaking, a report. It is a full tree of html pages that are browseable, both
+on line and offline
+
+> > 2) I see that every command is implemented with a python file in the
+> > becommand dir. For a better code, I'd like to split the command
+> > implementation into two files: a file that contain the actual code and
+> > a second file that have the html related part, any problem with this ?
+>
+> This sounds quite sensible to me. The existence of a command implies a
+> module of the same name in ‘becommand’, but there's no necessary
+> implication that that module can't import modules from elsewhere to do
+> its work.
+
+The "elsewhere" for now is the same directory, just another module
+
+bye
+Gianluca
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values
new file mode 100644
index 0000000..6259717
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values
@@ -0,0 +1,14 @@
+Alt-id: <200907062218.33895.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 06 Jul 2009 22:18:33 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: 1dba8196-654b-4ca0-9a95-fb334af81863
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/body
new file mode 100644
index 0000000..50a30e8
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/body
@@ -0,0 +1,35 @@
+Hi Gianluca,
+
+ > As i said in a previous mail, I am working on a "html" command
+ > for be. The goal is to be able to do something like "be html
+ > /web/page" to have in the /web/page directory some static html
+ > pages that basically are the dump of the be repository, much like
+ > ditz have. This will enable a simple and fast publish of the bus
+ > list and details on the web, at least in read only mode.
+
+It might be a good idea for "be html" to use the CherryPy web interface
+that Steve is working on. The command could start up the CherryPy app
+and scrape all of the available pages to get a stand-alone dump; this
+would avoid having to keep two (okay, more than two at this point)
+separate sets of HTML templates in the source tree. What do you think?
+
+ > 2) I see that every command is implemented with a python file in
+ > the becommand dir. For a better code, I'd like to split the
+ > command implementation into two files: a file that contain the
+ > actual code and a second file that have the html related part,
+ > any problem with this ? I don't like to have the html part and
+ > the code part in one big and unreadable file.
+
+I agree that becommands/*.py commands should not contain any HTML
+layout code. Putting it somewhere else instead sounds fine.
+
+Thanks!
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values
new file mode 100644
index 0000000..7d09f96
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values
@@ -0,0 +1,14 @@
+Alt-id: <m3iqi9thk1.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 03 Jul 2009 20:31:26 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/body
new file mode 100644
index 0000000..8991cfb
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/body
@@ -0,0 +1,93 @@
+> On Mon, Jul 06, 2009 at 10:18:33PM +0200, Gianluca Montecchi wrote:
+>> This sound like an interesting idea, but what i'd like to do is not,
+>> strictly
+>> speaking, a report. It is a full tree of html pages that are browseable,
+>> both
+>> on line and offline
+>
+> I'm not sure what distinction you're making about "report". You're
+> just producing a static snapshot of the current database status,
+> right? The number of pages and completeness of coverage are nice, but
+> it's still a static entity generated from a particular snapshot, which
+> is what I mean by "report" ;).
+
+Mmm, my bad here.
+I normally speak about "report" as something that is not browseable, like
+the output of a report generator (reportlab or whatever), but I admit that
+basically also the html output I am working on is a report.
+
+
+> On Mon, Jul 06, 2009 at 10:38:56PM +0200, Gianluca Montecchi wrote:
+>>
+>> Ok, but if I want to have an html dump that is browseable, I need to
+>> parse the
+>> xml. Am I correct ?
+>> If yes, should not be easiear to use directly the libbe ?
+>
+> Using libbe directly is easier, but also more tightly tied to the be
+> internals which could weigh down future refactoring. Partly I'm
+> afraid of our 2.5 different html-output mechanisms. Either their
+> should be a single Right Way that tries to satisfy everyone, or a
+> smorgasbord of loosely coupled translators, so it's not so painful to
+> kill them if/when they go out of style :p.
+
+I know that using libbe I am more tightly tied to the internals, but
+I am trying to keep the command code and the presentation code crearly
+separated to minimize this problem. I am not sure this is a real problem
+anyway.
+
+
+> On Mon, Jul 06, 2009 at 10:46:54PM +0200, Gianluca Montecchi wrote:
+>> On Saturday 04 July 2009 02:31:26 Chris Ball wrote:
+>> > It might be a good idea for "be html" to use the CherryPy web
+>> interface
+>> > that Steve is working on. The command could start up the CherryPy app
+>> > and scrape all of the available pages to get a stand-alone dump; this
+>> > would avoid having to keep two (okay, more than two at this point)
+>> > separate sets of HTML templates in the source tree. What do you
+>> think?
+>>
+>> It can be do, but this implies that CherryPy must be installed and
+>> configured,
+>> a thing that I don't want to impose. My idea is to offer a simpler way
+>> to have
+>> some html pages, where you just need to have BE installed.
+>
+> I agree that not needing CherryPy for a static html dump is good.
+> Also, read-only templates will look different from the CherryPy
+> interactive templates. +1 for another quasi-redundant template set
+> ;).
+
+The look is not a problem. I can always use the same html Steve is using.
+I am also playing with the idea to have the template themeable some time
+after I have a fully working version.
+
+>
+>> > > 2) I see that every command is implemented with a python file in
+>> > > the becommand dir. For a better code, I'd like to split the
+>> > > command implementation into two files: a file that contain the
+>> > > actual code and a second file that have the html related part,
+>> > > any problem with this ? I don't like to have the html part and
+>> > > the code part in one big and unreadable file.
+>> >
+>> > I agree that becommands/*.py commands should not contain any HTML
+>> > layout code. Putting it somewhere else instead sounds fine.
+>>
+>> I am in doubt with the "somewhere else", since for now I put the html
+>> template
+>> into a separate file in the same directory. Suggestion ?
+>
+> I think that only code intended only for command line use only should
+> go into becommands, but really, just dump it anywhere and we can shift
+> it around later :p.
+
+Of course.
+
+bye
+Gianluca
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values
new file mode 100644
index 0000000..e846ff5
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values
@@ -0,0 +1,14 @@
+Alt-id: <6f719a1c43fdcba8bdbfee1130072595.squirrel@webmail.grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 07 Jul 2009 14:15:08 +0200
+
+
+From: gian@grys.it
+
+
+In-reply-to: 83202b83-eea8-452f-8239-d468940bddba
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/body
new file mode 100644
index 0000000..d8f14fc
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/body
@@ -0,0 +1,32 @@
+Hello to everyone
+
+As i said in a previous mail, I am working on a "html" command for be.
+The goal is to be able to do something like "be html /web/page" to have in the
+/web/page directory some static html pages that basically are the dump of the
+be repository, much like ditz have
+This will enable a simple and fast publish of the bus list and details on the
+web, at least in read only mode.
+
+So I'd like to ask some question:
+1) is it ok to develop this command ? I know that this is not a fully featured
+web interface, but I am sure that it can be usefull.
+
+I am open to suggestion about it of course.
+
+2) I see that every command is implemented with a python file in the becommand
+dir. For a better code, I'd like to split the command implementation into two
+files: a file that contain the actual code and a second file that have the html
+related part, any problem with this ? I don't like to have the html part and
+the code part in one big and unreadable file.
+
+I'd like to hear other opinion about this.
+
+Thanks for now
+bye
+Gianluca
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values
new file mode 100644
index 0000000..e95ab61
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values
@@ -0,0 +1,11 @@
+Alt-id: <200907032250.17327.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 03 Jul 2009 22:50:17 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/body
new file mode 100644
index 0000000..27dca1e
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/body
@@ -0,0 +1,45 @@
+On Saturday 04 July 2009 02:31:26 Chris Ball wrote:
+> Hi Gianluca,
+>
+> > As i said in a previous mail, I am working on a "html" command
+> > for be. The goal is to be able to do something like "be html
+> > /web/page" to have in the /web/page directory some static html
+> > pages that basically are the dump of the be repository, much like
+> > ditz have. This will enable a simple and fast publish of the bus
+> > list and details on the web, at least in read only mode.
+>
+> It might be a good idea for "be html" to use the CherryPy web interface
+> that Steve is working on. The command could start up the CherryPy app
+> and scrape all of the available pages to get a stand-alone dump; this
+> would avoid having to keep two (okay, more than two at this point)
+> separate sets of HTML templates in the source tree. What do you think?
+
+It can be do, but this implies that CherryPy must be installed and configured,
+a thing that I don't want to impose. My idea is to offer a simpler way to have
+some html pages, where you just need to have BE installed.
+
+My very first implementation was a script that parse directly the .be directory
+to build the pages, without BE itself installed.
+
+
+> > 2) I see that every command is implemented with a python file in
+> > the becommand dir. For a better code, I'd like to split the
+> > command implementation into two files: a file that contain the
+> > actual code and a second file that have the html related part,
+> > any problem with this ? I don't like to have the html part and
+> > the code part in one big and unreadable file.
+>
+> I agree that becommands/*.py commands should not contain any HTML
+> layout code. Putting it somewhere else instead sounds fine.
+
+I am in doubt with the "somewhere else", since for now I put the html template
+into a separate file in the same directory. Suggestion ?
+
+thanks
+bye
+Gianluca
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values
new file mode 100644
index 0000000..1bf2dc4
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values
@@ -0,0 +1,14 @@
+Alt-id: <200907062246.54804.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 06 Jul 2009 22:46:54 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: b900f7fd-bab6-48c4-922c-a051f933da58
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/body
new file mode 100644
index 0000000..1d2b619
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/body
@@ -0,0 +1,43 @@
+On Thursday 25 June 2009 16:23:04 Steve Losh wrote:
+> On Jun 25, 2009, at 10:02 AM, Chris Ball <cjb@laptop.org> wrote:
+> >> Oh, and obviously there must still be bugs in BE. Please submit
+> >> more ;).
+> >
+> > Perhaps it's a good time to merge Steve Losh's CherryPy web interface?
+> >
+> > http://void.printf.net/pipermail/be-devel/2009-February/000095.html
+> > http://bitbucket.org/sjl/cherryflavoredbugseverywhere/
+>
+> Hey, I haven't touched the web interface in a while, but I should have
+> some time to fix some stuff up tonight and tomorrow. Hold off on
+> merging it in until then.
+>
+> I'm still curious as to what people think the role of a web interface
+> like this should be. When I wrote it I meant it as a single-user
+> interface like the command line one. It could definitely work as a
+> public, read-only interface too.
+
+I'd really like to have some sort of web interface for BE, also in read-only
+mode.
+
+I am thinking to write (actually I wrote some test code) a tool to parse a BE
+repository to output a set of static html pages to put online, like the "ditz
+html" command, but this was before I start to play with BE sourcecode, so now
+I ma thinking to implement it as a BE command.
+
+> If the goal is to allow more than one person to add issues, how should
+> commits go? One commit per change? Commit every X minutes if necessary?
+
+I think that a simple web interface should be read-only.
+
+Eventually, to allow to add issues also from the web interface, it should be
+done to a specific branch, one commit per change.
+
+just my 2 cents...
+bye
+Gianluca
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values
new file mode 100644
index 0000000..2285e1d
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values
@@ -0,0 +1,11 @@
+Alt-id: <200906252203.08535.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 25 Jun 2009 22:03:08 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/body b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/body
new file mode 100644
index 0000000..2e4f851
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/body
@@ -0,0 +1,43 @@
+On Monday 06 July 2009 12:48:39 W. Trevor King wrote:
+> On Mon, Jul 06, 2009 at 08:26:24AM +1000, Ben Finney wrote:
+> > "W. Trevor King" <wking@drexel.edu> writes:
+> > > On Sat, Jul 04, 2009 at 10:19:35AM +1000, Ben Finney wrote:
+> > > > Instead of a separate command for each output format, could we have
+> > > > a single "produce a static report of the bug database" command, and
+> > > > specify output format as an option?
+> > >
+> > > Do people like this architecture better than my be-xml-to-mbox
+> > > approach?
+> >
+> > I think this question is illuminated by the related question: Is mbox
+> > output a static report, or another read-write data store?
+>
+> Gianluca is clearly thinking about a static report:
+
+You are right, static, but not exactly a report as I think Ben is thinking
+
+>
+> On Fri, Jul 03, 2009 at 10:50:17PM +0200, Gianluca Montecchi wrote:
+> > The goal is to be able to do something like "be html /web/page" to have
+> > in the /web/page directory some static html pages that basically are the
+> > dump of the be repository, much like ditz have
+>
+> I think truly interactive frontends like Steve's working on need to be
+> build on top of libbe directly, since they'll need to make lots of
+> small changes to the database, and it's to slow to be reloading the
+> database for every change. Static dumps like my mbox or Gianluca's
+> html could just parse the xml output of `be list' and other be
+> commands.
+
+Ok, but if I want to have an html dump that is browseable, I need to parse the
+xml. Am I correct ?
+If yes, should not be easiear to use directly the libbe ?
+
+
+bye
+Gianluca
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values
new file mode 100644
index 0000000..b61fc2b
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values
@@ -0,0 +1,14 @@
+Alt-id: <200907062238.56930.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 06 Jul 2009 22:38:56 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: 55263144-9775-4b18-ab83-29d66ed91a53
+
diff --git a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values
new file mode 100644
index 0000000..093611e
--- /dev/null
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values
@@ -0,0 +1,20 @@
+assigned: Gianluca Montecchi <gian@grys.it>
+
+
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: Gianluca Montecchi <gian@grys.it>
+
+
+severity: wishlist
+
+
+status: assigned
+
+
+summary: Static html report generation
+
+
+time: Tue, 21 Jul 2009 18:43:06 +0000
+
diff --git a/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values b/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values
index 9da9004..70ec5f5 100644
--- a/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values
+++ b/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values
@@ -4,7 +4,7 @@ creator: abentley
severity: minor
-status: closed
+status: fixed
summary: auto-add files to revision control
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/body b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/body
new file mode 100644
index 0000000..53456f6
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/body
@@ -0,0 +1,10 @@
+Hmm, perhaps my thinking has been too revision-centric. I'm not
+really sure what other level of granularity is appropriate though.
+Both notifications and commits should be generated on a "per-session"
+level, so maybe I'll just ignore Arch and Mercurial (for whom revising
+history is difficult, so per-session commits can be more work) for the
+time being ;).
+
+In that case, _every_ commit will be a
+ notify-since <revision-id>
+sort of change, so I'll just use libbe.diff :).
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values
new file mode 100644
index 0000000..3e89c06
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values
@@ -0,0 +1,8 @@
+Content-type: text/plain
+
+
+Date: Wed, 22 Jul 2009 19:07:28 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/body b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/body
new file mode 100644
index 0000000..df90918
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/body
@@ -0,0 +1,16 @@
+Perhaps something like
+ be-handle-mail --notify-since <revision-id>
+to tell subscribers about changes since the specified revision.
+
+This would duplicate mail to P in our first example above, but that's
+not too annoying, and P might _want_ to know what R had merged from Q.
+
+On the other hand it would be annoying if 10 other repos merged Q and
+ran the notification.
+
+We could make the subscription something like
+ subscribe BUG-ID HOST-LIST
+e.g.
+ subscribe 1234 bugseverywhere.org,fancy_branch.com
+ subscribe abcd *
+To allow users to whitelist hosts they want updates from.
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values
new file mode 100644
index 0000000..ddb7442
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:52:25 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 950ac308-f3e1-4956-885a-e79ce3025fd5
+
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/body b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/body
new file mode 100644
index 0000000..8842587
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/body
@@ -0,0 +1,5 @@
+"all" and "new" might be valid shortnames?
+
+Nope, UUID string representations are restricted to hex (0-9a-f) and
+"-" as per RFC 4122 section 3.
+
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values
new file mode 100644
index 0000000..64258d9
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:53:02 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf
+
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/body b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/body
new file mode 100644
index 0000000..99d9cc3
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/body
@@ -0,0 +1,8 @@
+Obviously via the control interface:
+ subscribe #BUG-ID
+ subscribe new
+ subscribe all
+ unsubscribe #BUG-ID
+ ...
+Implemented via .extra_strings, although we'll need
+BugDir.extra_strings for the repo-wide new/all.
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values
new file mode 100644
index 0000000..eaad59f
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values
@@ -0,0 +1,8 @@
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:34:20 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/body b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/body
new file mode 100644
index 0000000..890a4b6
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/body
@@ -0,0 +1,10 @@
+This creates an interesting situation:
+ Person P subscribes to bug B in repo R.
+ Repo S merges repo R.
+ Person Q comments on B in S.
+ S notifies P :).
+which is nice. However
+ Person P subscribes to bug B in repo R.
+ Person Q comments on B in repo S.
+ R merges S.
+ P never notified about Q's comment.
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values
new file mode 100644
index 0000000..710ad30
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:34:32 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf
+
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/body b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/body
new file mode 100644
index 0000000..3c95f19
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/body
@@ -0,0 +1,2 @@
+The intereface changed a bit as I implemented it. See "be help
+subscribe" for details.
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values
new file mode 100644
index 0000000..1c908f7
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Wed, 22 Jul 2009 18:54:06 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf
+
diff --git a/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/values b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/values
new file mode 100644
index 0000000..aa22fab
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/values
@@ -0,0 +1,20 @@
+assigned: W. Trevor King <wking@drexel.edu>
+
+
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: 'subscribe/unsubscribe (bug #..., "new bugs", "all", etc.)'
+
+
+time: Tue, 21 Jul 2009 19:27:04 +0000
+
diff --git a/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/body b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/body
new file mode 100644
index 0000000..e39beb0
--- /dev/null
+++ b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/body
@@ -0,0 +1,24 @@
+> Currently, the code and interface of Bugs Everywhere speaks loosely
+> about the term “RCS”. Sometimes it means “revision control system”
+> referring in general to these types of system, and sometimes it talks
+> about GNU RCS, a specific system.
+
+I don't think we ever rever to GNU RCS. Our current libbe.rcs.RCS
+default implementation is a "don't version" backend for BE, but
+perhaps this is what you're refereing to.
+
+> I propose that “Version Control System” (“VCS”) has emerged as a
+> consensus term to refer to such systems in general, with no specific
+> reference to any particular system.
+
+Fair enough.
+
+> This will change some interface (e.g. the ‘rcs_name’ configuration
+> setting, and some of the methods on objects), but making this change
+> while Bugs Everywhere is small will be much less painful than making it
+> later.
+
+Hmm, we really need a method for upgrading the on-disk BugDir version.
+It's hard when you need to maintain backwards compatibilty with
+earlier versions in the VCS history....
+
diff --git a/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/values b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/values
new file mode 100644
index 0000000..7eb5b45
--- /dev/null
+++ b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/29ad0d9e-c05b-4793-bb8b-e8bf237f51b3/values
@@ -0,0 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 03 Aug 2009 23:26:22 +0000
+
+
+In-reply-to: a92f97a4-e9fe-43f7-bf56-5862b03a2641
+
diff --git a/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/body b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/body
new file mode 100644
index 0000000..f9c166b
--- /dev/null
+++ b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/body
@@ -0,0 +1,33 @@
+Howdy all,
+
+Currently, the code and interface of Bugs Everywhere speaks loosely
+about the term “RCS”. Sometimes it means “revision control system”
+referring in general to these types of system, and sometimes it talks
+about GNU RCS, a specific system.
+
+I propose that “Version Control System” (“VCS”) has emerged as a
+consensus term to refer to such systems in general, with no specific
+reference to any particular system.
+
+So I'd like to modify the Bugs Everywhere code to disambiguate: the term
+“VCS” will be used consistently to refer to version control systems in
+general, and “RCS” will only ever refer to GNU RCS.
+
+This will change some interface (e.g. the ‘rcs_name’ configuration
+setting, and some of the methods on objects), but making this change
+while Bugs Everywhere is small will be much less painful than making it
+later.
+
+Any objections? Any alternative suggestions?
+
+--
+ \ “I watched the Indy 500, and I was thinking that if they left |
+ `\ earlier they wouldn't have to go so fast.” —Steven Wright |
+_o__) |
+Ben Finney
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/values b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/values
new file mode 100644
index 0000000..5f3cf73
--- /dev/null
+++ b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/comments/a92f97a4-e9fe-43f7-bf56-5862b03a2641/values
@@ -0,0 +1,11 @@
+Alt-id: <87d49879v7.fsf@benfinney.id.au>
+
+
+Author: Ben Finney <ben@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 13 Jun 2009 19:37:16 +1000
+
diff --git a/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values
new file mode 100644
index 0000000..a5cc2cd
--- /dev/null
+++ b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: open
+
+
+summary: 'Terminology: Version control system vs. RCS'
+
+
+time: Mon, 03 Aug 2009 23:10:02 +0000
+
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/body
deleted file mode 100644
index b8a6cb9..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/body
+++ /dev/null
@@ -1 +0,0 @@
-Reply 1-bis
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/values
deleted file mode 100644
index 062b638..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/3e83dd98-d421-43b6-a78c-5da7aac5f279/values
+++ /dev/null
@@ -1,11 +0,0 @@
-Content-type: text/plain
-
-
-Date: Tue, 21 Jul 2009 21:33:37 +0000
-
-
-From: Gianluca Montecchi <gian@grys.it>
-
-
-In-reply-to: f776d667-6959-4cab-b05d-39e07702c04b
-
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/body
deleted file mode 100644
index 1e25ab8..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/body
+++ /dev/null
@@ -1 +0,0 @@
-Reply 1
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/values
deleted file mode 100644
index f335ced..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/433e2090-55d6-4b13-bc6d-0b509556f21b/values
+++ /dev/null
@@ -1,11 +0,0 @@
-Content-type: text/plain
-
-
-Date: Tue, 21 Jul 2009 21:33:29 +0000
-
-
-From: Gianluca Montecchi <gian@grys.it>
-
-
-In-reply-to: a0cbbd2e-a078-41ac-b583-900e9bb2abf3
-
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/body
deleted file mode 100644
index df57a1a..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/body
+++ /dev/null
@@ -1 +0,0 @@
-prova
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/values
deleted file mode 100644
index b2249ff..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/9d56f097-bf5b-4d8a-a83e-7ade8afd2b4c/values
+++ /dev/null
@@ -1,11 +0,0 @@
-Content-type: text/plain
-
-
-Date: Tue, 04 Aug 2009 19:48:58 +0000
-
-
-From: Gianluca Montecchi <gian@grys.it>
-
-
-In-reply-to: 433e2090-55d6-4b13-bc6d-0b509556f21b
-
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/body
deleted file mode 100644
index c8e09f8..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/body
+++ /dev/null
@@ -1 +0,0 @@
-Reply
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/values
deleted file mode 100644
index 06691af..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/a0cbbd2e-a078-41ac-b583-900e9bb2abf3/values
+++ /dev/null
@@ -1,11 +0,0 @@
-Content-type: text/plain
-
-
-Date: Tue, 21 Jul 2009 21:31:21 +0000
-
-
-From: Gianluca Montecchi <gian@grys.it>
-
-
-In-reply-to: f776d667-6959-4cab-b05d-39e07702c04b
-
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/b1a772a0-241f-42fc-8209-765162485b0a/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/b1a772a0-241f-42fc-8209-765162485b0a/body
deleted file mode 100644
index 2e6c9cb..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/b1a772a0-241f-42fc-8209-765162485b0a/body
+++ /dev/null
@@ -1 +0,0 @@
-commento \n su due righe
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/body
deleted file mode 100644
index 6db3446..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/body
+++ /dev/null
@@ -1 +0,0 @@
-Identato
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/values
deleted file mode 100644
index 2b282c0..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/c1b9bc11-71e1-473e-ad9c-cfba0a2533d5/values
+++ /dev/null
@@ -1,11 +0,0 @@
-Content-type: text/plain
-
-
-Date: Mon, 27 Jul 2009 20:08:02 +0000
-
-
-From: Gianluca Montecchi <gian@grys.it>
-
-
-In-reply-to: d74a6a82-6a08-472b-86d8-b1546c4d460f
-
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/body
deleted file mode 100644
index bea1060..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/body
+++ /dev/null
@@ -1 +0,0 @@
-Commento normale
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/values
deleted file mode 100644
index e26b88e..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/d74a6a82-6a08-472b-86d8-b1546c4d460f/values
+++ /dev/null
@@ -1,8 +0,0 @@
-Content-type: text/plain
-
-
-Date: Wed, 22 Jul 2009 20:05:15 +0000
-
-
-From: Gianluca Montecchi <gian@grys.it>
-
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/body b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/body
deleted file mode 100644
index 184fdad..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/body
+++ /dev/null
@@ -1 +0,0 @@
-Commento di test
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/values
deleted file mode 100644
index dd1fd4b..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/f776d667-6959-4cab-b05d-39e07702c04b/values
+++ /dev/null
@@ -1,8 +0,0 @@
-Content-type: text/plain
-
-
-Date: Mon, 20 Jul 2009 21:54:57 +0000
-
-
-From: Gianluca Montecchi <gian@grys.it>
-
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/values b/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/values
deleted file mode 100644
index ba1e385..0000000
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/values
+++ /dev/null
@@ -1,20 +0,0 @@
-assigned: Gianluca Montecchi <gian@grys.it>
-
-
-creator: gianluca <gian@galactica>
-
-
-reporter: gianluca <gian@galactica>
-
-
-severity: minor
-
-
-status: assigned
-
-
-summary: Bug di test
-
-
-time: Fri, 03 Jul 2009 20:19:36 +0000
-
diff --git a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values
index c8719a0..62b29d5 100644
--- a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values
+++ b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values
@@ -1,28 +1,11 @@
+Content-type: text/plain
+Date: Mon, 16 Jul 2007 15:23:47 +0000
-Content-type=text/plain
+From: abentley
-
-
-
-Date=Mon, 16 Jul 2007 15:23:47 +0000
-
-
-
-
-
-
-From=abentley
-
-
-
-
-
-
-In-reply-to=e173c09a-1b3e-4d8a-a86a-6b8c94a76247
-
-
+In-reply-to: e173c09a-1b3e-4d8a-a86a-6b8c94a76247
diff --git a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values
index 96cc18c..e31b44b 100644
--- a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values
+++ b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values
@@ -1,21 +1,8 @@
+Content-type: text/plain
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Sun, 15 Jul 2007 13:34:52 +0000
-
-
-
-
-
-
-From=jelmer
+Date: Sun, 15 Jul 2007 13:34:52 +0000
+From: jelmer
diff --git a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values
index 9b17373..c471b0f 100644
--- a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values
+++ b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values
@@ -4,7 +4,7 @@ creator: jelmer
severity: minor
-status: closed
+status: fixed
summary: should check not just EDITOR but also VISUAL.
diff --git a/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/body b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/body
new file mode 100644
index 0000000..90b386a
--- /dev/null
+++ b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/body
@@ -0,0 +1,20 @@
+I'm all for flexibility, so long as it doesn't require too much
+hackery to implement it. You'll have two problems:
+
+ * Determining what to commit.
+
+ You'd have to have RCS keep a log of all versioned files it
+ touched, and extend .commit() to accept the keyword list "files"
+ and commit only those files. This is doable, but maybe not worth
+ the trouble.
+
+ * Generating meaningful commit messages.
+
+ You'd have to add this functionality to each command (and future
+ commands).
+
+This would probably not be a good idea for the Arch and Mercurial
+backends, since they have a limited ability to rewrite history when
+you screw up your commit message (as far as I can tell). Mercurial
+does have "hg rollback", but it only works once, and lots of
+typo-correction commits would just make the logs awkward.
diff --git a/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values
new file mode 100644
index 0000000..5823128
--- /dev/null
+++ b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Fri, 24 Jul 2009 12:33:58 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: b17a561a-6100-490e-84eb-d1ae4b617940
+
diff --git a/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/body b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/body
new file mode 100644
index 0000000..c88a838
--- /dev/null
+++ b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/body
@@ -0,0 +1,9 @@
+...
+Also, why doesn't be commit after it takes an action? I think it's
+kinda weird that I have to commit after creating a new bug.
+...
+
+as posted in
+ http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=477125
+ on
+ Fri, 12 Jun 2009 17:03:02 +0200
diff --git a/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values
new file mode 100644
index 0000000..c069931
--- /dev/null
+++ b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values
@@ -0,0 +1,8 @@
+Content-type: text/plain
+
+
+Date: Fri, 24 Jul 2009 12:09:02 +0000
+
+
+From: Martin F Krafft <madduck@debian.org>
+
diff --git a/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/values b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/values
new file mode 100644
index 0000000..d060e87
--- /dev/null
+++ b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/values
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: Martin F Krafft <madduck@debian.org>
+
+
+severity: wishlist
+
+
+status: open
+
+
+summary: Allow autocommit option for command line interface?
+
+
+time: Fri, 24 Jul 2009 12:04:08 +0000
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/body
new file mode 100644
index 0000000..fa9e963
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/body
@@ -0,0 +1,36 @@
+* W. Trevor King (wking@drexel.edu) wrote:
+> One problem is that we don't actually have "releases". People just
+> clone a branch, install, and go.
+
+ This is actually the main reason I've manually mirrored the tree in
+the past, so that users of our projects can get BE. If tarballs were
+available I probably wouldn't even bother, but bzr really isn't a nice
+dependency for just submitting/commenting on bugs.
+
+ Isn't there a bzr web interface that at least supports creating
+tarballs/zips? It is pretty standard functionality for most other VCS'
+web interfaces so I'm guessing there must be, but loggerhead seems not
+to support it.
+
+ If it is a case of not having the hardware to host a more featureful
+web UI I may be able to offer some assistance.
+
+> If you're worried about stability, just clone from a more stable branch
+> (i.e., Chris' trunk). I think > this is good for distributed development,
+> but maybe makes it hard to package into a conventional release-based system.
+> With the bzr patch number in setup.py as the patch release number, I would be
+> releasing my 0.1.363 while Chris releases his 0.1.314, even though they're at
+> about the same point. I would rather be releasing my
+> 0.1.20090714121347
+> while Chris releases his
+> 0.1.20090713154540
+> Since then the similarity is clearer.
+
+ Both approaches seem pretty odd to me, as a user you would have no
+idea if 0.1.200910302359 has the fixes you required in a release you
+were using that was numbered 0.1.200907141554. Surely you'd at least be
+{pre,suf}fixing a branch name to the version.
+
+Thanks,
+
+James
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values
new file mode 100644
index 0000000..c064938
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values
@@ -0,0 +1,14 @@
+Alt-id: <20090714142942.GA5717@ukfsn.org>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 15:29:42 +0100
+
+
+From: James Rowe <jnrowe@gmail.com>
+
+
+In-reply-to: ea01c122-e629-4d5c-afa7-b180f4a8748b
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/body
new file mode 100644
index 0000000..7e1434b
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/body
@@ -0,0 +1,115 @@
+On Tue, Jul 14, 2009 at 03:29:42PM +0100, James Rowe wrote:
+> * W. Trevor King (wking@drexel.edu) wrote:
+> > One problem is that we don't actually have "releases". People just
+> > clone a branch, install, and go.
+>
+> This is actually the main reason I've manually mirrored the tree in
+> the past, so that users of our projects can get BE. If tarballs were
+> available I probably wouldn't even bother, but bzr really isn't a nice
+> dependency for just submitting/commenting on bugs.
+
+Fair enough. It will be good when we get a commit-able web interface
+and/or email interface going.
+
+> Isn't there a bzr web interface that at least supports creating
+> tarballs/zips? It is pretty standard functionality for most other VCS'
+> web interfaces so I'm guessing there must be, but loggerhead seems not
+> to support it.
+
+Unfortunately, people would still need bzr to install the versioned source:
+
+ libbe/_version.py:
+ bzr version-info --format python > $@
+
+So you'll need a "release" target in the makefile to build a bzr-less
+install. While you're at it, you should probably compile the manpage
+too to remove the docbook-to-man dependency.
+
+Do people want a HEAD tarball too? There must be a bzr equivalent of
+ .git/hooks/post-update
+but I don't know what it is.
+
+> > If you're worried about stability, just clone from a more stable branch
+> > (i.e., Chris' trunk). I think > this is good for distributed development,
+> > but maybe makes it hard to package into a conventional release-based system.
+> > With the bzr patch number in setup.py as the patch release number, I would be
+> > releasing my 0.1.363 while Chris releases his 0.1.314, even though they're at
+> > about the same point. I would rather be releasing my
+> > 0.1.20090714121347
+> > while Chris releases his
+> > 0.1.20090713154540
+> > Since then the similarity is clearer.
+>
+> Both approaches seem pretty odd to me, as a user you would have no
+> idea if 0.1.200910302359 has the fixes you required in a release you
+> were using that was numbered 0.1.200907141554. Surely you'd at least be
+> {pre,suf}fixing a branch name to the version.
+
+"be --version" currently gives you the revision id:
+ wking@drexel.edu-20090714121347-c6rloikst1t3m5yl
+which tells you exactly which commit your installed version is based on.
+If we want stick with revision numbers, how about:
+ major.minor.revno-branch_nick
+But then we'd have to pick "unique" branch nicknames...
+
+I'd sliced out the timestamp portion of the revision id so that the
+"patch-number" would be an integer and the branch name wasn't
+references, so that "upgrading" from one branch to another could be
+transparent to the users (who just see an increading timestamp), but
+still available to the developers (who know when commits were made to
+the branches they track, and the likelyhood of to-the-second commit
+collisions in official packages is small).
+
+On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote:
+> "W. Trevor King" <wking@drexel.edu> writes:
+>
+> > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote:
+> > > Please, no. Timestamps aren't version strings, that's conflating two
+> > > pieces of information with very different meanings. Correlating the
+> > > two is the job of a changelog.
+> >
+> > Which we don't bother keeping (also NEWS), since "bzr log" works so
+> > nicely.
+>
+> That's not a changelog, that's a commit log of every source-level commit
+> made. Far too much detail for a changelog of *user-visible* changes
+> associated with a release.
+
+I need a user around to help me determine "user-visable" changes ;).
+My labmates loose interest after be init/new/comment :p. None of
+which has ever changed, other than set-root -> init ;).
+
+> > The timestamp should at least replace the patch release number, which
+> > you agree is-desirable-to increase motonically ;).
+>
+> I still disagree that a timestamp is the right thing to use there. If
+> you want a monotonically-increasing indicator of which revision we're up
+> to, that's immediately available with the revision number from VCS on
+> the main branch. That also has the advantage of producing consecutive
+> numbers for each revision, by definition.
+
+But not during branch-switches, while my method skips large regions,
+but probably increases during any reasonable branch-switch. For
+example, when I upgraded to rich root to pull Ben's patch, I'm not
+sure if Chris upgraded the trunk and merged my branch, or just ditched
+the trunk and cloned my branch. Using actual bzr revision numbers
+would make switching branches that either wrong (in the case of
+rev-id decreases) or confusing (in the case of a single
+non-consecutive increase).
+
+On Tue, Jul 14, 2009 at 11:11:31AM -0400, Chris Ball wrote:
+> > I agree that's a problem. I think the solution is to start making
+> > releases, with specific version strings, as source tarballs.
+>
+> I'm happy to do this if people think it would be useful, and I don't
+> yet have a strong opinion on whether the releases should come with
+> version numbers or timestamps.
+
+I imagine the news from 2006 to now will be somewhat abridged ;).
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values
new file mode 100644
index 0000000..4538a9f
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values
@@ -0,0 +1,14 @@
+Alt-id: <20090714171725.GB10445@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 13:17:25 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 0c40c13a-3515-4b45-a8c3-142cceab9254
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/body
new file mode 100644
index 0000000..a0b6a14
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/body
@@ -0,0 +1,58 @@
+Chris Ball <cjb@laptop.org> writes:
+
+> Hi,
+>
+> > That's not a changelog, that's a commit log of every source-level
+> > commit made. Far too much detail for a changelog of
+> > *user-visible* changes associated with a release.
+>
+> I think I agree with both of you. :) It seems like it's both true that
+> there's no point in keeping a GNU-style ChangeLog these days
+
+I think I have a better understanding of why this apparent disagreement
+occurred, and it was due to my sloppy use of terms.
+
+Looking into it further, it seems there is a certain expectation (set,
+in part, by the long-standing GNU coding conventions) that a “GNU-style
+ChangeLog” contains not only a particular *format*, but information at
+a particular level of *detail*.
+
+That is, a GNU ChangeLog is intended for the style of work where one
+logs all the changes made to every file in the tree each working day,
+and then makes a new day's entry above that, and so on. This is, of
+course, entirely redundant with a VCS revision history, which makes all
+the commit messages available on request.
+
+So to disambiguate, that's not what I meant. I'm more familiar with a
+less-frequently-updated and less-fine-detail change log; perhaps more
+akin to the GNU-style “NEWS” file.
+
+> and that if we make a release we should write an announce mail that
+> directly mentions new user-visible changes as well as attaching the
+> commit log. That smaller list of highly user-visible changes could
+> live in NEWS, or in the announce mail, or both.
+
+Yes, that's mostly what I meant.
+
+I actually don't think the commit log needs to be part of the release at
+all. It's of interest only to those who want fine-level detail about
+changes to every file, and for that purpose I think read access to the
+VCS is much better. Packaging a static copy of the commit log as plain
+text seems pointless.
+
+Rather, we should treat a user-changes level “NEWS” file (or whatever
+name we choose for it) as part of the documentation, and set the
+expectation among the team that it will be updated for each user-visible
+change being worked on, like any other documentation.
+
+--
+ \ “… Nature … is seen to do all things Herself and through |
+ `\ herself of own accord, rid of all gods.” —Titus Lucretius |
+_o__) Carus, c. 40 BCE |
+Ben Finney
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values
new file mode 100644
index 0000000..4e3ade1
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values
@@ -0,0 +1,14 @@
+Alt-id: <87hbxdhtkp.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 16 Jul 2009 19:21:10 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/body
new file mode 100644
index 0000000..5f478b5
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/body
@@ -0,0 +1,96 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+W. Trevor King wrote:
+> Thinking about this some more, I think that the role of the
+> main-branch is to officially sanction the current state of the code as
+> "released". If a series of commits will leave a branch in a
+> known-unusable form, they should be carried out in some appropriately
+> named development branch. Then the log of commits to the main branch
+> ("bzr log -n 1" for bzr > ) should produce a fairly respectable
+> changelog.
+
+This is how we develop bzr itself. The mainline is controlled by PQM,
+which is a tool that merges feature branches, runs the tests, and
+commits only if the tests pass.
+
+$ bzr log --short --limit 10
+ 4534 Canonical.com Patch Queue Manager 2009-07-14 [merge]
+ (abentley) Implement merge --interactive
+
+ 4533 Canonical.com Patch Queue Manager 2009-07-14 [merge]
+ (jml) Merge in changes from 1.17 branch.
+
+ 4532 Canonical.com Patch Queue Manager 2009-07-14 [merge]
+ (igc) zc.buildout Windows build support (Sidnei da Silva)
+
+ 4531 Canonical.com Patch Queue Manager 2009-07-13 [merge]
+ (vila) Delete forgotten debug print
+
+ 4530 Canonical.com Patch Queue Manager 2009-07-13 [merge]
+ (vila) Isolate some tests from TZ
+
+ 4529 Canonical.com Patch Queue Manager 2009-07-13 [merge]
+ (igc) Bazaar 2.0 Upgrade Guide
+
+ 4528 Canonical.com Patch Queue Manager 2009-07-13 [merge]
+ (mbp) correction to news
+
+ 4527 Canonical.com Patch Queue Manager 2009-07-13 [merge]
+ (jml) Merge in 1.17 branch, updating version numbers and NEWS file.
+
+ 4526 Canonical.com Patch Queue Manager 2009-07-10 [merge]
+ (mbp, vila) Finish the *_implementation to per_* test renaming
+
+ 4525 Canonical.com Patch Queue Manager 2009-07-10 [merge]
+ (vila) Quicker check for changes in mutable trees
+
+You can also see all the merges as they come into the mainline:
+
+$ bzr log --short --limit 10 --include-merges
+ 4534 Canonical.com Patch Queue Manager 2009-07-14 [merge]
+ (abentley) Implement merge --interactive
+
+ 4526.6.15 Aaron Bentley 2009-07-14
+ Update command help
+
+ 4526.6.14 Aaron Bentley 2009-07-14
+ Use default DiffWriter.
+
+ 4526.6.13 Aaron Bentley 2009-07-14
+ Add docstring to do_interactive.
+
+ 4526.6.12 Aaron Bentley 2009-07-14
+ Updates from review.
+
+ 4526.6.11 Aaron Bentley 2009-07-13
+ Update NEWS.
+
+ 4526.6.10 Aaron Bentley 2009-07-13 [merge]
+ Merged apply-vocab into merge-interactive.
+
+ 4526.7.4 Aaron Bentley 2009-07-13 [merge]
+ Merged bzr.dev into apply-vocab.
+
+ 4526.6.9 Aaron Bentley 2009-07-13 [merge]
+ Merged apply-vocab into merge-interactive.
+
+ 4526.7.3 Aaron Bentley 2009-07-13
+ Test shelve_change.
+
+> This also means that _every_commit_ to a main branch would
+> be an official release.
+
+We don't do that. We have official releases every 4 weeks, but we do
+believe that running bzr.dev is pretty safe, because it's always tested
+and our test suite is quite thorough.
+
+Aaron
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
+
+iEYEARECAAYFAkpcznIACgkQ0F+nu1YWqI0yhACePTFUUp6u+Dw+8IRwWOWBQRtb
+TgsAniJq4lqnDfjNACMr7IEt7xYJhx7m
+=BbGG
+-----END PGP SIGNATURE-----
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values
new file mode 100644
index 0000000..5134cf2
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values
@@ -0,0 +1,14 @@
+Alt-id: <4A5CCE76.9040106@aaronbentley.com>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 14:29:10 -0400
+
+
+From: Aaron Bentley <aaron@aaronbentley.com>
+
+
+In-reply-to: ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/body
new file mode 100644
index 0000000..b34e037
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/body
@@ -0,0 +1,52 @@
+On Tue, Jul 14, 2009 at 07:34:04PM +0100, jnrowe@gmail.com wrote:
+> [This time to the list]
+>
+> * W. Trevor King (wking@drexel.edu) wrote:
+> > On Tue, Jul 14, 2009 at 03:29:42PM +0100, James Rowe wrote:
+> > > Isn't there a bzr web interface that at least supports creating
+> > > tarballs/zips? It is pretty standard functionality for most other VCS'
+> > > web interfaces so I'm guessing there must be, but loggerhead seems not
+> > > to support it.
+> >
+> > Unfortunately, people would still need bzr to install the versioned source:
+> >
+> > libbe/_version.py:
+> > bzr version-info --format python > $@
+>
+> I hadn't even seen that change go in. The last upstream change in the
+> version I have installed locally was by you on 2008-11-24.
+
+It's only been in Chris' http://bzr.bugseverywhere.org/be/ branch
+since revno: 321, 2009-06-25. Obviously we may have to adjust the
+--verison output once we settle on a versioning scheme, but whatever
+we pick, I think having the auto-generated libbe/_version.py around
+for pinpointing bugs is worth the trouble of requiring bzr when
+building distribution tarballs.
+
+> > So you'll need a "release" target in the makefile to build a bzr-less
+> > install. While you're at it, you should probably compile the manpage
+> > too to remove the docbook-to-man dependency.
+>
+> Maybe for others. Our packages just don't have the manpage as it is only
+> the "be help" text reformatted, the easy option is sometimes the right
+> one :) Also, I've just noticed that it has even less documentation in
+> the bzr tree[1] making its installation much less compelling unless your
+> packaging rules require a man page like Debians.
+>
+> Out of curiosity why is the Makefile being used for this stuff anyway?
+> It is going to make it difficult to build locally when we finally get
+> around to merging. Examples: If distutils was being used exclusively it
+> would fix the #! lines in xml/*. We'd be able to point Python
+> $version_of_the_day at setup.py instead of having to sed the Makefile or
+> run setup and manually install other files.
+
+I speak Makefile better than I speak distutils ;). I'm not sure how
+to translate the be.1 generation/installation or the libbe/_version.py
+generation into distutils. Anyone else?
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values
new file mode 100644
index 0000000..0c1e529
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values
@@ -0,0 +1,14 @@
+Alt-id: <20090714191145.GB10606@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 15:11:45 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 6e315abe-a080-4369-8729-4aea2dee8494
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/body
new file mode 100644
index 0000000..7ffe231
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/body
@@ -0,0 +1,38 @@
+[This time to the list]
+
+* W. Trevor King (wking@drexel.edu) wrote:
+> On Tue, Jul 14, 2009 at 03:29:42PM +0100, James Rowe wrote:
+> > Isn't there a bzr web interface that at least supports creating
+> > tarballs/zips? It is pretty standard functionality for most other VCS'
+> > web interfaces so I'm guessing there must be, but loggerhead seems not
+> > to support it.
+>
+> Unfortunately, people would still need bzr to install the versioned source:
+>
+> libbe/_version.py:
+> bzr version-info --format python > $@
+
+ I hadn't even seen that change go in. The last upstream change in the
+version I have installed locally was by you on 2008-11-24.
+
+> So you'll need a "release" target in the makefile to build a bzr-less
+> install. While you're at it, you should probably compile the manpage
+> too to remove the docbook-to-man dependency.
+
+ Maybe for others. Our packages just don't have the manpage as it is only
+the "be help" text reformatted, the easy option is sometimes the right
+one :) Also, I've just noticed that it has even less documentation in
+the bzr tree[1] making its installation much less compelling unless your
+packaging rules require a man page like Debians.
+
+ Out of curiosity why is the Makefile being used for this stuff anyway?
+It is going to make it difficult to build locally when we finally get
+around to merging. Examples: If distutils was being used exclusively it
+would fix the #! lines in xml/*. We'd be able to point Python
+$version_of_the_day at setup.py instead of having to sed the Makefile or
+run setup and manually install other files.
+
+Thanks,
+
+James
+ 1. http://pullcord.laptop.org:4000/revision/314.1.15/doc/be.1.sgml
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values
new file mode 100644
index 0000000..a4534a2
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values
@@ -0,0 +1,14 @@
+Alt-id: <20090714183404.GB26032@ukfsn.org>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 19:34:04 +0100
+
+
+From: jnrowe@gmail.com
+
+
+In-reply-to: 1f40efc1-6efc-4dd8-bdd2-97907e5aa624
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/body
new file mode 100644
index 0000000..24ff7b0
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/body
@@ -0,0 +1,58 @@
+"W. Trevor King" <wking@drexel.edu> writes:
+
+> Currently setup.py sets the version number for BE to 0.0.193 and the
+> url to http://panoramicfeedback.com/opensource/. These are both a bit
+> outdated ;).
+
+Right, that should change.
+
+> I've switched my branch over to the current url, and moved to
+> last-commit-timestamp version numbers.
+
+Please, no. Timestamps aren't version strings, that's conflating two
+pieces of information with very different meanings. Correlating the two
+is the job of a changelog.
+
+> This removes the "prefered branch" issues with the old scheme, and
+> version numbers should increase monotonically
+
+The English word “should” is ambiguous in this context. Are you saying
+this is desirable, or are you predicting that it will likely be the
+case?
+
+I don't see how it's either, so am doubly confused :-)
+
+> but it looses any stability information suggested by the preceding
+> 0.0.
+
+The convention for three-part version strings is often:
+
+ * Major release number (big changes in how the program works,
+ increasing monotonically per major release, with “0”indicating no
+ major release yet)
+
+ * Minor release number (smaller impact on how the program works,
+ increasing monotonically per minor release, with “0” indicating no
+ minor release yet since the previous major)
+
+ * Patch release number (bug-fix and other changes that don't affect
+ the documented interface, increasing monotonically per patch
+ release, with “0” indicating no patch release since the previous
+ major or minor)
+
+Obviously there's no standard or enforcement for this, but that's the
+interpretation I usually give to dotted version strings in the absence
+of more formal declaration specific to the project.
+
+> We can add those back in if people want. Does the first 0 mean
+> "interfaces in flux" and the second 0 mean "lots of bugs"? If so, I
+> think we're up to 0.1, since the major features are pretty calm.
+
+I disagree with your interpretation and prefer mine, above; on that
+basis, I agree that we're at least up to version 0.1 by now :-)
+
+--
+ \ “A lot of water has been passed under the bridge since this |
+ `\ variation has been played.” chess book, Russia |
+_o__) |
+Ben Finney
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values
new file mode 100644
index 0000000..1b70837
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values
@@ -0,0 +1,14 @@
+Alt-id: <87ocrnjvat.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 22:36:26 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/body
new file mode 100644
index 0000000..8b32751
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/body
@@ -0,0 +1,14 @@
+Hi,
+
+ > Which we don't bother keeping (also NEWS), since "bzr log" works
+ > so nicely. If you really want an standard changelog, see
+ > http://mail.gnome.org/archives/desktop-devel-list/2007-September/msg00186.html
+
+Actually, there's a `bzr log --gnu-changelog` now, and `bzr help
+log-formats` offers some more styles. (None of them seem to match
+my preferred style for release announcements exactly, which would
+be `git shortlog`-style.)
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values
new file mode 100644
index 0000000..ea6e6aa
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values
@@ -0,0 +1,14 @@
+Alt-id: <m3ljmrfgot.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 11:05:38 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: ea01c122-e629-4d5c-afa7-b180f4a8748b
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/body
new file mode 100644
index 0000000..33a8d66
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/body
@@ -0,0 +1,51 @@
+I don't think anyone's changing their mind ;), so tallying the
+comments so far:
+
+On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote:
+> I still disagree that a timestamp is the right thing to use there. If
+> you want a monotonically-increasing indicator of which revision we're up
+> to, that's immediately available with the revision number from VCS on
+> the main branch. That also has the advantage of producing consecutive
+> numbers for each revision, by definition.
+
++1 for trunk version number.
+
+On Tue, Jul 14, 2009 at 05:27:52PM +0200, Elena of Valhalla wrote:
+> I also have a weak preference for version numbers, as long as they
+> give useful informations on the state the release.
+
++1 for trunk version number.
+
+On Tue, Jul 14, 2009 at 02:29:10PM -0400, Aaron Bentley wrote:
+> We don't do that. We have official releases every 4 weeks, but we do
+> believe that running bzr.dev is pretty safe, because it's always tested
+> and our test suite is quite thorough.
+
++1 for by hand version bumps.
+
+On Fri, Jul 17, 2009 at 11:37:49PM +0200, Gianluca Montecchi wrote:
+> The version number of trunk _is_ should be the official version number of the
+> Bugs Everywhere releases.
+> The version number in branch does not means nothing outside the branch.
+> At least we can have a mechanism to build a version number scheme that is
+> consistent for us to be able to merge branch easily.
+
++1 for trunk version number.
+
+And me with my timestamps ;).
+
+Sounds like we should go with trunk version number, but that it should
+be set by hand whenever Chris decides to release something, since the
+rest of us don't know what version the trunk is on. Unless we do
+something like:
+ bzr log -n 0 | grep -B2 'nick: be$' | head -n1 | sed 's/ *revno: \([0-9]*\).*/\1/'
+to extract the last trunk commit referenced from our branch.
+
+Implementation preferences? (i.e. Chris vs. regexp matching :p)
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values
new file mode 100644
index 0000000..1acfd91
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values
@@ -0,0 +1,14 @@
+Alt-id: <20090718105008.GA31639@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 18 Jul 2009 06:50:08 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: c35835c0-8f9f-4090-ba92-1f616867e486
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/body
new file mode 100644
index 0000000..063afcb
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/body
@@ -0,0 +1,30 @@
+Hi,
+
+ > That's not a changelog, that's a commit log of every source-level
+ > commit made. Far too much detail for a changelog of
+ > *user-visible* changes associated with a release.
+
+I think I agree with both of you. :) It seems like it's both true that
+there's no point in keeping a GNU-style ChangeLog these days, and that
+if we make a release we should write an announce mail that directly
+mentions new user-visible changes as well as attaching the commit log.
+That smaller list of highly user-visible changes could live in NEWS,
+or in the announce mail, or both.
+
+ > I agree that's a problem. I think the solution is to start making
+ > releases, with specific version strings, as source tarballs.
+
+I'm happy to do this if people think it would be useful, and I don't
+yet have a strong opinion on whether the releases should come with
+version numbers or timestamps.
+
+Thanks,
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values
new file mode 100644
index 0000000..761c219
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values
@@ -0,0 +1,14 @@
+Alt-id: <m3k52bfgf0.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 11:11:31 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/body
new file mode 100644
index 0000000..1e2a870
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/body
@@ -0,0 +1,37 @@
+On Tue, Jul 14, 2009 at 01:17:25PM -0400, W. Trevor King wrote:
+> On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote:
+> > "W. Trevor King" <wking@drexel.edu> writes:
+> >
+> > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote:
+> > > > Please, no. Timestamps aren't version strings, that's conflating two
+> > > > pieces of information with very different meanings. Correlating the
+> > > > two is the job of a changelog.
+> > >
+> > > Which we don't bother keeping (also NEWS), since "bzr log" works so
+> > > nicely.
+> >
+> > That's not a changelog, that's a commit log of every source-level commit
+> > made. Far too much detail for a changelog of *user-visible* changes
+> > associated with a release.
+>
+> I need a user around to help me determine "user-visable" changes ;).
+> My labmates loose interest after be init/new/comment :p. None of
+> which has ever changed, other than set-root -> init ;).
+
+Thinking about this some more, I think that the role of the
+main-branch is to officially sanction the current state of the code as
+"released". If a series of commits will leave a branch in a
+known-unusable form, they should be carried out in some appropriately
+named development branch. Then the log of commits to the main branch
+("bzr log -n 1" for bzr > ) should produce a fairly respectable
+changelog. Obviously we are all quite guilty of doing most of our
+development in single branches, but it may be a useful model going
+forward. This also means that _every_commit_ to a main branch would
+be an official release.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values
new file mode 100644
index 0000000..4439bad
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values
@@ -0,0 +1,14 @@
+Alt-id: <20090714182034.GA10606@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 14:20:34 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 1f40efc1-6efc-4dd8-bdd2-97907e5aa624
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/body
new file mode 100644
index 0000000..e02bd38
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/body
@@ -0,0 +1,25 @@
+On Tue, Jul 14, 2009 at 5:11 PM, Chris Ball<cjb@laptop.org> wrote:
+>   > I agree that's a problem. I think the solution is to start making
+>   > releases, with specific version strings, as source tarballs.
+>
+> I'm happy to do this if people think it would be useful, and I don't
+> yet have a strong opinion on whether the releases should come with
+> version numbers or timestamps.
+
+as an user of be that plans to try and "package" it for openembedded,
+a release would be very useful: writing a recipe that downloads a
+specific commit from bzr and builds it is probably feasible, but
+definitely not ideal.
+
+I also have a weak preference for version numbers, as long as they
+give useful informations on the state the release.
+
+--
+Elena ``of Valhalla''
+
+email: elena.valhalla@gmail.com
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values
new file mode 100644
index 0000000..5d49c42
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values
@@ -0,0 +1,14 @@
+Alt-id: <5c5e5c350907140827u218553e8rc5773325d43c2bf2@mail.gmail.com>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 17:27:52 +0200
+
+
+From: Elena of Valhalla <elena.valhalla@gmail.com>
+
+
+In-reply-to: aad59898-8949-44fb-ad0b-2acea6eb2ef8
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/body
new file mode 100644
index 0000000..d8014d2
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/body
@@ -0,0 +1,102 @@
+On Thursday 16 July 2009 12:38:55 W. Trevor King wrote:
+> On Thu, Jul 16, 2009 at 07:32:31PM +1000, Ben Finney wrote:
+> > "W. Trevor King" <wking@drexel.edu> writes:
+> > > On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote:
+> > > > "W. Trevor King" <wking@drexel.edu> writes:
+> > > > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote:
+> > > > > > Please, no. Timestamps aren't version strings, that's conflating
+> > > > > > two pieces of information with very different meanings.
+> > > > > > Correlating the two is the job of a [NEWS file].
+> > > >
+> > > > If you want a monotonically-increasing indicator of which revision
+> > > > we're up to, that's immediately available with the revision number
+> > > > from VCS on the main branch. That also has the advantage of
+> > > > producing consecutive numbers for each revision, by definition.
+> > >
+> > > But not during branch-switches, while my method skips large regions,
+> > > but probably increases during any reasonable branch-switch.
+> >
+> > I've read this several times now, and I don't see what it's saying.
+> >
+> > The assumption I'm making is that there is a single canonical “main
+> > branch”, from which releases will be made.
+>
+> I don't think you need to assume this. See my "virtual branch"
+> argument below.
+
+But if we have a canonical "main branch" that we release, and the packager
+get, we can refer to it as the stable branch, that it is not a bad idea.
+
+
+
+> > The version number set in that branch is the one which determines
+> > the version of Bugs Everywhere as a whole.
+>
+> If you are suggesting that the dev branches adjust their release
+> number _by_hand_ to match the current trunk release number, that
+> allows switching, but sounds like a lot of work and isn't correct
+> anyway, since they are not in the same state as the trunk.
+
+The version number of trunk _is_ should be the official version number of the
+Bugs Everywhere releases.
+The version number in branch does not means nothing outside the branch.
+At least we can have a mechanism to build a version number scheme that is
+consistent for us to be able to merge branch easily.
+
+> > The revision number is only useful in the context of the branch, so it
+> > only matters when comparing versions within a branch. When you switch
+> > between branches, if you're interested in the revision number you'll
+> > still need to know which branch you're talking about.
+>
+> I think this is our main disagreement. I see all the branches as part
+> of the same codebase, with monotonically increasing timestamp patch
+> numbers. If you were to collapse all the commit snapshots down into a
+> single chronological "virtual branch", it would still make sense, it
+> would just be a bit unorganized. We do all try to move in the same
+> general direction ;).
+
+I don't think that, outside the developers, a version number like
+
+cjb@laptop.org-20090713154540-ve4pmydqzb1ghgvc
+
+is a good choice, not for the user of BE, not for the packager of BE
+
+
+> > This, then, is an argument for not having the revision number in the
+> > version string at all. The version then becomes a more traditional
+> > “major.minor.patch” tuple, and is only ever updated when some release
+> > manager of the canonical branch decides it's correct to do so.
+>
+> It is an argument for not using the revision number. You can avoid
+> revision numbers by using hand-coded patch numbers, or by using
+> timestamps, which is what we're trying to decide on :p.
+
+We can use both.
+During the development we can use version number like
+
+x.y.z.timestamp
+
+As we decide to release a stable version, the release manager set the version
+number to a more traditional x.y.z format, and create a branch (stable branch)
+
+This way we have these advantages:
+
+1) an user have a simple version number to use for bug report/feature
+request/help request
+
+2) a packager have an easy life to choose to package a stable or a trunk
+version, knowing what are they doing
+
+bonus) we can maintain a stable and a developmente source tree/branch, where
+in the development tree we can make also backward incompatible modification to
+the source without making any damage to the users/packagers, while in the
+stable branch we can make only bugfix/security fix or port from the devel branch
+some interesting features as long as they don't break compatibility.
+
+bye
+Gianluca
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values
new file mode 100644
index 0000000..a828a3a
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values
@@ -0,0 +1,14 @@
+Alt-id: <200907172337.49779.gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 17 Jul 2009 23:37:49 +0200
+
+
+From: Gianluca Montecchi <gian@grys.it>
+
+
+In-reply-to: f925e56f-26f9-4620-82fb-a0f160f27921
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/body
new file mode 100644
index 0000000..4e8445a
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/body
@@ -0,0 +1,18 @@
+Currently setup.py sets the version number for BE to 0.0.193 and the
+url to http://panoramicfeedback.com/opensource/. These are both a bit
+outdated ;). I've switched my branch over to the current url, and
+moved to last-commit-timestamp version numbers. This removes the
+"prefered branch" issues with the old scheme, and version numbers
+should increase monotonically, but it looses any stability information
+suggested by the preceding 0.0.
+
+We can add those back in if people want. Does the first 0 mean
+"interfaces in flux" and the second 0 mean "lots of bugs"? If so, I
+think we're up to 0.1, since the major features are pretty calm.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values
new file mode 100644
index 0000000..94bb94d
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values
@@ -0,0 +1,11 @@
+Alt-id: <20090714110543.GB4855@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 07:05:43 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/body
new file mode 100644
index 0000000..fce4941
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/body
@@ -0,0 +1,72 @@
+On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote:
+> "W. Trevor King" <wking@drexel.edu> writes:
+> > I've switched my branch over to the current url, and moved to
+> > last-commit-timestamp version numbers.
+>
+> Please, no. Timestamps aren't version strings, that's conflating two
+> pieces of information with very different meanings. Correlating the two
+> is the job of a changelog.
+
+Which we don't bother keeping (also NEWS), since "bzr log" works so nicely.
+If you really want an standard changelog, see
+ http://mail.gnome.org/archives/desktop-devel-list/2007-September/msg00186.html
+
+> > This removes the "prefered branch" issues with the old scheme, and
+> > version numbers should increase monotonically
+>
+> The English word “should” is ambiguous in this context. Are you saying
+> this is desirable, or are you predicting that it will likely be the
+> case?
+
+Both.
+
+> I don't see how it's either, so am doubly confused :-)
+
+The timestamp should at least replace the patch release number, which
+you agree is-desirable-to increase motonically ;). I also predict
+that it will increase monotonically for any given branch, since the
+branch HEAD will have both the most recent commit and the highest
+version number. The only problem I can think of is having your clock
+_way_ off, and that is unlikely enough to ignore. If you hop between
+branches, the timestamp is much more likely to increase going to the
+more modern branch than the bzr revision number, which desynchronize
+between branches fairly quickly.
+
+> The convention for three-part version strings is often:
+>
+> * Major release number (big changes in how the program works,
+> increasing monotonically per major release, with “0”indicating no
+> major release yet)
+>
+> * Minor release number (smaller impact on how the program works,
+> increasing monotonically per minor release, with “0” indicating no
+> minor release yet since the previous major)
+>
+> * Patch release number (bug-fix and other changes that don't affect
+> the documented interface, increasing monotonically per patch
+> release, with “0” indicating no patch release since the previous
+> major or minor)
+
+One problem is that we don't actually have "releases". People just
+clone a branch, install, and go. If you're worried about stability,
+just clone from a more stable branch (i.e., Chris' trunk). I think
+this is good for distributed development, but maybe makes it hard to
+package into a conventional release-based system. With the bzr patch
+number in setup.py as the patch release number, I would be releasing
+my 0.1.363 while Chris releases his 0.1.314, even though they're at
+about the same point. I would rather be releasing my
+ 0.1.20090714121347
+while Chris releases his
+ 0.1.20090713154540
+Since then the similarity is clearer.
+
+At any rate, I think the two approaches are close enough that an
+auto-updating timestamp beats a manually bumped patch number, since
+no-one ever actually bumps the patch number ;).
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values
new file mode 100644
index 0000000..c863757
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values
@@ -0,0 +1,14 @@
+Alt-id: <20090714133732.GB6160@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 14 Jul 2009 09:37:32 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 744435b7-1521-4059-a55d-f0c403d7b4d8
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/body
new file mode 100644
index 0000000..5eeb353
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/body
@@ -0,0 +1,88 @@
+On Thu, Jul 16, 2009 at 07:32:31PM +1000, Ben Finney wrote:
+> "W. Trevor King" <wking@drexel.edu> writes:
+>
+> > On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote:
+> > > "W. Trevor King" <wking@drexel.edu> writes:
+> > >
+> > > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote:
+> > > > > Please, no. Timestamps aren't version strings, that's conflating
+> > > > > two pieces of information with very different meanings.
+> > > > > Correlating the two is the job of a [NEWS file].
+> >
+> > > If you want a monotonically-increasing indicator of which revision
+> > > we're up to, that's immediately available with the revision number
+> > > from VCS on the main branch. That also has the advantage of
+> > > producing consecutive numbers for each revision, by definition.
+> >
+> > But not during branch-switches, while my method skips large regions,
+> > but probably increases during any reasonable branch-switch.
+>
+> I've read this several times now, and I don't see what it's saying.
+>
+> The assumption I'm making is that there is a single canonical “main
+> branch”, from which releases will be made.
+
+I don't think you need to assume this. See my "virtual branch"
+argument below.
+
+> The version number set in that branch is the one which determines
+> the version of Bugs Everywhere as a whole.
+
+If you are suggesting that the dev branches adjust their release
+number _by_hand_ to match the current trunk release number, that
+allows switching, but sounds like a lot of work and isn't correct
+anyway, since they are not in the same state as the trunk.
+
+> The revision number is only useful in the context of the branch, so it
+> only matters when comparing versions within a branch. When you switch
+> between branches, if you're interested in the revision number you'll
+> still need to know which branch you're talking about.
+
+I think this is our main disagreement. I see all the branches as part
+of the same codebase, with monotonically increasing timestamp patch
+numbers. If you were to collapse all the commit snapshots down into a
+single chronological "virtual branch", it would still make sense, it
+would just be a bit unorganized. We do all try to move in the same
+general direction ;).
+
+> Switching between branches doesn't change the canonical version string.
+
+Different released code should have different version numbers.
+
+> > For example, when I upgraded to rich root to pull Ben's patch, I'm not
+> > sure if Chris upgraded the trunk and merged my branch, or just ditched
+> > the trunk and cloned my branch. Using actual bzr revision numbers
+> > would make switching branches that either wrong (in the case of rev-id
+> > decreases) or confusing (in the case of a single non-consecutive
+> > increase).
+>
+> This, then, is an argument for not having the revision number in the
+> version string at all. The version then becomes a more traditional
+> “major.minor.patch” tuple, and is only ever updated when some release
+> manager of the canonical branch decides it's correct to do so.
+
+It is an argument for not using the revision number. You can avoid
+revision numbers by using hand-coded patch numbers, or by using
+timestamps, which is what we're trying to decide on :p.
+
+> If we use the ‘bzr version-info --format=python > foo_version.py’
+> command in some build routine, the branch's revision number will be
+> available directly within Python by importing that module. That would
+> allow it to be output in some UI, if that's what you're interested in
+> seeing.
+
+True. Which means that whichever version string wins out, the other
+side will still be able to access the info we both want included ;).
+We can certainly suggest that bug reporters submit their
+ be --verbose-version
+when they submit bugs. The only role of the official "version string"
+is to make life easy for packagers. If they woln't be switching
+branches, then either of our proposals are fine. If they will, then
+I think timestamps work better.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values
new file mode 100644
index 0000000..36f4007
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values
@@ -0,0 +1,14 @@
+Alt-id: <20090716103855.GA8579@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 16 Jul 2009 06:38:55 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: fdb615a4-168a-467b-8090-875c998455e5
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/body
new file mode 100644
index 0000000..b36292a
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/body
@@ -0,0 +1,55 @@
+"W. Trevor King" <wking@drexel.edu> writes:
+
+> On Wed, Jul 15, 2009 at 12:54:05AM +1000, Ben Finney wrote:
+> > "W. Trevor King" <wking@drexel.edu> writes:
+> >
+> > > On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote:
+> > > > Please, no. Timestamps aren't version strings, that's conflating
+> > > > two pieces of information with very different meanings.
+> > > > Correlating the two is the job of a [NEWS file].
+>
+> > If you want a monotonically-increasing indicator of which revision
+> > we're up to, that's immediately available with the revision number
+> > from VCS on the main branch. That also has the advantage of
+> > producing consecutive numbers for each revision, by definition.
+>
+> But not during branch-switches, while my method skips large regions,
+> but probably increases during any reasonable branch-switch.
+
+I've read this several times now, and I don't see what it's saying.
+
+The assumption I'm making is that there is a single canonical “main
+branch”, from which releases will be made. The version number set in
+that branch is the one which determines the version of Bugs Everywhere
+as a whole.
+
+The revision number is only useful in the context of the branch, so it
+only matters when comparing versions within a branch. When you switch
+between branches, if you're interested in the revision number you'll
+still need to know which branch you're talking about.
+
+Switching between branches doesn't change the canonical version string.
+
+> For example, when I upgraded to rich root to pull Ben's patch, I'm not
+> sure if Chris upgraded the trunk and merged my branch, or just ditched
+> the trunk and cloned my branch. Using actual bzr revision numbers
+> would make switching branches that either wrong (in the case of rev-id
+> decreases) or confusing (in the case of a single non-consecutive
+> increase).
+
+This, then, is an argument for not having the revision number in the
+version string at all. The version then becomes a more traditional
+“major.minor.patch” tuple, and is only ever updated when some release
+manager of the canonical branch decides it's correct to do so.
+
+If we use the ‘bzr version-info --format=python > foo_version.py’
+command in some build routine, the branch's revision number will be
+available directly within Python by importing that module. That would
+allow it to be output in some UI, if that's what you're interested in
+seeing.
+
+--
+ \ “Never do anything against conscience even if the state demands |
+ `\ it.” —Albert Einstein |
+_o__) |
+Ben Finney
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values
new file mode 100644
index 0000000..d373a73
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values
@@ -0,0 +1,14 @@
+Alt-id: <87d481ht1s.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 16 Jul 2009 19:32:31 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/body b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/body
new file mode 100644
index 0000000..30e3cbd
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/body
@@ -0,0 +1,44 @@
+"W. Trevor King" <wking@drexel.edu> writes:
+
+> On Tue, Jul 14, 2009 at 10:36:26PM +1000, Ben Finney wrote:
+> > Please, no. Timestamps aren't version strings, that's conflating two
+> > pieces of information with very different meanings. Correlating the
+> > two is the job of a changelog.
+>
+> Which we don't bother keeping (also NEWS), since "bzr log" works so
+> nicely.
+
+That's not a changelog, that's a commit log of every source-level commit
+made. Far too much detail for a changelog of *user-visible* changes
+associated with a release.
+
+> The timestamp should at least replace the patch release number, which
+> you agree is-desirable-to increase motonically ;).
+
+I still disagree that a timestamp is the right thing to use there. If
+you want a monotonically-increasing indicator of which revision we're up
+to, that's immediately available with the revision number from VCS on
+the main branch. That also has the advantage of producing consecutive
+numbers for each revision, by definition.
+
+> One problem is that we don't actually have "releases". People just
+> clone a branch, install, and go.
+
+I agree that's a problem. I think the solution is to start making
+releases, with specific version strings, as source tarballs.
+
+James Rowe <jnrowe@gmail.com> writes:
+
+> Isn't there a bzr web interface that at least supports creating
+> tarballs/zips?
+
+Even better: ‘bzr export /tmp/foo.tar.gz’ will create a source tarball
+of all the files in the branch's VCS inventory. All we need to do is
+start the practice of tagging a release in the VCS, and export the
+tarball at that time.
+
+--
+ \ “Pinky, are you pondering what I'm pondering?” “Well, I think |
+ `\ so (hiccup), but Kevin Costner with an English accent?” —_Pinky |
+_o__) and The Brain_ |
+Ben Finney
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values
new file mode 100644
index 0000000..aa9b55f
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values
@@ -0,0 +1,14 @@
+Alt-id: <87k52bjoxe.fsf_-_@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Wed, 15 Jul 2009 00:54:05 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
+
diff --git a/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/values
new file mode 100644
index 0000000..b9e8dff
--- /dev/null
+++ b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/values
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: wishlist
+
+
+status: open
+
+
+summary: How should we version BE?
+
+
+time: Tue, 21 Jul 2009 17:19:22 +0000
+
diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values
index 4cb1f35..59a3828 100644
--- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values
+++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values
@@ -1,21 +1,8 @@
+Content-type: text/plain
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 24 Mar 2005 17:04:47 +0000
-
-
-
-
-
-
-From=abentley
+Date: Thu, 24 Mar 2005 17:04:47 +0000
+From: abentley
diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values
index 51af41d..ddbdc15 100644
--- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values
+++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values
@@ -1,21 +1,8 @@
+Content-type: text/plain
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 24 Mar 2005 13:05:13 +0000
-
-
-
-
-
-
-From=abentley
+Date: Thu, 24 Mar 2005 13:05:13 +0000
+From: abentley
diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values
index 94a1f9f..e2a930c 100644
--- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values
+++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values
@@ -4,7 +4,7 @@ creator: abentley
severity: serious
-status: closed
+status: fixed
summary: Add test cases
diff --git a/.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values b/.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values
index 918f6be..5d80e70 100644
--- a/.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values
+++ b/.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values
@@ -10,7 +10,7 @@ severity: minor
status: wontfix
-summary: Add the html files for the status detail
+summary: Add the html files for the status detail to "be html" output
time: Fri, 03 Jul 2009 22:56:09 +0000
diff --git a/.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values b/.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values
index 600f2c3..4b6bb4f 100644
--- a/.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values
+++ b/.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values
@@ -10,7 +10,7 @@ severity: minor
status: wontfix
-summary: Add the html files for the severity detail
+summary: Add the html files for the severity detail to "be html" output
time: Fri, 03 Jul 2009 22:56:19 +0000
diff --git a/.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values b/.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values
index 8b58566..b859364 100644
--- a/.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values
+++ b/.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values
@@ -10,7 +10,7 @@ severity: wishlist
status: open
-summary: Add a verbose option?
+summary: Add a verbose option to "be html"?
time: Fri, 03 Jul 2009 21:17:41 +0000
diff --git a/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/body b/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/body
new file mode 100644
index 0000000..1090ace
--- /dev/null
+++ b/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/body
@@ -0,0 +1,2 @@
+Available with
+ be -d DIR html
diff --git a/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/values b/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/values
new file mode 100644
index 0000000..9ac4884
--- /dev/null
+++ b/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/comments/5b2e1ec8-3bb7-40cd-9f4f-74e5c59838f6/values
@@ -0,0 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 07 Aug 2009 17:58:58 +0000
+
diff --git a/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values b/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values
index a5777b9..ce4bc92 100644
--- a/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values
+++ b/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values
@@ -7,10 +7,10 @@ reporter: gianluca <gian@galactica>
severity: wishlist
-status: closed
+status: fixed
-summary: Add the possibility to specify the repository Directory ?
+summary: Add the possibility to specify the repository directory to "be html"?
time: Fri, 03 Jul 2009 21:18:13 +0000
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/body
new file mode 100644
index 0000000..63b61ad
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/body
@@ -0,0 +1,26 @@
+Hi,
+
+ > http://bitbucket.org/sjl/cherryflavoredbugseverywhere/
+
+Cool! I've set up a copy here:
+
+ http://bugsweb.bugseverywhere.org/
+
+(Since we don't have any open bugs lately, click "Closed" at the top,
+or create some, but don't expect them to persist if you do.)
+
+ > anyone has any feedback (on any aspect of it) I'd appreciate it.
+
+I'm pretty enthusiastic about merging this and then working on it
+further.
+
+Thanks,
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values
new file mode 100644
index 0000000..f303bf3
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values
@@ -0,0 +1,14 @@
+Alt-id: <m3eisxtgfx.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 03 Jul 2009 20:55:30 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: 2496ccca-130b-4459-bfae-9d9ef0138177
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/body
new file mode 100644
index 0000000..3a08d71
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/body
@@ -0,0 +1,35 @@
+Hi everyone. I found Bugs Everywhere and really like the idea of
+distributed bug tracking. I felt like practicing building a CherryPy
+site so I put together a quick web interface to BE. I know there's
+already a TurboGears one in the works, but I needed an excuse to try
+out CherryPy again after working with Django for a while.
+
+Would any of you be willing to take a look at what I've got so far and
+tell me what you think I could improve?
+
+To install and use it:
+
+* Install CherryPy from http://cherrypy.org/ if you don't have it.
+* Install Jinja2 from http://jinja.pocoo.org/2/ if you don't have it.
+* Install BugsEverywhere - you probably know how to do this :)
+* Download a zip/tar of my project (or hg clone) from http://bitbucket.org/sjl/cherryflavoredbugseverywhere/
+* Unzip my project and put the folder in your Python site-packages
+directory.
+* Symlink site-packages/cherryflavoredbugseverywhere/cfbe.py to /usr/
+local/bin/cfbe
+* Use the "cfbe [project_root]" command to start up the web interface
+for that project.
+* Visit http://localhost:8080/ in a browser.
+
+I know that's a lot of steps. I'd like to streamline it quite a bit,
+but first I wanted to see if you have any feedback on the system
+itself. Thanks!
+
+--
+Steve Losh
+http://stevelosh.com/
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values
new file mode 100644
index 0000000..2029281
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values
@@ -0,0 +1,11 @@
+Alt-id: <272FECFE-D16B-47B7-B39A-E2C8A718CCC5@stevelosh.com>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 07 Feb 2009 16:30:33 -0500
+
+
+From: Steve Losh <steve@stevelosh.com>
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/body
new file mode 100644
index 0000000..d2ef28c
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/body
@@ -0,0 +1,77 @@
+Those are beautiful templates -- can you share those? I'd love to
+study the HTML and CSS behind them.
+
+On Sat, Feb 7, 2009 at 5:48 PM, Steve Losh <steve@stevelosh.com> wrote:
+> Hey Chris, thanks for the comments.
+>
+>>
+>> My initial impression is that this looks good enough already to merge as
+>> a replacement for the turbogears site. What does everyone else think?
+>>
+>
+> I'm not quite sure it's there yet. There are a bunch of bugs I've got
+> marked as "beta" that I'd like to see fixed before it's ready for real use.
+> Hopefully they shouldn't be too tough to fix. You can point CFBE at itself
+> to see them. :)
+>
+>> Could you explain a little about how you handle authorship of bug
+>> changes at the moment, and if it looks plausible to try making it
+>> multiuser? (Having it handle more than one "user" logged in at once.)
+>>
+>
+> That's something I need advice on. Right now CFBE is pretty much only
+> suitable for local use - you check out whatever you're working on and use it
+> as a local interface to the bugs in the repository. Change those, check in,
+> etc. It's effectively just a pretty version of the command line be tool.
+>
+> I haven't used CherryPy's session/authentication support before. This might
+> be a good time for me to learn. One way it might be able to handle multiple
+> users hitting a central server:
+>
+> * Each user has to register with the server and be approved by an admin.
+> * Each account would be mapped to a contributor string, the same one that
+> would show up if you were going to commit to the repository.
+> * Once you have an account, you'd login to make any changes.
+>
+>
+> Aside from all that, I'm a little fuzzy on how a centralized interface to a
+> distributed bug tracking system should work. A read-only interface to a
+> central "main" repository would be easy. Run the server in read-only mode
+> pointing at the main repository. People can use it to look at the bugs in
+> the tip of that repository.
+>
+> If it's not read-only, what happens when a user changes/adds/whatevers a
+> bug? Should CFBE commit that change to the repository right then and there?
+> Should it never commit, just update the bugdir and let the commits happen
+> manually?
+>
+> What happens when you have multiple branches for a repository? Should there
+> be one CFBE instance for each branch, or a single one that lets you switch
+> between branches (effectively switching between revisions)?
+>
+> Those are the kind of things that don't really apply when CFBE is just a
+> local interface to a single repository. If anyone has any advice on how a
+> multi-user interface should work I'd love to hear it!
+>
+> --
+> Steve Losh
+> http://stevelosh.com/
+>
+>
+> _______________________________________________
+> Be-devel mailing list
+> Be-devel@bugseverywhere.org
+> http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
+>
+
+
+
+--
+Matthew Wilson
+matt@tplus1.com
+http://tplus1.com
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values
new file mode 100644
index 0000000..96612e6
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values
@@ -0,0 +1,14 @@
+Alt-id: <f6f643a20902071531y6aa3d7a6k7c5a4bd4aa5a04f6@mail.gmail.com>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 07 Feb 2009 18:31:04 -0500
+
+
+From: Matthew Wilson <matt@tplus1.com>
+
+
+In-reply-to: 21c90231-d7f2-49bb-97d9-99e16459d799
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/body
new file mode 100644
index 0000000..504f82b
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/body
@@ -0,0 +1,62 @@
+Hey Chris, thanks for the comments.
+
+>
+> My initial impression is that this looks good enough already to
+> merge as
+> a replacement for the turbogears site. What does everyone else think?
+>
+
+I'm not quite sure it's there yet. There are a bunch of bugs I've got
+marked as "beta" that I'd like to see fixed before it's ready for real
+use. Hopefully they shouldn't be too tough to fix. You can point
+CFBE at itself to see them. :)
+
+> Could you explain a little about how you handle authorship of bug
+> changes at the moment, and if it looks plausible to try making it
+> multiuser? (Having it handle more than one "user" logged in at once.)
+>
+
+That's something I need advice on. Right now CFBE is pretty much only
+suitable for local use - you check out whatever you're working on and
+use it as a local interface to the bugs in the repository. Change
+those, check in, etc. It's effectively just a pretty version of the
+command line be tool.
+
+I haven't used CherryPy's session/authentication support before. This
+might be a good time for me to learn. One way it might be able to
+handle multiple users hitting a central server:
+
+* Each user has to register with the server and be approved by an admin.
+* Each account would be mapped to a contributor string, the same one
+that would show up if you were going to commit to the repository.
+* Once you have an account, you'd login to make any changes.
+
+
+Aside from all that, I'm a little fuzzy on how a centralized interface
+to a distributed bug tracking system should work. A read-only
+interface to a central "main" repository would be easy. Run the
+server in read-only mode pointing at the main repository. People can
+use it to look at the bugs in the tip of that repository.
+
+If it's not read-only, what happens when a user changes/adds/whatevers
+a bug? Should CFBE commit that change to the repository right then
+and there? Should it never commit, just update the bugdir and let the
+commits happen manually?
+
+What happens when you have multiple branches for a repository? Should
+there be one CFBE instance for each branch, or a single one that lets
+you switch between branches (effectively switching between revisions)?
+
+Those are the kind of things that don't really apply when CFBE is just
+a local interface to a single repository. If anyone has any advice on
+how a multi-user interface should work I'd love to hear it!
+
+--
+Steve Losh
+http://stevelosh.com/
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values
new file mode 100644
index 0000000..7bdded2
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values
@@ -0,0 +1,14 @@
+Alt-id: <D765386C-4D43-4AE0-83E3-986A1CB4008C@stevelosh.com>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 07 Feb 2009 17:48:06 -0500
+
+
+From: Steve Losh <steve@stevelosh.com>
+
+
+In-reply-to: 42d57a41-219f-46db-9fda-21b42351da63
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/body
new file mode 100644
index 0000000..e160b76
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/body
@@ -0,0 +1,52 @@
+Speaking of that interface, I changed up the look and feel a bit last
+weekend. It's still at http://bitbucket.org/sjl/cherryflavoredbugseverywhere/
+ -- if anyone has any feedback (on any aspect of it) I'd appreciate it.
+
+--
+Steve
+
+On Jul 3, 2009, at 8:31 PM, Chris Ball wrote:
+
+> Hi Gianluca,
+>
+>> As i said in a previous mail, I am working on a "html" command
+>> for be. The goal is to be able to do something like "be html
+>> /web/page" to have in the /web/page directory some static html
+>> pages that basically are the dump of the be repository, much like
+>> ditz have. This will enable a simple and fast publish of the bus
+>> list and details on the web, at least in read only mode.
+>
+> It might be a good idea for "be html" to use the CherryPy web
+> interface
+> that Steve is working on. The command could start up the CherryPy app
+> and scrape all of the available pages to get a stand-alone dump; this
+> would avoid having to keep two (okay, more than two at this point)
+> separate sets of HTML templates in the source tree. What do you
+> think?
+>
+>> 2) I see that every command is implemented with a python file in
+>> the becommand dir. For a better code, I'd like to split the
+>> command implementation into two files: a file that contain the
+>> actual code and a second file that have the html related part,
+>> any problem with this ? I don't like to have the html part and
+>> the code part in one big and unreadable file.
+>
+> I agree that becommands/*.py commands should not contain any HTML
+> layout code. Putting it somewhere else instead sounds fine.
+>
+> Thanks!
+>
+> - Chris.
+> --
+> Chris Ball <cjb@laptop.org>
+>
+> _______________________________________________
+> Be-devel mailing list
+> Be-devel@bugseverywhere.org
+> http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
+
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values
new file mode 100644
index 0000000..0c68560
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values
@@ -0,0 +1,14 @@
+Alt-id: <4701D71B-A14D-4C63-ABCC-E7E5FFE4E4BA@stevelosh.com>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 03 Jul 2009 20:34:51 -0400
+
+
+From: Steve Losh <steve@stevelosh.com>
+
+
+In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/body
new file mode 100644
index 0000000..f43e8dd
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/body
@@ -0,0 +1,32 @@
+Hi Steve,
+
+ > Hi everyone. I found Bugs Everywhere and really like the idea of
+ > distributed bug tracking. I felt like practicing building a
+ > CherryPy site so I put together a quick web interface to BE. I
+ > know there's already a TurboGears one in the works, but I needed an
+ > excuse to try out CherryPy again after working with Django for a
+ > while.
+
+This looks awesome, thanks! I've taken some screenshots for others to
+see:
+
+http://chris.printf.net/cfbe-main.png
+http://chris.printf.net/cfbe-detail.png
+
+My initial impression is that this looks good enough already to merge as
+a replacement for the turbogears site. What does everyone else think?
+
+Could you explain a little about how you handle authorship of bug
+changes at the moment, and if it looks plausible to try making it
+multiuser? (Having it handle more than one "user" logged in at once.)
+
+Great work, thanks!
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
+
+_______________________________________________
+Be-devel mailing list
+Be-devel@bugseverywhere.org
+http://void.printf.net/cgi-bin/mailman/listinfo/be-devel
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values
new file mode 100644
index 0000000..7c7e41a
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values
@@ -0,0 +1,14 @@
+Alt-id: <m3zlgxyjo5.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 07 Feb 2009 17:19:22 -0500
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/body
new file mode 100644
index 0000000..7bea88c
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/body
@@ -0,0 +1,30 @@
+Hi,
+
+ > Works for me. I've done this now, which closes the last open bug
+ > in my repo :D.
+
+Wow. Congrats! I've merged your branch.
+
+ > All the new functionality comes from bug.extra_strings, which
+ > provides a list for storing arbitrary strings in the bug's
+ > permanent state.
+
+That's going to be really useful.
+
+ > Next up: regexp searching for list --extra-strings! ;).
+
+Awesome.
+
+ > Oh, and obviously there must still be bugs in BE. Please submit
+ > more ;).
+
+Perhaps it's a good time to merge Steve Losh's CherryPy web interface?
+
+http://void.printf.net/pipermail/be-devel/2009-February/000095.html
+http://bitbucket.org/sjl/cherryflavoredbugseverywhere/
+
+Thanks,
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values
new file mode 100644
index 0000000..9b607ef
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values
@@ -0,0 +1,14 @@
+Alt-id: <m31vp82yyj.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 25 Jun 2009 10:02:44 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/body
new file mode 100644
index 0000000..909b989
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/body
@@ -0,0 +1,25 @@
+On Jun 25, 2009, at 10:02 AM, Chris Ball <cjb@laptop.org> wrote:
+>
+>> Oh, and obviously there must still be bugs in BE. Please submit
+>> more ;).
+>
+> Perhaps it's a good time to merge Steve Losh's CherryPy web interface?
+>
+> http://void.printf.net/pipermail/be-devel/2009-February/000095.html
+> http://bitbucket.org/sjl/cherryflavoredbugseverywhere/
+>
+
+Hey, I haven't touched the web interface in a while, but I should have
+some time to fix some stuff up tonight and tomorrow. Hold off on
+merging it in until then.
+
+I'm still curious as to what people think the role of a web interface
+like this should be. When I wrote it I meant it as a single-user
+interface like the command line one. It could definitely work as a
+public, read-only interface too.
+
+If the goal is to allow more than one person to add issues, how should
+commits go? One commit per change? Commit every X minutes if necessary?
+
+--
+Steve
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values
new file mode 100644
index 0000000..72597bc
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values
@@ -0,0 +1,14 @@
+Alt-id: <26FBD153-39C5-4641-AF5E-749731964086@stevelosh.com>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 25 Jun 2009 10:23:04 -0400
+
+
+From: Steve Losh <steve@stevelosh.com>
+
+
+In-reply-to: 5e339bac-f4f3-407b-974a-b88795d3573b
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/body
new file mode 100644
index 0000000..614abd3
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/body
@@ -0,0 +1,33 @@
+On Sat, Feb 07, 2009 at 05:48:06PM -0500, Steve Losh wrote:
+>> My initial impression is that this looks good enough already to merge as
+>> a replacement for the turbogears site. What does everyone else think?
+>
+> I'm not quite sure it's there yet. There are a bunch of bugs I've
+> got marked as "beta" that I'd like to see fixed before it's ready
+> for real use. Hopefully they shouldn't be too tough to fix. You
+> can point CFBE at itself to see them. :)
+
+Steve's also versioning it with Mercurial. Will he mind changing to
+Bazaar?
+
+Steve, I've touched up CFBE to work with my current BE branch. Some
+of the changes apply to the trunk BE, and hopefully the rest will
+soon. I think the version-naming issue is what's currently blocking
+their adoption ;). I've put up my CFBE branch at
+ static-http://www.physics.drexel.edu/~wking/code/hg/cfbe
+for Mercurial.
+
+Everyone, should CFBE-specific discussions move off-list? More
+generally, I've been sending in lots of what-I'm-working on messages,
+but not hearing much back, so let me know if I'm being too obnoxious,
+and I'll shut up (or at least move more off-list) ;).
+
+Cheers,
+Trevor
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values
new file mode 100644
index 0000000..37e1899
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values
@@ -0,0 +1,14 @@
+Alt-id: <20090721135907.GB4469@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 09:59:07 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 21c90231-d7f2-49bb-97d9-99e16459d799
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/body b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/body
new file mode 100644
index 0000000..ae6a5fe
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/body
@@ -0,0 +1,69 @@
+On Thu, Jun 25, 2009 at 10:23:04AM -0400, Steve Losh wrote:
+> I'm still curious as to what people think the role of a web interface like
+> this should be. When I wrote it I meant it as a single-user interface like
+> the command line one. It could definitely work as a public, read-only
+> interface too.
+
+I think the multi-user/public is the way to go. I'd also like to see
+a procmail-able script to handle multi-user/public access via email,
+which would have all the same issues we're worrying about here.
+
+On Sat, Feb 07, 2009 at 05:48:06PM -0500, Steve Losh wrote:
+> I haven't used CherryPy's session/authentication support before. This
+> might be a good time for me to learn. One way it might be able to handle
+> multiple users hitting a central server:
+>
+> * Each user has to register with the server and be approved by an admin.
+> * Each account would be mapped to a contributor string, the same one that
+> would show up if you were going to commit to the repository.
+> * Once you have an account, you'd login to make any changes.
+
+This sounds good to me. Yay spam reduction ;).
+
+> If it's not read-only, what happens when a user changes/adds/whatevers a
+> bug? Should CFBE commit that change to the repository right then and
+> there? Should it never commit, just update the bugdir and let the commits
+> happen manually?
+
+On Thu, Jun 25, 2009 at 10:23:04AM -0400, Steve Losh wrote:
+> One commit per change? Commit every X minutes if necessary?
+
+On Sat, Feb 07, 2009 at 05:48:06PM -0500, Steve Losh wrote:
+> What happens when you have multiple branches for a repository? Should
+> there be one CFBE instance for each branch, or a single one that lets you
+> switch between branches (effectively switching between revisions)?
+
+There are interesting discussions about the role of distributed
+bugtracking here (I'm sure there are others):
+ http://lwn.net/Articles/281849/
+ http://community.livejournal.com/evan_tech/248736.html
+
+Personally, I've never done much cherry-picking or anything that would
+require me to determine exactly which parts of someone's work I want
+and which I don't want. I just merge someone's head and edit out the
+bits I don't like, a process that also works well for our current
+"commit however you want" BE development model ;). Maybe that just
+shows that I only work on minor branches of small projects :p. In the
+absence of big-project advice, I think we just limit the web front end
+to our current model, and let the web interface commit however it
+wants as well ;). +1 for adding a <commit> button to the web
+interface ;).
+
+One caveat about a multi-user interface would be that it would allow
+the casual users to commit bugs about whatever version they had
+installed onto the head of the public-bug branch. In order to figure
+out what version of the project they were talking about, the project
+should have a way for the user to get a unique version string, ideally
+be the bzr-revision-id/git-commit/etc. of the commit for the version
+they were using. This would allow developers to determine what branch
+to work on with the bug fix, and what branches needed to pull the
+eventual fix. If the initially reported buggy version wasn't actually
+the root of the bug, oh well :p. Material for a later related bug
+report or a reopen.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values
new file mode 100644
index 0000000..2fa8470
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values
@@ -0,0 +1,14 @@
+Alt-id: <20090625154734.GA19441@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 25 Jun 2009 11:47:34 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
+
diff --git a/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/values b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/values
new file mode 100644
index 0000000..8cf85c9
--- /dev/null
+++ b/.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/values
@@ -0,0 +1,20 @@
+assigned: Steve Losh <steve@stevelosh.com>
+
+
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: Steve Losh <steve@stevelosh.com>
+
+
+severity: wishlist
+
+
+status: assigned
+
+
+summary: CherryPy interface "Cherry-flavored BE"
+
+
+time: Tue, 21 Jul 2009 18:52:44 +0000
+
diff --git a/.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values b/.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values
index 4d784ad..2832bb3 100644
--- a/.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values
+++ b/.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values
@@ -10,7 +10,7 @@ severity: wishlist
status: open
-summary: Add an icon near the status string
+summary: Add an icon near the status string in "be html" output
time: Tue, 04 Aug 2009 21:15:52 +0000
diff --git a/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/body b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/body
new file mode 100644
index 0000000..d29c749
--- /dev/null
+++ b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/body
@@ -0,0 +1,43 @@
+BE should not crash when be list|show is used on a git repository that
+have not the config variables user.name and user.email defined in the
+.git/config file.
+
+To view the bug, in my opinion shold not be mandatory to have these two options
+defined
+
+
+Traceroute:
+
+galactica:~/Devel/dumb> be show 996
+Traceback (most recent call last):
+ File "/usr/bin/be", line 62, in <module>
+ sys.exit(cmdutil.execute(args[0], args[1:]))
+ File "/usr/lib/python2.5/site-packages/libbe/cmdutil.py", line 76, in execute
+ ret = cmd.execute([a.decode(enc) for a in args])
+ File "/usr/lib/python2.5/site-packages/becommands/show.py", line 60, in execute
+ bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 302, in __init__
+ self.load()
+ File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 382, in load
+ self.load_settings()
+ File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 411, in load_settings
+ self._setup_user_id(self.user_id)
+ File "/usr/lib/python2.5/site-packages/libbe/properties.py", line 293, in _fget
+ value = generator(self)
+ File "/usr/lib/python2.5/site-packages/libbe/bugdir.py", line 177, in _guess_user_id
+ return self.rcs.get_user_id()
+ File "/usr/lib/python2.5/site-packages/libbe/rcs.py", line 258, in get_user_id
+ id = self._rcs_get_user_id()
+ File "/usr/lib/python2.5/site-packages/libbe/git.py", line 56, in _rcs_get_user_id
+ status,output,error = self._u_invoke_client("config", "user.name")
+ File "/usr/lib/python2.5/site-packages/libbe/rcs.py", line 458, in _u_invoke_client
+ return self._u_invoke(cl_args, stdin=stdin,expect=expect,cwd=directory)
+ File "/usr/lib/python2.5/site-packages/libbe/rcs.py", line 450, in _u_invoke
+ raise CommandError(args, status, error)
+libbe.rcs.CommandError: Command failed (1):
+
+
+while executing
+ ['git', 'config', 'user.name']
+galactica:~/Devel/dumb>
+
diff --git a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/b1a772a0-241f-42fc-8209-765162485b0a/values b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values
index b6abe1f..d6d1b61 100644
--- a/.be/bugs/4ddf1313-bb3c-45d3-8dca-79ed5830d606/comments/b1a772a0-241f-42fc-8209-765162485b0a/values
+++ b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values
@@ -1,7 +1,7 @@
Content-type: text/plain
-Date: Wed, 22 Jul 2009 21:48:23 +0000
+Date: Mon, 03 Aug 2009 20:33:30 +0000
From: Gianluca Montecchi <gian@grys.it>
diff --git a/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/values b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/values
new file mode 100644
index 0000000..375e44d
--- /dev/null
+++ b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/values
@@ -0,0 +1,17 @@
+creator: Gianluca Montecchi <gian@grys.it>
+
+
+reporter: Gianluca Montecchi <gian@grys.it>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: BE should not crash if user.email and user.name are not defined
+
+
+time: Mon, 03 Aug 2009 20:30:43 +0000
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/body b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/body
new file mode 100644
index 0000000..770af86
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/body
@@ -0,0 +1,19 @@
+On Thu, Jul 16, 2009 at 09:39:30AM -0400, W. Trevor King wrote:
+> Disclaimer: I imaging the current implementation will choke on
+> non-text/plain content types. Also possibly on non-ascii encodings.
+
+Non-ascii encodings are now handled (although now the output is
+base64-encoded). This is limited by poor unicode handling in the
+email module for current pythons, see the log for more details.
+
+> I should probably allow the "help" command ... ;).
+
+Added, but it currently shows _all_ commands, not just allowed
+commands.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values
new file mode 100644
index 0000000..d8edf61
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values
@@ -0,0 +1,14 @@
+Alt-id: <20090718131220.GA31832@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 18 Jul 2009 09:12:20 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/body b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/body
new file mode 100644
index 0000000..e008923
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/body
@@ -0,0 +1,27 @@
+On Sat, Jul 18, 2009 at 06:05:51PM -0400, W. Trevor King wrote:
+> My email interface now supports bug creation/comments that look
+> like:
+>
+> $ cat | mail -s "[be-bug] new" "whatever-dev@fancyprojects.com"
+> The demuxulizer is broken
+>
+> <describe bug>
+> ^D
+
+The move towards the DBT interface means this example should now look
+like
+
+ $ cat | mail -s "[be-bug:submit] The demuxulizer is broken" whatever-dev@fancyprojects.com
+ Version: XYZ
+
+ <describe bug>
+ --
+ Ignored text
+ ^D
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values
new file mode 100644
index 0000000..c00299a
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values
@@ -0,0 +1,14 @@
+Alt-id: <20090719130649.GA4164@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 19 Jul 2009 09:06:49 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 7b904395-86e9-4eb1-8534-69cec63801d4
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/body b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/body
new file mode 100644
index 0000000..800609e
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/body
@@ -0,0 +1,27 @@
+Ah, it's been a good day :). My email interface now supports bug
+creation/comments that look like:
+
+ $ cat | mail -s "[be-bug] new" "whatever-dev@fancyprojects.com"
+ The demuxulizer is broken
+
+ <describe bug>
+ ^D
+
+Which is probably easy enough for just about anybody ;). Also easy
+for other projects to wrap into one of their tools:
+
+ $ cat | projectAexecutable --report-bug
+ The demuxulizer is broken
+
+ <describe bug>
+ ^D
+
+Which could do things like automatically add the version string, OS
+name, etc.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values
new file mode 100644
index 0000000..ab160fb
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values
@@ -0,0 +1,14 @@
+Alt-id: <20090718220551.GB32230@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 18 Jul 2009 18:05:51 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: 09f950d4-9366-4e7b-98b3-9057999f8f38
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/body b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/body
new file mode 100644
index 0000000..087d67a
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/body
@@ -0,0 +1,58 @@
+On Sun, Jul 19, 2009 at 09:09:05AM +1000, Ben Finney wrote:
+> > The interface is basically "place your be command in the subject line"
+>
+> I would far prefer an interface of “place as many BE commands as you
+> like at the top of the message body, ending with an optional terminator
+> command, and they will each be executed in turn”.
+> ...
+
+I think the idea behind my first approach was "you guys have
+experience with the command line BE interface, so it will be easier to
+test if you don't have to learn the DBT interface." The Debian people
+have been doing this for a while though, so I imagine their email
+interface is pretty good ;).
+
+Here's a short primer on my take on the DBT interface.
+
+The DBT has three main email addresses, each with it's own parsing style.
+ 1) creating bugs (submit@bugs.debian.org)
+ 2) commenting on bugs (<bug-number>@bugs.debian.org)
+ 3) controlling/managing bugs (control@bugs.debian.org)
+I'm trying to squeeze these down into a single email address to avoid
+having to tinker with the mail delivery system, so I've got everything
+at (wking at tremily dot us) with subject tags:
+ 1) creating bugs
+ Subject: [be-bug:submit] new bug summary ...
+ 2) commenting on bugs
+ Subject: [be-bug:<bug-number>] human-specific subject
+ 3) control
+ Subject: [be-bug] human-specific subject
+
+The parsing styles each follow their DBT counterparts, but currently
+have a much restricted breadth.
+
+The control-style consists of a list of allowed be commands, with one
+command per line. Blank lines and lines beginning with '#' are
+ignored, as well anything following a line starting with '--'. All the
+listed commands are executed in order and their output returned.
+
+The comment-style interface appends a comment to the bug specified in
+the subject tag. The the first non-multipart body is attached with
+the appropriate content-type. In the case of "text/plain" contents,
+anything following a line starting with '--' is stripped.
+
+The create-style interface creates a bug whose summary is given by the
+email's post-tag subject. The body of the email must begin with a
+psuedo-header containing at least the "Version" field. Anything after
+the pseudo-header and before a line starting with '--' is, if present,
+attached as the bugs first comment.
+
+Take a look at my interfaces/email/interactive/examples for some
+examples.
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values
new file mode 100644
index 0000000..30de513
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values
@@ -0,0 +1,14 @@
+Alt-id: <20090719130153.GA4036@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 19 Jul 2009 09:01:53 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
+
+In-reply-to: cfd7cbc7-27ad-4618-8530-cb4d7323514a
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/body b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/body
new file mode 100644
index 0000000..3db2a91
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/body
@@ -0,0 +1,26 @@
+"W. Trevor King" <wking@drexel.edu> writes:
+
+> The interface is basically "place your be command in the subject line"
+
+I would far prefer an interface of “place as many BE commands as you
+like at the top of the message body, ending with an optional terminator
+command, and they will each be executed in turn”.
+
+This would allow a single message to perform a batch of BE commands that
+are related, instead of requiring to send each command in a separate
+message.
+
+It would also leave the subject field free for something more
+descriptive. The subject field could also be used as the summary field
+of newly-created bug reports. With a terminator command, this would
+allow the message to be sent both to BE and to some other recipient
+(e.g. a mailing list) explaining the change.
+
+Have a look at the email interface of the Debian BTS for an example
+<URL:http://www.debian.org/Bugs/server-request>.
+
+--
+ \ “Pinky, are you pondering what I'm pondering?” “Wuh, I think |
+ `\ so, Brain, but will they let the Cranberry Duchess stay in the |
+_o__) Lincoln Bedroom?” —_Pinky and The Brain_ |
+Ben Finney
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values
new file mode 100644
index 0000000..b98fbf7
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values
@@ -0,0 +1,14 @@
+Alt-id: <87fxctbnce.fsf@benfinney.id.au>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 19 Jul 2009 09:09:05 +1000
+
+
+From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+
+
+In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/body b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/body
new file mode 100644
index 0000000..37b9936
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/body
@@ -0,0 +1,38 @@
+I finally did something towards a useful interactive email interface
+;). As per our new guidelines, I'll develop this feature in it's own
+branch:
+ http://www.physics.drexel.edu/~wking/code/bzr/be-email
+
+The interface is basically "place your be command in the subject line"
+with a few exceptions. Some examples:
+ Subject: [be-bug] list --status=all
+ Subject: [be-bug] show --xml ID
+ Subject: [be-bug] new
+ Subject: [be-bug] comment ID
+In the case of "new", the bug description is extracted from the first
+non-blank body line. In the case of "comment", the email body is used
+as the comment. Currently only "list", "show", "new", and "comment"
+are allowed.
+
+You should get a reply email with exit status, stdout, and stderr from
+your command.
+
+Send some mail to [wking (at) tremily (dot) us] to try it out! Depending
+on spam attraction, this might be a limited time offer ;).
+
+Hopefully this lowers the entry barrier for bug reporting :).
+
+Disclaimer: I imaging the current implementation will choke on
+non-text/plain content types. Also possibly on non-ascii encodings.
+Probably lots of other bugs too... ;). For example, I should probably
+allow the "help" command ... ;).
+
+Cheers,
+Trevor
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values
new file mode 100644
index 0000000..3f81305
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values
@@ -0,0 +1,11 @@
+Alt-id: <20090716133930.GC12213@mjolnir.home.net>
+
+
+Content-type: text/plain
+
+
+Date: Thu, 16 Jul 2009 09:39:30 -0400
+
+
+From: '"W. Trevor King" <wking@drexel.edu>'
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/body b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/body
new file mode 100644
index 0000000..167cfe5
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/body
@@ -0,0 +1,10 @@
+Hi Trevor,
+
+ > I finally did something towards a useful interactive email
+ > interface ;).
+
+Wow, nice! That'll be really useful.
+
+- Chris.
+--
+Chris Ball <cjb@laptop.org>
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values
new file mode 100644
index 0000000..f5da0c9
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values
@@ -0,0 +1,14 @@
+Alt-id: <m3fxct5vl6.fsf@pullcord.laptop.org>
+
+
+Content-type: text/plain
+
+
+Date: Sat, 18 Jul 2009 21:07:33 -0400
+
+
+From: Chris Ball <cjb@laptop.org>
+
+
+In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49
+
diff --git a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values
new file mode 100644
index 0000000..da43639
--- /dev/null
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values
@@ -0,0 +1,20 @@
+assigned: W. Trevor King <wking@drexel.edu>
+
+
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: wishlist
+
+
+status: fixed
+
+
+summary: Interactive email interface
+
+
+time: Tue, 21 Jul 2009 18:53:50 +0000
+
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body
new file mode 100644
index 0000000..0598d70
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body
@@ -0,0 +1,8 @@
+<type 'unicode'> <body>�</body>
+Traceback (most recent call last):
+ File "<string>", line 1, in <module>
+ File "/usr/lib/python2.5/xml/etree/ElementTree.py", line 963, in XML
+ parser.feed(text)
+ File "/usr/lib/python2.5/xml/etree/ElementTree.py", line 1245, in feed
+ self._parser.Parse(data, 0)
+UnicodeEncodeError: 'ascii' codec can't encode character u'\u1234' in position 6: ordinal not in range(128)
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values
new file mode 100644
index 0000000..cd8d8b9
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 11:34:22 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132
+
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body
new file mode 100644
index 0000000..397d4b6
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body
@@ -0,0 +1 @@
+It looks like etree wants a byte string, not unicode input
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values
new file mode 100644
index 0000000..8bdaf52
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 11:42:16 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132
+
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body
new file mode 100644
index 0000000..ce2bb8d
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body
@@ -0,0 +1,5 @@
+For example, this works:
+
+python -c 'from xml.etree import ElementTree; a=u"<body>\u1234</body>"; print type(a), a; b=ElementTree.XML(a.encode("unicode_escape")); print type(b.text), unicode(b.text).decode("unicode_escape");'
+
+Ugly though :p. Ah well.
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values
new file mode 100644
index 0000000..1784e0e
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 11:46:57 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 520a9829-8d90-43ce-be64-868b8321e5b0
+
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body
new file mode 100644
index 0000000..89a8f8d
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body
@@ -0,0 +1 @@
+That's with Python 2.5.2 and ElementTree "2326 2005-03-17 07:45:21Z fredrik"
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values
new file mode 100644
index 0000000..cca07c3
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 11:37:55 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 07fc448f-c42e-4846-929a-8924de485766
+
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body
new file mode 100644
index 0000000..57e050d
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body
@@ -0,0 +1,5 @@
+Isolated problem to:
+
+python -c 'from xml.etree import ElementTree; a=u"<body>\u1234</body>"; print type(a), a; b=ElementTree.XML(a);'
+
+Output attached below
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values
new file mode 100644
index 0000000..e430ea0
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values
@@ -0,0 +1,8 @@
+Content-type: text/plain
+
+
+Date: Sun, 12 Jul 2009 11:31:13 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values
new file mode 100644
index 0000000..4bc81f5
--- /dev/null
+++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: utf8 problems in xml parsing
+
+
+time: Sat, 11 Jul 2009 15:48:32 +0000
+
diff --git a/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values b/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values
index df99653..a82beb8 100644
--- a/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values
+++ b/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values
@@ -4,7 +4,7 @@ creator: abentley
severity: minor
-status: closed
+status: fixed
summary: no tests for missing $EDITOR
diff --git a/.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values b/.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values
index 5a7261b..72c2839 100644
--- a/.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values
+++ b/.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values
@@ -10,7 +10,7 @@ severity: minor
status: fixed
-summary: Comment should be threaded in the html output
+summary: Comment should be threaded in the "be html" output
time: Tue, 21 Jul 2009 21:39:52 +0000
diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values
index eb56317..d39c4a1 100644
--- a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values
+++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values
@@ -1,21 +1,8 @@
+Content-type: text/plain
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Tue, 25 Nov 2008 19:41:02 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Date: Tue, 25 Nov 2008 19:41:02 +0000
+From: W. Trevor King <wking@drexel.edu>
diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values
index f976972..639fd4a 100644
--- a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values
+++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values
@@ -1,21 +1,8 @@
+Content-type: text/plain
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Tue, 25 Nov 2008 02:36:16 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Date: Tue, 25 Nov 2008 02:36:16 +0000
+From: W. Trevor King <wking@drexel.edu>
diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values
index bf5085b..2821b2f 100644
--- a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values
+++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values
@@ -1,21 +1,8 @@
+Content-type: text/plain
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Tue, 25 Nov 2008 03:02:59 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Date: Tue, 25 Nov 2008 03:02:59 +0000
+From: W. Trevor King <wking@drexel.edu>
diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body
new file mode 100644
index 0000000..b441da9
--- /dev/null
+++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body
@@ -0,0 +1 @@
+Test unicode �quotes�
diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values
new file mode 100644
index 0000000..a67680d
--- /dev/null
+++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values
@@ -0,0 +1,8 @@
+Content-type: text/plain
+
+
+Date: Sat, 11 Jul 2009 18:28:57 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
diff --git a/.be/settings b/.be/settings
index a9bd6dd..15c4553 100644
--- a/.be/settings
+++ b/.be/settings
@@ -1,3 +1,7 @@
+extra_strings:
+- "SUBSCRIBE:W. Trevor King <wking@drexel.edu>\tall\t*"
+
+
inactive_status:
- - closed
- The bug is no longer relevant.
diff --git a/AUTHORS b/AUTHORS
index 4910d6c..6b66315 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,6 +1,7 @@
Bugs Everywhere was written by:
Aaron Bentley
Alexander Belchenko
+Alex Miller
Ben Finney
Chris Ball
Gianluca Montecchi
diff --git a/Bugs-Everywhere-Web/libbe b/Bugs-Everywhere-Web/libbe
deleted file mode 120000
index 4e5e17e..0000000
--- a/Bugs-Everywhere-Web/libbe
+++ /dev/null
@@ -1 +0,0 @@
-../libbe \ No newline at end of file
diff --git a/COPYING b/COPYING
index e50e067..d511905 100644
--- a/COPYING
+++ b/COPYING
@@ -1,9 +1,8 @@
-
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -16,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
+the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -56,7 +55,7 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
-
+
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@@ -111,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -169,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -226,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -279,7 +278,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
-
+
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
@@ -304,17 +303,16 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
- Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -337,5 +335,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
+library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
diff --git a/Makefile b/Makefile
index 034f033..fe482c3 100644
--- a/Makefile
+++ b/Makefile
@@ -7,11 +7,20 @@
# Copyright (C) 2008-2009 Ben Finney <benf@cybersource.com.au>
# Chris Ball <cjb@laptop.org>
# W. Trevor King <wking@drexel.edu>
-# This is free software; you may copy, modify and/or distribute this work
-# under the terms of the GNU General Public License, version 2 or later.
-# No warranty expressed or implied. See the file COPYING for details.
-
-# Makefile for Bugs Everywhere project
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
SHELL = /bin/bash
PATH = /usr/bin:/bin
@@ -29,6 +38,7 @@ MODULES += ${DOC_DIR}
RM = rm
+#PREFIX = /usr/local
PREFIX = ${HOME}
INSTALL_OPTIONS = "--prefix=${PREFIX}"
@@ -47,12 +57,14 @@ build: libbe/_version.py
.PHONY: install
install: doc build
python setup.py install ${INSTALL_OPTIONS}
- cp -v xml/* ${PREFIX}/bin
+#cp -v interfaces/xml/* ${PREFIX}/bin
+#cp -v interfaces/email/catmutt ${PREFIX}/bin
.PHONY: clean
clean:
$(RM) -rf ${GENERATED_FILES}
+.PHONY: libbe/_version.py
libbe/_version.py:
bzr version-info --format python > $@
diff --git a/README b/README
index 9562a75..b43c15c 100644
--- a/README
+++ b/README
@@ -1,12 +1,14 @@
Bugs Everywhere
===============
-This is Bugs Everywhere, a bugtracker built on distributed revision control.
-It works with Bazaar and Arch at the moment, but is easily extensible. It
-can also function with no RCS at all.
+This is Bugs Everywhere, a bugtracker built on distributed revision
+control. It works with Arch, Bazaar, Darcs, Git, and Mercurial at the
+moment, but is easily extensible. It can also function with no RCS at
+all.
The idea is to package the bug information with the source code, so that
bugs can be marked 'fixed' in the branches that fix them. So, instead of
-numbers, bugs have ids.
+numbers, bugs have globally unique ids.
+
Getting started
===============
@@ -15,9 +17,11 @@ set the bug root to your project root, so that Bugs Everywhere works in any
part of your project tree.
$ be init $PROJECT_ROOT
-To create bugs, use "be new $DESCRIPTION". To comment on bugs, you can can use
-"be comment $BUG_ID". To close a bug, use "be close $BUG_ID". For more
-commands, see "be help". You can also look at the usage in test_usage.sh.
+To create bugs, use "be new $DESCRIPTION". To comment on bugs, you
+can can use "be comment $BUG_ID". To close a bug, use "be close
+$BUG_ID" or "be status $BUG_ID fixed". For more commands, see "be
+help". You can also look at the usage examples in test_usage.sh.
+
Using BeWeb, the web UI
=======================
diff --git a/README.dev b/README.dev
index 644d965..ddc3a88 100644
--- a/README.dev
+++ b/README.dev
@@ -31,6 +31,7 @@ consistent interface
Again, you can just browse around in becommands to get a feel for things.
+
Testing
-------
@@ -67,3 +68,12 @@ later. In recognition of this, cmdutil provides the default_complete
function which ensures that if '--complete' is any one of the
arguments, options, or option-arguments, GetCompletions will be raised
with and empty list.
+
+Profiling
+=========
+
+Find out which 20 calls take the most cumulative time (time of
+execution + childrens' times).
+
+ $ python -m cProfile -o profile be [command] [args]
+ $ python -c "import pstats; p=pstats.Stats('profile'); p.sort_stats('cumulative').print_stats(20)"
diff --git a/be b/be
index 2023daa..36deaba 100755
--- a/be
+++ b/be
@@ -3,54 +3,81 @@
# Chris Ball <cjb@laptop.org>
# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import os
import sys
+
from libbe import cmdutil, _version
-__doc__ == cmdutil.help()
+__doc__ = cmdutil.help()
+
+usage = "be [options] [command] [command_options ...] [command_args ...]"
-if len(sys.argv) == 1 or sys.argv[1] in ('--help', '-h'):
- print cmdutil.help()
-elif sys.argv[1] == '--complete':
- for command, module in cmdutil.iter_commands():
- print command
- print '\n'.join(["--help","--complete","--options","--version"])
-elif sys.argv[1] == '--version':
+parser = cmdutil.CmdOptionParser(usage)
+parser.command = "be"
+parser.add_option("--version", action="store_true", dest="version",
+ help="Print version string and exit.")
+parser.add_option("-d", "--dir", dest="dir", metavar="DIR",
+ help="Run this command from DIR instead of the current directory.")
+
+try:
+ options,args = parser.parse_args()
+ for option,value in cmdutil.option_value_pairs(options, parser):
+ if value == "--complete":
+ if option == "dir":
+ if len(args) == 0:
+ args = ["."]
+ paths = cmdutil.complete_path(args[0])
+ raise cmdutil.GetCompletions(paths)
+except cmdutil.GetHelp:
+ print cmdutil.help(parser=parser)
+ sys.exit(0)
+except cmdutil.GetCompletions, e:
+ print '\n'.join(e.completions)
+ sys.exit(0)
+
+if options.version == True:
print _version.version_info["revision_id"]
-else:
- try:
- try:
- sys.exit(cmdutil.execute(sys.argv[1], sys.argv[2:]))
- except KeyError, e:
- raise cmdutil.UserError("Unknown command \"%s\"" % e.args[0])
- except cmdutil.GetHelp:
- print cmdutil.help(sys.argv[1])
- sys.exit(0)
- except cmdutil.GetCompletions, e:
- print '\n'.join(e.completions)
- sys.exit(0)
- except cmdutil.UsageError, e:
- print "Invalid usage:", e
- print "\nArgs:", sys.argv[1:]
- print cmdutil.help(sys.argv[1])
- sys.exit(1)
- except cmdutil.UserError, e:
- print "ERROR:"
- print e
- sys.exit(1)
+ sys.exit(0)
+if options.dir != None:
+ os.chdir(options.dir)
+
+try:
+ if len(args) == 0:
+ raise cmdutil.UsageError, "must supply a command"
+ sys.exit(cmdutil.execute(args[0], args[1:]))
+except cmdutil.GetHelp:
+ print cmdutil.help(sys.argv[1])
+ sys.exit(0)
+except cmdutil.GetCompletions, e:
+ print '\n'.join(e.completions)
+ sys.exit(0)
+except cmdutil.UnknownCommand, e:
+ print e
+ sys.exit(1)
+except cmdutil.UsageError, e:
+ print "Invalid usage:", e
+ if len(args) == 0:
+ print cmdutil.help(parser=parser)
+ else:
+ print "\nArgs:", args
+ print cmdutil.help(sys.argv[1])
+ sys.exit(1)
+except cmdutil.UserError, e:
+ print "ERROR:"
+ print e
+ sys.exit(1)
diff --git a/becommands/assign.py b/becommands/assign.py
index 985cfdd..794f028 100644
--- a/becommands/assign.py
+++ b/becommands/assign.py
@@ -2,47 +2,47 @@
# Marien Zwart <marienz@gentoo.org>
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Assign an individual or group to fix a bug"""
-from libbe import cmdutil, bugdir, settings_object
+from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> bd.bug_from_shortname("a").assigned is settings_object.EMPTY
+ >>> bd.bug_from_shortname("a").assigned is None
True
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> bd.bug_from_shortname("a").assigned == bd.user_id
True
- >>> execute(["a", "someone"], test=True)
+ >>> execute(["a", "someone"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> print bd.bug_from_shortname("a").assigned
someone
- >>> execute(["a","none"], test=True)
+ >>> execute(["a","none"], manipulate_encodings=False)
>>> bd._clear_bugs()
- >>> bd.bug_from_shortname("a").assigned is settings_object.EMPTY
+ >>> bd.bug_from_shortname("a").assigned is None
True
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -54,7 +54,9 @@ def execute(args, test=False):
if len(args) > 2:
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
bug = bd.bug_from_shortname(args[0])
if len(args) == 1:
bug.assigned = bd.user_id
diff --git a/becommands/close.py b/becommands/close.py
index deaccce..0532ed2 100644
--- a/becommands/close.py
+++ b/becommands/close.py
@@ -2,37 +2,37 @@
# Marien Zwart <marienz@gentoo.org>
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Close a bug"""
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import bugdir
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("a").status
open
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> print bd.bug_from_shortname("a").status
closed
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -42,8 +42,9 @@ def execute(args, test=False):
raise cmdutil.UsageError("Please specify a bug id.")
if len(args) > 1:
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bug = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
bug.status = "closed"
bd.save()
diff --git a/becommands/comment.py b/becommands/comment.py
index 0b3a576..9a614b2 100644
--- a/becommands/comment.py
+++ b/becommands/comment.py
@@ -1,62 +1,66 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# Chris Ball <cjb@laptop.org>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Add a comment to a bug"""
-from libbe import cmdutil, bugdir, settings_object, editor
+from libbe import cmdutil, bugdir, comment, editor
import os
import sys
+try: # import core module, Python >= 2.5
+ from xml.etree import ElementTree
+except ImportError: # look for non-core module
+ from elementtree import ElementTree
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import time
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a", "This is a comment about a"], test=True)
+ >>> execute(["a", "This is a comment about a"], manipulate_encodings=False)
>>> bd._clear_bugs()
- >>> bug = bd.bug_from_shortname("a")
+ >>> bug = cmdutil.bug_from_shortname(bd, "a")
>>> bug.load_comments(load_full=False)
>>> comment = bug.comment_root[0]
>>> print comment.body
This is a comment about a
<BLANKLINE>
- >>> comment.From == bd.user_id
+ >>> comment.author == bd.user_id
True
>>> comment.time <= int(time.time())
True
- >>> comment.in_reply_to is settings_object.EMPTY
+ >>> comment.in_reply_to is None
True
>>> if 'EDITOR' in os.environ:
... del os.environ["EDITOR"]
- >>> execute(["b"], test=True)
+ >>> execute(["b"], manipulate_encodings=False)
Traceback (most recent call last):
UserError: No comment supplied, and EDITOR not specified.
>>> os.environ["EDITOR"] = "echo 'I like cheese' > "
- >>> execute(["b"], test=True)
+ >>> execute(["b"], manipulate_encodings=False)
>>> bd._clear_bugs()
- >>> bug = bd.bug_from_shortname("b")
+ >>> bug = cmdutil.bug_from_shortname(bd, "b")
>>> bug.load_comments(load_full=False)
>>> comment = bug.comment_root[0]
>>> print comment.body
I like cheese
<BLANKLINE>
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -65,10 +69,10 @@ def execute(args, test=False):
raise cmdutil.UsageError("Please specify a bug or comment id.")
if len(args) > 2:
raise cmdutil.UsageError("Too many arguments.")
-
+
shortname = args[0]
if shortname.count(':') > 1:
- raise cmdutil.UserError("Invalid id '%s'." % shortname)
+ raise cmdutil.UserError("Invalid id '%s'." % shortname)
elif shortname.count(':') == 1:
# Split shortname generated by Comment.comment_shortnames()
bugname = shortname.split(':')[0]
@@ -76,24 +80,30 @@ def execute(args, test=False):
else:
bugname = shortname
is_reply = False
-
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bug = bd.bug_from_shortname(bugname)
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, bugname)
bug.load_comments(load_full=False)
if is_reply:
parent = bug.comment_root.comment_from_shortname(shortname,
bug_shortname=bugname)
else:
parent = bug.comment_root
-
+
if len(args) == 1: # try to launch an editor for comment-body entry
try:
- body = editor.editor_string("Please enter your comment above")
+ if parent == bug.comment_root:
+ parent_body = bug.summary+"\n"
+ else:
+ parent_body = parent.body
+ estr = "Please enter your comment above\n\n> %s\n" \
+ % ("\n> ".join(parent_body.splitlines()))
+ body = editor.editor_string(estr)
except editor.CantFindEditor, e:
raise cmdutil.UserError, "No comment supplied, and EDITOR not specified."
if body is None:
raise cmdutil.UserError("No comment entered.")
- body = body.decode('utf-8')
elif args[1] == '-': # read body from stdin
binary = not (options.content_type == None
or options.content_type.startswith("text/"))
@@ -107,16 +117,68 @@ def execute(args, test=False):
body = args[1]
if not body.endswith('\n'):
body+='\n'
-
- comment = parent.new_reply(body=body)
- if options.content_type != None:
- comment.content_type = options.content_type
- bd.save()
+
+ if options.XML == False:
+ new = parent.new_reply(body=body)
+ if options.author != None:
+ new.author = options.author
+ if options.alt_id != None:
+ new.alt_id = options.alt_id
+ if options.content_type != None:
+ new.content_type = options.content_type
+ else: # import XML comment [list]
+ # read in the comments
+ str_body = body.encode("unicode_escape").replace(r'\n', '\n')
+ comment_list = ElementTree.XML(str_body)
+ if comment_list.tag not in ["bug", "comment-list"]:
+ raise comment.InvalidXML(
+ comment_list, "root element must be <bug> or <comment-list>")
+ new_comments = []
+ ids = []
+ for c in bug.comment_root.traverse():
+ ids.append(c.uuid)
+ if c.alt_id != None:
+ ids.append(c.alt_id)
+ for child in comment_list.getchildren():
+ if child.tag == "comment":
+ new = comment.Comment(bug)
+ new.from_xml(unicode(ElementTree.tostring(child)).decode("unicode_escape"))
+ if new.alt_id in ids:
+ raise cmdutil.UserError(
+ "Clashing comment alt_id: %s" % new.alt_id)
+ ids.append(new.uuid)
+ if new.alt_id != None:
+ ids.append(new.alt_id)
+ if new.in_reply_to == None:
+ new.in_reply_to = parent.uuid
+ new_comments.append(new)
+ else:
+ print >> sys.stderr, "Ignoring unknown tag %s in %s" \
+ % (child.tag, comment_list.tag)
+ try:
+ comment.list_to_root(new_comments,bug,root=parent, # link new comments
+ ignore_missing_references=options.ignore_missing_references)
+ except comment.MissingReference, e:
+ raise cmdutil.UserError(e)
+ # Protect against programmer error causing data loss:
+ kids = [c.uuid for c in parent.traverse()]
+ for nc in new_comments:
+ assert nc.uuid in kids, "%s wasn't added to %s" % (nc.uuid, parent.uuid)
+ nc.save()
def get_parser():
parser = cmdutil.CmdOptionParser("be comment ID [COMMENT]")
+ parser.add_option("-a", "--author", metavar="AUTHOR", dest="author",
+ help="Set the comment author", default=None)
+ parser.add_option("--alt-id", metavar="ID", dest="alt_id",
+ help="Set an alternate comment ID", default=None)
parser.add_option("-c", "--content-type", metavar="MIME", dest="content_type",
help="Set comment content-type (e.g. text/plain)", default=None)
+ parser.add_option("-x", "--xml", action="store_true", default=False,
+ dest='XML', help="Use COMMENT to specify an XML comment description rather than the comment body. The root XML element should be either <bug> or <comment-list> with one or more <comment> children. The syntax for the <comment> elements should match that generated by 'be show --xml COMMENT-ID'. Unrecognized tags are ignored. Missing tags are left at the default value. The comment UUIDs are always auto-generated, so if you set a <uuid> field, but no <alt-id> field, your <uuid> will be used as the comment's <alt-id>. An exception is raised if <alt-id> conflicts with an existing comment.")
+ parser.add_option("-i", "--ignore-missing-references", action="store_true",
+ dest="ignore_missing_references",
+ help="For XML import, if any comment's <in-reply-to> refers to a non-existent comment, ignore it (instead of raising an exception).")
return parser
longhelp="""
diff --git a/becommands/commit.py b/becommands/commit.py
new file mode 100644
index 0000000..fb85651
--- /dev/null
+++ b/becommands/commit.py
@@ -0,0 +1,78 @@
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Commit the currently pending changes to the repository"""
+from libbe import cmdutil, bugdir, editor, rcs
+import sys
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os, time
+ >>> from libbe import bug
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> full_path = "testfile"
+ >>> test_contents = "A test file"
+ >>> bd.rcs.set_file_contents(full_path, test_contents)
+ >>> execute(["Added %s." % (full_path)], manipulate_encodings=False) # doctest: +ELLIPSIS
+ Committed ...
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser)
+ if len(args) != 1:
+ raise cmdutil.UsageError("Please supply a commit message")
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if args[0] == '-': # read summary from stdin
+ assert options.body != "EDITOR", \
+ "Cannot spawn and editor when the summary is using stdin."
+ summary = sys.stdin.readline()
+ else:
+ summary = args[0]
+ if options.body == None:
+ body = None
+ elif options.body == "EDITOR":
+ body = editor.editor_string("Please enter your commit message above")
+ else:
+ body = bd.rcs.get_file_contents(options.body, allow_no_rcs=True)
+ try:
+ revision = bd.rcs.commit(summary, body=body,
+ allow_empty=options.allow_empty)
+ except rcs.EmptyCommit, e:
+ print e
+ return 1
+ else:
+ print "Committed %s" % revision
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be commit COMMENT")
+ parser.add_option("-b", "--body", metavar="FILE", dest="body",
+ help='Provide a detailed body for the commit message. In the special case that FILE == "EDITOR", spawn an editor to enter the body text (in which case you cannot use stdin for the summary)', default=None)
+ parser.add_option("-a", "--allow-empty", dest="allow_empty",
+ help="Allow empty commits",
+ default=False, action="store_true")
+ return parser
+
+longhelp="""
+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.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/becommands/depend.py b/becommands/depend.py
index 58e4388..f72b8ba 100644
--- a/becommands/depend.py
+++ b/becommands/depend.py
@@ -1,39 +1,69 @@
# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Add/remove bug dependencies"""
-from libbe import cmdutil, bugdir
+from libbe import cmdutil, bugdir, tree
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+BLOCKS_TAG="BLOCKS:"
+BLOCKED_BY_TAG="BLOCKED-BY:"
+
+class BrokenLink (Exception):
+ def __init__(self, blocked_bug, blocking_bug, blocks=True):
+ if blocks == True:
+ msg = "Missing link: %s blocks %s" \
+ % (blocking_bug.uuid, blocked_bug.uuid)
+ else:
+ msg = "Missing link: %s blocked by %s" \
+ % (blocked_bug.uuid, blocking_bug.uuid)
+ Exception.__init__(self, msg)
+ self.blocked_bug = blocked_bug
+ self.blocking_bug = blocking_bug
+
+
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> bd.save()
>>> os.chdir(bd.root)
- >>> execute(["a", "b"], test=True)
- Blocks on a:
+ >>> execute(["a", "b"], manipulate_encodings=False)
+ a blocked by:
b
- >>> execute(["a"], test=True)
- Blocks on a:
+ >>> execute(["a"], manipulate_encodings=False)
+ a blocked by:
b
- >>> execute(["--show-status", "a"], test=True) # doctest: +NORMALIZE_WHITESPACE
- Blocks on a:
+ >>> execute(["--show-status", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ a blocked by:
+ b closed
+ >>> execute(["b", "a"], manipulate_encodings=False)
+ b blocked by:
+ a
+ b blocks:
+ a
+ >>> execute(["--show-status", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ a blocked by:
+ b closed
+ a blocks:
b closed
- >>> execute(["-r", "a", "b"], test=True)
+ >>> execute(["-r", "b", "a"], manipulate_encodings=False)
+ b blocks:
+ a
+ >>> execute(["-r", "a", "b"], manipulate_encodings=False)
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -41,46 +71,83 @@ def execute(args, test=False):
bugid_args={0: lambda bug : bug.active==True,
1: lambda bug : bug.active==True})
- if len(args) < 1:
+ if options.repair == True:
+ if len(args) > 0:
+ raise cmdutil.UsageError("No arguments with --repair calls.")
+ elif len(args) < 1:
raise cmdutil.UsageError("Please a bug id.")
- if len(args) > 2:
+ elif len(args) > 2:
help()
raise cmdutil.UsageError("Too many arguments.")
-
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bugA = bd.bug_from_shortname(args[0])
+ elif len(args) == 2 and options.tree_depth != None:
+ raise cmdutil.UsageError("Only one bug id used in tree mode.")
+
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if options.repair == True:
+ good,fixed,broken = check_dependencies(bd, repair_broken_links=True)
+ assert len(broken) == 0, broken
+ if len(fixed) > 0:
+ print "Fixed the following links:"
+ print "\n".join(["%s |-- %s" % (blockee.uuid, blocker.uuid)
+ for blockee,blocker in fixed])
+ return 0
+
+ bugA = cmdutil.bug_from_shortname(bd, args[0])
+
+ if options.tree_depth != None:
+ dtree = DependencyTree(bd, bugA, options.tree_depth)
+ if len(dtree.blocked_by_tree()) > 0:
+ print "%s blocked by:" % bugA.uuid
+ for depth,node in dtree.blocked_by_tree().thread():
+ if depth == 0: continue
+ print "%s%s" % (" "*(depth), node.bug.string(shortlist=True))
+ if len(dtree.blocks_tree()) > 0:
+ print "%s blocks:" % bugA.uuid
+ for depth,node in dtree.blocks_tree().thread():
+ if depth == 0: continue
+ print "%s%s" % (" "*(depth), node.bug.string(shortlist=True))
+ return 0
+
if len(args) == 2:
- bugB = bd.bug_from_shortname(args[1])
- estrs = bugA.extra_strings
- depend_string = "BLOCKED-BY:%s" % bugB.uuid
+ bugB = cmdutil.bug_from_shortname(bd, args[1])
if options.remove == True:
- estrs.remove(depend_string)
+ remove_block(bugA, bugB)
else: # add the dependency
- estrs.append(depend_string)
- bugA.extra_strings = estrs # reassign to notice change
- bugA.save()
-
- depends = []
- for estr in bugA.extra_strings:
- if estr.startswith("BLOCKED-BY:"):
- uuid = estr[11:]
- if options.show_status == True:
- blocker = bd.bug_from_uuid(uuid)
- block_string = "%s\t%s" % (uuid, blocker.status)
- else:
- block_string = uuid
- depends.append(block_string)
- if len(depends) > 0:
- print "Blocks on %s:" % bugA.uuid
- print '\n'.join(depends)
+ add_block(bugA, bugB)
+
+ blocked_by = get_blocked_by(bd, bugA)
+ if len(blocked_by) > 0:
+ print "%s blocked by:" % bugA.uuid
+ if options.show_status == True:
+ print '\n'.join(["%s\t%s" % (bug.uuid, bug.status)
+ for bug in blocked_by])
+ else:
+ print '\n'.join([bug.uuid for bug in blocked_by])
+ blocks = get_blocks(bd, bugA)
+ if len(blocks) > 0:
+ print "%s blocks:" % bugA.uuid
+ if options.show_status == True:
+ print '\n'.join(["%s\t%s" % (bug.uuid, bug.status)
+ for bug in blocks])
+ else:
+ print '\n'.join([bug.uuid for bug in blocks])
def get_parser():
- parser = cmdutil.CmdOptionParser("be depend BUG-ID [BUG-ID]")
- parser.add_option("-r", "--remove", action="store_true", dest="remove",
+ parser = cmdutil.CmdOptionParser("be depend BUG-ID [BUG-ID]\nor: be depend --repair")
+ parser.add_option("-r", "--remove", action="store_true",
+ dest="remove", default=False,
help="Remove dependency (instead of adding it)")
parser.add_option("-s", "--show-status", action="store_true",
- dest="show_status",
+ dest="show_status", default=False,
help="Show status of blocking bugs")
+ parser.add_option("-t", "--tree-depth", metavar="DEPTH", default=None,
+ type="int", dest="tree_depth",
+ help="Print dependency tree rooted at BUG-ID with DEPTH levels of both blockers and blockees. Set DEPTH <= 0 to disable the depth limit.")
+ parser.add_option("--repair", action="store_true",
+ dest="repair", default=False,
+ help="Check for and repair one-way links")
return parser
longhelp="""
@@ -89,7 +156,184 @@ If bug B is not specified, just print a list of bugs blocking (A).
To search for bugs blocked by a particular bug, try
$ be list --extra-strings BLOCKED-BY:<your-bug-uuid>
+
+In repair mode, add the missing direction to any one-way links.
+
+The "|--" symbol in the repair-mode output is inspired by the
+"negative feedback" arrow common in biochemistry. See, for example
+ http://www.nature.com/nature/journal/v456/n7223/images/nature07513-f5.0.jpg
"""
def help():
return get_parser().help_str() + longhelp
+
+# internal helper functions
+
+def _generate_blocks_string(blocked_bug):
+ return "%s%s" % (BLOCKS_TAG, blocked_bug.uuid)
+
+def _generate_blocked_by_string(blocking_bug):
+ return "%s%s" % (BLOCKED_BY_TAG, blocking_bug.uuid)
+
+def _parse_blocks_string(string):
+ assert string.startswith(BLOCKS_TAG)
+ return string[len(BLOCKS_TAG):]
+
+def _parse_blocked_by_string(string):
+ assert string.startswith(BLOCKED_BY_TAG)
+ return string[len(BLOCKED_BY_TAG):]
+
+def _add_remove_extra_string(bug, string, add):
+ estrs = bug.extra_strings
+ if add == True:
+ estrs.append(string)
+ else: # remove the string
+ estrs.remove(string)
+ bug.extra_strings = estrs # reassign to notice change
+
+def _get_blocks(bug):
+ uuids = []
+ for line in bug.extra_strings:
+ if line.startswith(BLOCKS_TAG):
+ uuids.append(_parse_blocks_string(line))
+ return uuids
+
+def _get_blocked_by(bug):
+ uuids = []
+ for line in bug.extra_strings:
+ if line.startswith(BLOCKED_BY_TAG):
+ uuids.append(_parse_blocked_by_string(line))
+ return uuids
+
+def _repair_one_way_link(blocked_bug, blocking_bug, blocks=None):
+ if blocks == True: # add blocks link
+ blocks_string = _generate_blocks_string(blocked_bug)
+ _add_remove_extra_string(blocking_bug, blocks_string, add=True)
+ else: # add blocked by link
+ blocked_by_string = _generate_blocked_by_string(blocking_bug)
+ _add_remove_extra_string(blocked_bug, blocked_by_string, add=True)
+
+# functions exposed to other modules
+
+def add_block(blocked_bug, blocking_bug):
+ blocked_by_string = _generate_blocked_by_string(blocking_bug)
+ _add_remove_extra_string(blocked_bug, blocked_by_string, add=True)
+ blocks_string = _generate_blocks_string(blocked_bug)
+ _add_remove_extra_string(blocking_bug, blocks_string, add=True)
+
+def remove_block(blocked_bug, blocking_bug):
+ blocked_by_string = _generate_blocked_by_string(blocking_bug)
+ _add_remove_extra_string(blocked_bug, blocked_by_string, add=False)
+ blocks_string = _generate_blocks_string(blocked_bug)
+ _add_remove_extra_string(blocking_bug, blocks_string, add=False)
+
+def get_blocks(bugdir, bug):
+ """
+ Return a list of bugs that the given bug blocks.
+ """
+ blocks = []
+ for uuid in _get_blocks(bug):
+ blocks.append(bugdir.bug_from_uuid(uuid))
+ return blocks
+
+def get_blocked_by(bugdir, bug):
+ """
+ Return a list of bugs blocking the given bug blocks.
+ """
+ blocked_by = []
+ for uuid in _get_blocked_by(bug):
+ blocked_by.append(bugdir.bug_from_uuid(uuid))
+ return blocked_by
+
+def check_dependencies(bugdir, repair_broken_links=False):
+ """
+ Check that links are bi-directional for all bugs in bugdir.
+
+ >>> bd = bugdir.SimpleBugDir(sync_with_disk=False)
+ >>> a = bd.bug_from_uuid("a")
+ >>> b = bd.bug_from_uuid("b")
+ >>> blocked_by_string = _generate_blocked_by_string(b)
+ >>> _add_remove_extra_string(a, blocked_by_string, add=True)
+ >>> good,repaired,broken = check_dependencies(bd, repair_broken_links=False)
+ >>> good
+ []
+ >>> repaired
+ []
+ >>> broken
+ [(Bug(uuid='a'), Bug(uuid='b'))]
+ >>> _get_blocks(b)
+ []
+ >>> good,repaired,broken = check_dependencies(bd, repair_broken_links=True)
+ >>> _get_blocks(b)
+ ['a']
+ >>> good
+ []
+ >>> repaired
+ [(Bug(uuid='a'), Bug(uuid='b'))]
+ >>> broken
+ []
+ """
+ if bugdir.sync_with_disk == True:
+ bugdir.load_all_bugs()
+ good_links = []
+ fixed_links = []
+ broken_links = []
+ for bug in bugdir:
+ for blocker in get_blocked_by(bugdir, bug):
+ blocks = get_blocks(bugdir, blocker)
+ if (bug, blocks) in good_links+fixed_links+broken_links:
+ continue # already checked that link
+ if bug not in blocks:
+ if repair_broken_links == True:
+ _repair_one_way_link(bug, blocker, blocks=True)
+ fixed_links.append((bug, blocker))
+ else:
+ broken_links.append((bug, blocker))
+ else:
+ good_links.append((bug, blocker))
+ for blockee in get_blocks(bugdir, bug):
+ blocked_by = get_blocked_by(bugdir, blockee)
+ if (blockee, bug) in good_links+fixed_links+broken_links:
+ continue # already checked that link
+ if bug not in blocked_by:
+ if repair_broken_links == True:
+ _repair_one_way_link(blockee, bug, blocks=False)
+ fixed_links.append((blockee, bug))
+ else:
+ broken_links.append((blockee, bug))
+ else:
+ good_links.append((blockee, bug))
+ return (good_links, fixed_links, broken_links)
+
+class DependencyTree (object):
+ """
+ Note: should probably be DependencyDiGraph.
+ """
+ def __init__(self, bugdir, root_bug, depth_limit=0):
+ self.bugdir = bugdir
+ self.root_bug = root_bug
+ self.depth_limit = depth_limit
+ def _build_tree(self, child_fn):
+ root = tree.Tree()
+ root.bug = self.root_bug
+ root.depth = 0
+ stack = [root]
+ while len(stack) > 0:
+ node = stack.pop()
+ if self.depth_limit > 0 and node.depth == self.depth_limit:
+ continue
+ for bug in child_fn(self.bugdir, node.bug):
+ child = tree.Tree()
+ child.bug = bug
+ child.depth = node.depth+1
+ node.append(child)
+ stack.append(child)
+ return root
+ def blocks_tree(self):
+ if not hasattr(self, "_blocks_tree"):
+ self._blocks_tree = self._build_tree(get_blocks)
+ return self._blocks_tree
+ def blocked_by_tree(self):
+ if not hasattr(self, "_blocked_by_tree"):
+ self._blocked_by_tree = self._build_tree(get_blocked_by)
+ return self._blocked_by_tree
diff --git a/becommands/diff.py b/becommands/diff.py
index 2bdea93..034823d 100644
--- a/becommands/diff.py
+++ b/becommands/diff.py
@@ -1,44 +1,54 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Compare bug reports with older tree"""
from libbe import cmdutil, bugdir, diff
import os
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
>>> original = bd.rcs.commit("Original status")
>>> bug = bd.bug_from_uuid("a")
>>> bug.status = "closed"
- >>> bd.save()
>>> changed = bd.rcs.commit("Closed bug a")
>>> os.chdir(bd.root)
>>> if bd.rcs.versioned == True:
- ... execute([original], test=True)
+ ... execute([original], manipulate_encodings=False)
... else:
- ... print "a:cm: Bug A\\nstatus: open -> closed\\n"
- Modified bug reports:
- a:cm: Bug A
- status: open -> closed
- <BLANKLINE>
+ ... print "Modified bugs:\\n a:cm: Bug A\\n Changed bug settings:\\n status: open -> closed"
+ Modified bugs:
+ a:cm: Bug A
+ Changed bug settings:
+ status: open -> closed
+ >>> if bd.rcs.versioned == True:
+ ... execute(["--modified", original], manipulate_encodings=False)
+ ... else:
+ ... print "a"
+ a
+ >>> if bd.rcs.versioned == False:
+ ... execute([original], manipulate_encodings=False)
+ ... else:
+ ... print "This directory is not revision-controlled."
+ This directory is not revision-controlled.
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -49,27 +59,32 @@ def execute(args, test=False):
revision = args[0]
if len(args) > 1:
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if bd.rcs.versioned == False:
print "This directory is not revision-controlled."
else:
+ if revision == None: # get the most recent revision
+ revision = bd.rcs.revision_id(-1)
old_bd = bd.duplicate_bugdir(revision)
- r,m,a = diff.diff(old_bd, bd)
-
- optbugs = []
+ d = diff.Diff(old_bd, bd)
+ tree = d.report_tree()
+
+ uuids = []
if options.all == True:
options.new = options.modified = options.removed = True
if options.new == True:
- optbugs.extend(a)
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/new")])
if options.modified == True:
- optbugs.extend([new for old,new in m])
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/mod")])
if options.removed == True:
- optbugs.extend(r)
- if len(optbugs) > 0:
- for bug in optbugs:
- print bug.uuid
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/rem")])
+ if (options.new or options.modified or options.removed) == True:
+ print "\n".join(uuids)
else :
- print diff.diff_report((r,m,a), bd).encode(bd.encoding)
+ rep = tree.report_string()
+ if rep != None:
+ print rep
bd.remove_duplicate_bugdir()
def get_parser():
@@ -85,13 +100,14 @@ def get_parser():
long = "--%s" % s[1]
help = s[2]
parser.add_option(short, long, action="store_true",
- dest=attr, help=help)
+ default=False, dest=attr, help=help)
return parser
longhelp="""
-Uses the RCS to compare the current tree with a previous tree, and prints
-a pretty report. If specifier is given, it is a specifier for the particular
-previous tree to use. Specifiers are specific to their RCS.
+Uses the RCS to compare the current tree with a previous tree, and
+prints a pretty report. If REVISION is given, it is a specifier for
+the particular previous tree to use. Specifiers are specific to their
+RCS.
For Arch your specifier must be a fully-qualified revision name.
diff --git a/becommands/help.py b/becommands/help.py
index b0b182d..a8f346a 100644
--- a/becommands/help.py
+++ b/becommands/help.py
@@ -1,28 +1,29 @@
# Copyright (C) 2006-2009 Aaron Bentley and Panometrics, Inc.
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
-# <tgerigk@gmx.de>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Print help for given subcommand"""
from libbe import cmdutil, utility
__desc__ = __doc__
-def execute(args):
+def execute(args, manipulate_encodings=False):
"""
- Print help of specified command.
+ Print help of specified command (the manipulate_encodings argument
+ is ignored).
+
>>> execute(["help"])
Usage: be help [COMMAND]
<BLANKLINE>
diff --git a/becommands/html.py b/becommands/html.py
index 4bf43f4..3acf973 100644
--- a/becommands/html.py
+++ b/becommands/html.py
@@ -1,8 +1,4 @@
-# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
-# Marien Zwart <marienz@gentoo.org>
-# Thomas Gerigk <tgerigk@gmx.de>
-# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
+# Copyright (C) 2005-2009
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,24 +14,33 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Generate a static HTML dump of the current repository status"""
-from libbe import cmdutil, bugdir, bug, settings_object
+from libbe import cmdutil, bugdir, bug
#from html_data import *
import codecs, os, re, string, time
import xml.sax.saxutils, htmlentitydefs
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> print bd.bug_from_shortname("b").status
- closed
- >>> execute(["b"], test=True)
- >>> bd._clear_bugs()
- >>> print bd.bug_from_shortname("b").status
- open
+ >>> execute([], manipulate_encodings=False)
+ Creating the html output in html_export
+ >>> os.path.exists("./html_export")
+ True
+ >>> os.path.exists("./html_export/index.html")
+ True
+ >>> os.path.exists("./html_export/index_inactive.html")
+ True
+ >>> os.path.exists("./html_export/bugs")
+ True
+ >>> os.path.exists("./html_export/bugs/a.html")
+ True
+ >>> os.path.exists("./html_export/bugs/b.html")
+ True
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -51,7 +56,8 @@ def execute(args, test=False):
if len(args) > 0:
raise cmdutil.UsageError, "Too many arguments."
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bd.load_all_bugs()
status_list = bug.status_values
severity_list = bug.severity_values
@@ -98,7 +104,7 @@ def complete(options, args, parser):
def escape(string):
- if string == settings_object.EMPTY:
+ if string == None:
return ""
chars = []
for char in xml.sax.saxutils.escape(string):
diff --git a/becommands/init.py b/becommands/init.py
index 390dd15..4156a26 100644
--- a/becommands/init.py
+++ b/becommands/init.py
@@ -1,26 +1,25 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Assign the root directory for bug tracking"""
import os.path
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility, rcs
>>> import os
@@ -30,7 +29,7 @@ def execute(args, test=False):
... except bugdir.NoBugDir, e:
... True
True
- >>> execute(['--root', dir.path], test=True)
+ >>> execute(['--root', dir.path], manipulate_encodings=False)
No revision control detected.
Directory initialized.
>>> del(dir)
@@ -41,17 +40,17 @@ def execute(args, test=False):
>>> rcs.init('.')
>>> print rcs.name
Arch
- >>> execute([], test=True)
+ >>> execute([], manipulate_encodings=False)
Using Arch for revision control.
Directory initialized.
>>> rcs.cleanup()
>>> try:
- ... execute(['--root', '.'], test=True)
+ ... execute(['--root', '.'], manipulate_encodings=False)
... except cmdutil.UserError, e:
... str(e).startswith("Directory already initialized: ")
True
- >>> execute(['--root', '/highly-unlikely-to-exist'], test=True)
+ >>> execute(['--root', '/highly-unlikely-to-exist'], manipulate_encodings=False)
Traceback (most recent call last):
UserError: No such directory: /highly-unlikely-to-exist
>>> os.chdir('/')
@@ -65,7 +64,7 @@ def execute(args, test=False):
bd = bugdir.BugDir(options.root_dir, from_disk=False,
sink_to_existing_root=False,
assert_new_BugDir=True,
- manipulate_encodings=not test)
+ manipulate_encodings=manipulate_encodings)
except bugdir.NoRootEntry:
raise cmdutil.UserError("No such directory: %s" % options.root_dir)
except bugdir.AlreadyInitialized:
diff --git a/becommands/list.py b/becommands/list.py
index 443704b..12e1e29 100644
--- a/becommands/list.py
+++ b/becommands/list.py
@@ -2,21 +2,20 @@
# Chris Ball <cjb@laptop.org>
# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""List bugs"""
from libbe import cmdutil, bugdir, bug
import os
@@ -27,16 +26,17 @@ __desc__ = __doc__
AVAILABLE_CMPS = [fn[4:] for fn in dir(bug) if fn[:4] == 'cmp_']
AVAILABLE_CMPS.remove("attr") # a cmp_* template.
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute([], test=True)
+ >>> execute([], manipulate_encodings=False)
a:om: Bug A
- >>> execute(["--status", "all"], test=True)
+ >>> execute(["--status", "all"], manipulate_encodings=False)
a:om: Bug A
b:cm: Bug B
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -47,11 +47,13 @@ def execute(args, test=False):
if options.sort_by != None:
for cmp in options.sort_by.split(','):
if cmp not in AVAILABLE_CMPS:
- raise cmdutil.UserError("Invalid sort on '%s'.\nValid sorts:\n %s"
- % (cmp, '\n '.join(AVAILABLE_CMPS)))
+ raise cmdutil.UserError(
+ "Invalid sort on '%s'.\nValid sorts:\n %s"
+ % (cmp, '\n '.join(AVAILABLE_CMPS)))
cmp_list.append(eval('bug.cmp_%s' % cmp))
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bd.load_all_bugs()
# select status
if options.status != None:
@@ -135,11 +137,12 @@ def execute(args, test=False):
return True
bugs = [b for b in bd if filter(b) ]
- if len(bugs) == 0:
+ if len(bugs) == 0 and options.xml == False:
print "No matching bugs found"
def list_bugs(cur_bugs, title=None, just_uuids=False, xml=False):
if xml == True:
+ print '<?xml version="1.0" encoding="%s" ?>' % bd.encoding
print "<bugs>"
if len(cur_bugs) > 0:
if title != None and xml == False:
diff --git a/becommands/merge.py b/becommands/merge.py
index 4bec6bf..f212b01 100644
--- a/becommands/merge.py
+++ b/becommands/merge.py
@@ -1,28 +1,28 @@
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Merge duplicate bugs"""
from libbe import cmdutil, bugdir
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
>>> a = bd.bug_from_shortname("a")
>>> a.comment_root.time = 0
>>> dummy = a.new_comment("Testing")
@@ -36,9 +36,8 @@ def execute(args, test=False):
>>> dummy.time = 1
>>> dummy = dummy.new_reply("1 2 3 4")
>>> dummy.time = 2
- >>> bd.save()
>>> os.chdir(bd.root)
- >>> execute(["a", "b"], test=True)
+ >>> execute(["a", "b"], manipulate_encodings=False)
Merging bugs a and b
>>> bd._clear_bugs()
>>> a = bd.bug_from_shortname("a")
@@ -121,6 +120,7 @@ def execute(args, test=False):
Merged into bug a
>>> print b.status
closed
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -134,20 +134,21 @@ def execute(args, test=False):
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bugA = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bugA = cmdutil.bug_from_shortname(bd, args[0])
bugA.load_comments()
- bugB = bd.bug_from_shortname(args[1])
+ bugB = cmdutil.bug_from_shortname(bd, args[1])
bugB.load_comments()
mergeA = bugA.new_comment("Merged from bug %s" % bugB.uuid)
newCommTree = copy.deepcopy(bugB.comment_root)
- for comment in newCommTree.traverse():
+ for comment in newCommTree.traverse(): # all descendant comments
comment.bug = bugA
- for comment in newCommTree:
+ comment.save() # force onto disk under bugA
+ for comment in newCommTree: # just the child comments
mergeA.add_reply(comment, allow_time_inversion=True)
bugB.new_comment("Merged into bug %s" % bugA.uuid)
bugB.status = "closed"
- bd.save()
print "Merging bugs %s and %s" % (bugA.uuid, bugB.uuid)
def get_parser():
diff --git a/becommands/new.py b/becommands/new.py
index 32e070a..a8ee2ec 100644
--- a/becommands/new.py
+++ b/becommands/new.py
@@ -1,34 +1,34 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Create a new bug"""
-from libbe import cmdutil, bugdir, settings_object
+from libbe import cmdutil, bugdir
+import sys
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os, time
>>> from libbe import bug
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> bug.uuid_gen = lambda: "X"
- >>> execute (["this is a test",], test=True)
+ >>> execute (["this is a test",], manipulate_encodings=False)
Created bug with ID X
- >>> bd.load()
+ >>> bd._clear_bugs()
>>> bug = bd.bug_from_uuid("X")
>>> print bug.summary
this is a test
@@ -36,25 +36,30 @@ def execute(args, test=False):
True
>>> print bug.severity
minor
- >>> bug.target == settings_object.EMPTY
+ >>> bug.target == None
True
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
cmdutil.default_complete(options, args, parser)
if len(args) != 1:
raise cmdutil.UsageError("Please supply a summary message")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bug = bd.new_bug(summary=args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if args[0] == '-': # read summary from stdin
+ summary = sys.stdin.readline()
+ else:
+ summary = args[0]
+ bug = bd.new_bug(summary=summary.strip())
if options.reporter != None:
bug.reporter = options.reporter
else:
bug.reporter = bug.creator
if options.assigned != None:
bug.assigned = options.assigned
- elif bd.default_assignee != settings_object.EMPTY:
+ elif bd.default_assignee != None:
bug.assigned = bd.default_assignee
- bd.save()
print "Created bug with ID %s" % bd.bug_shortname(bug)
def get_parser():
@@ -66,8 +71,9 @@ def get_parser():
return parser
longhelp="""
-Create a new bug, with a new ID. The summary specified on the commandline
-is a string that describes the bug briefly.
+Create a new bug, with a new ID. The summary specified on the
+commandline is a string (only one line) that describes the bug briefly
+or "-", in which case the string will be read from stdin.
"""
def help():
diff --git a/becommands/open.py b/becommands/open.py
index f9abcbb..0c6bf05 100644
--- a/becommands/open.py
+++ b/becommands/open.py
@@ -2,36 +2,36 @@
# Marien Zwart <marienz@gentoo.org>
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Re-open a bug"""
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("b").status
closed
- >>> execute(["b"], test=True)
+ >>> execute(["b"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> print bd.bug_from_shortname("b").status
open
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -41,10 +41,10 @@ def execute(args, test=False):
raise cmdutil.UsageError, "Please specify a bug id."
if len(args) > 1:
raise cmdutil.UsageError, "Too many arguments."
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bug = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
bug.status = "open"
- bd.save()
def get_parser():
parser = cmdutil.CmdOptionParser("be open BUG-ID")
diff --git a/becommands/remove.py b/becommands/remove.py
index 213a8d9..8d85033 100644
--- a/becommands/remove.py
+++ b/becommands/remove.py
@@ -1,39 +1,39 @@
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Remove (delete) a bug and its comments"""
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import mapfile
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("b").status
closed
- >>> execute (["b"], test=True)
+ >>> execute (["b"], manipulate_encodings=False)
Removed bug b
>>> bd._clear_bugs()
>>> try:
... bd.bug_from_shortname("b")
- ... except KeyError:
+ ... except bugdir.NoBugMatches:
... print "Bug not found"
Bug not found
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -41,10 +41,10 @@ def execute(args, test=False):
bugid_args={0: lambda bug : bug.active==True})
if len(args) != 1:
raise cmdutil.UsageError, "Please specify a bug id."
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bug = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
bd.remove_bug(bug)
- bd.save()
print "Removed bug %s" % bug.uuid
def get_parser():
diff --git a/becommands/set.py b/becommands/set.py
index e771018..e78a1ea 100644
--- a/becommands/set.py
+++ b/becommands/set.py
@@ -3,55 +3,57 @@
# Marien Zwart <marienz@gentoo.org>
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Change tree settings"""
-from libbe import cmdutil, bugdir, settings_object
+import textwrap
+from libbe import cmdutil, bugdir, rcs, settings_object
__desc__ = __doc__
def _value_string(bd, setting):
val = bd.settings.get(setting, settings_object.EMPTY)
if val == settings_object.EMPTY:
default = getattr(bd, bd._setting_name_to_attr_name(setting))
- if default != settings_object.EMPTY:
+ if default not in [None, settings_object.EMPTY]:
val = "None (%s)" % default
else:
val = None
return str(val)
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["target"], test=True)
+ >>> execute(["target"], manipulate_encodings=False)
None
- >>> execute(["target", "tomorrow"], test=True)
- >>> execute(["target"], test=True)
+ >>> execute(["target", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["target"], manipulate_encodings=False)
tomorrow
- >>> execute(["target", "none"], test=True)
- >>> execute(["target"], test=True)
+ >>> execute(["target", "none"], manipulate_encodings=False)
+ >>> execute(["target"], manipulate_encodings=False)
None
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
complete(options, args, parser)
if len(args) > 2:
raise cmdutil.UsageError, "Too many arguments"
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if len(args) == 0:
keys = bd.settings_properties
keys.sort()
@@ -60,7 +62,9 @@ def execute(args, test=False):
elif len(args) == 1:
print _value_string(bd, args[0])
else:
- if args[1] != "none":
+ if args[1] == "none":
+ setattr(bd, args[0], settings_object.EMPTY)
+ else:
if args[0] not in bd.settings_properties:
msg = "Invalid setting %s\n" % args[0]
msg += 'Allowed settings:\n '
@@ -68,14 +72,35 @@ def execute(args, test=False):
raise cmdutil.UserError(msg)
old_setting = bd.settings.get(args[0])
setattr(bd, args[0], args[1])
- else:
- del bd.settings[args[0]]
- bd.save()
def get_parser():
parser = cmdutil.CmdOptionParser("be set [NAME] [VALUE]")
return parser
+def get_bugdir_settings():
+ settings = []
+ for s in bugdir.BugDir.settings_properties:
+ settings.append(s)
+ settings.sort()
+ documented_settings = []
+ for s in settings:
+ set = getattr(bugdir.BugDir, s)
+ dstr = set.__doc__.strip()
+ # per-setting comment adjustments
+ if s == "rcs_name":
+ lines = dstr.split('\n')
+ while lines[0].startswith("This property defaults to") == False:
+ lines.pop(0)
+ assert len(lines) != None, \
+ "Unexpected rcs_name docstring:\n '%s'" % dstr
+ lines.insert(
+ 0, "The name of the revision control system to use.\n")
+ dstr = '\n'.join(lines)
+ doc = textwrap.wrap(dstr, width=70, initial_indent=' ',
+ subsequent_indent=' ')
+ documented_settings.append("%s\n%s" % (s, '\n'.join(doc)))
+ return documented_settings
+
longhelp="""
Show or change per-tree settings.
@@ -83,14 +108,11 @@ If name and value are supplied, the name is set to a new value.
If no value is specified, the current value is printed.
If no arguments are provided, all names and values are listed.
-Interesting settings are:
-rcs_name
- The name of the revision control system. "Arch" and "None" are supported.
-target
- The current development goal
-
To unset a setting, set it to "none".
-"""
+
+Allowed settings are:
+
+%s""" % ('\n'.join(get_bugdir_settings()),)
def help():
return get_parser().help_str() + longhelp
diff --git a/becommands/severity.py b/becommands/severity.py
index f8a0c02..660586e 100644
--- a/becommands/severity.py
+++ b/becommands/severity.py
@@ -2,46 +2,47 @@
# Marien Zwart <marienz@gentoo.org>
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Show or change a bug's severity level"""
from libbe import cmdutil, bugdir, bug
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
minor
- >>> execute(["a", "wishlist"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "wishlist"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
wishlist
- >>> execute(["a", "none"], test=True)
+ >>> execute(["a", "none"], manipulate_encodings=False)
Traceback (most recent call last):
UserError: Invalid severity level: none
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
complete(options, args, parser)
if len(args) not in (1,2):
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bug = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
if len(args) == 1:
print bug.severity
elif len(args) == 2:
@@ -51,7 +52,6 @@ def execute(args, test=False):
if e.name != "severity":
raise e
raise cmdutil.UserError ("Invalid severity level: %s" % e.value)
- bd.save()
def get_parser():
parser = cmdutil.CmdOptionParser("be severity BUG-ID [SEVERITY]")
diff --git a/becommands/show.py b/becommands/show.py
index ff434ab..50bd6eb 100644
--- a/becommands/show.py
+++ b/becommands/show.py
@@ -3,31 +3,31 @@
# Thomas Gerigk <tgerigk@gmx.de>
# Thomas Habets <thomas@habets.pp.se>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Show a particular bug"""
+import sys
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute (["a",], test=True) # doctest: +ELLIPSIS
+ >>> execute (["a",], manipulate_encodings=False) # doctest: +ELLIPSIS
ID : a
Short name : a
Severity : minor
@@ -39,7 +39,8 @@ def execute(args, test=False):
Created : ...
Bug A
<BLANKLINE>
- >>> execute (["--xml", "a"], test=True) # doctest: +ELLIPSIS
+ >>> execute (["--xml", "a"], manipulate_encodings=False) # doctest: +ELLIPSIS
+ <?xml version="1.0" encoding="..." ?>
<bug>
<uuid>a</uuid>
<short-name>a</short-name>
@@ -49,31 +50,66 @@ def execute(args, test=False):
<created>...</created>
<summary>Bug A</summary>
</bug>
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
cmdutil.default_complete(options, args, parser,
- bugid_args={0: lambda bug : bug.active==True})
+ bugid_args={-1: lambda bug : bug.active==True})
if len(args) == 0:
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- for bugid in args:
- bug = bd.bug_from_shortname(bugid)
- if options.dumpXML:
- print bug.xml(show_comments=True)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if options.XML:
+ print '<?xml version="1.0" encoding="%s" ?>' % bd.encoding
+ for shortname in args:
+ if shortname.count(':') > 1:
+ raise cmdutil.UserError("Invalid id '%s'." % shortname)
+ elif shortname.count(':') == 1:
+ # Split shortname generated by Comment.comment_shortnames()
+ bugname = shortname.split(':')[0]
+ is_comment = True
else:
- print bug.string(show_comments=True)
- if bugid != args[-1]:
- print "" # add a blank line between bugs
+ bugname = shortname
+ is_comment = False
+ if is_comment == True and options.comments == False:
+ continue
+ bug = cmdutil.bug_from_shortname(bd, bugname)
+ if is_comment == False:
+ if options.XML:
+ print bug.xml(show_comments=options.comments)
+ else:
+ print bug.string(show_comments=options.comments)
+ else:
+ comment = bug.comment_root.comment_from_shortname(
+ shortname, bug_shortname=bugname)
+ if options.XML:
+ print comment.xml(shortname=shortname)
+ else:
+ if len(args) == 1 and options.only_raw_body == True:
+ sys.__stdout__.write(comment.body)
+ else:
+ print comment.string(shortname=shortname)
+ if shortname != args[-1] and options.XML == False:
+ print "" # add a blank line between bugs/comments
def get_parser():
- parser = cmdutil.CmdOptionParser("be show [options] BUG-ID [BUG-ID ...]")
- parser.add_option("-x", "--xml", action="store_true",
- dest='dumpXML', help="Dump as XML")
+ parser = cmdutil.CmdOptionParser("be show [options] ID [ID ...]")
+ parser.add_option("-x", "--xml", action="store_true", default=False,
+ dest='XML', help="Dump as XML")
+ parser.add_option("--only-raw-body", action="store_true",
+ dest='only_raw_body',
+ help="When printing only a single comment, just print it's body. This allows extraction of non-text content types.")
+ parser.add_option("-c", "--no-comments", dest="comments",
+ action="store_false", default=True,
+ help="Disable comment output. This is useful if you just want more details on a bug's current status.")
return parser
longhelp="""
-Show all information about a bug.
+Show all information about the bugs or comments whose IDs are given.
+
+It's probably not a good idea to mix bug and comment IDs in a single
+call, but you're free to do so if you like.
"""
def help():
diff --git a/becommands/status.py b/becommands/status.py
index d8bd4c4..f315003 100644
--- a/becommands/status.py
+++ b/becommands/status.py
@@ -1,44 +1,45 @@
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Show or change a bug's status"""
from libbe import cmdutil, bugdir, bug
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
open
- >>> execute(["a", "closed"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "closed"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
closed
- >>> execute(["a", "none"], test=True)
+ >>> execute(["a", "none"], manipulate_encodings=False)
Traceback (most recent call last):
UserError: Invalid status: none
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
complete(options, args, parser)
if len(args) not in (1,2):
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bug = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
if len(args) == 1:
print bug.status
else:
@@ -48,7 +49,6 @@ def execute(args, test=False):
if e.name != "status":
raise
raise cmdutil.UserError ("Invalid status: %s" % e.value)
- bd.save()
def get_parser():
parser = cmdutil.CmdOptionParser("be status BUG-ID [STATUS]")
@@ -56,24 +56,40 @@ def get_parser():
def help():
- longhelp=["""
-Show or change a bug's status.
-
-If no status is specified, the current value is printed. If a status
-is specified, it will be assigned to the bug.
-
-Status levels are:
-"""]
try: # See if there are any per-tree status configurations
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=False)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=False)
except bugdir.NoBugDir, e:
pass # No tree, just show the defaults
longest_status_len = max([len(s) for s in bug.status_values])
- for status in bug.status_values :
+ active_statuses = []
+ for status in bug.active_status_values :
+ description = bug.status_description[status]
+ s = "%*s : %s" % (longest_status_len, status, description)
+ active_statuses.append(s)
+ inactive_statuses = []
+ for status in bug.inactive_status_values :
description = bug.status_description[status]
- s = "%*s : %s\n" % (longest_status_len, status, description)
- longhelp.append(s)
- longhelp = ''.join(longhelp)
+ s = "%*s : %s" % (longest_status_len, status, description)
+ inactive_statuses.append(s)
+ longhelp="""
+Show or change a bug's status.
+
+If no status is specified, the current value is printed. If a status
+is specified, it will be assigned to the bug.
+
+There are two classes of statuses, active and inactive, which are only
+important for commands like "be list" that show only active bugs by
+default.
+
+Active status levels are:
+ %s
+Inactive status levels are:
+ %s
+
+You can overide the list of allowed statuses on a per-repository basis.
+See "be set --help" for more details.
+""" % ('\n '.join(active_statuses), '\n '.join(inactive_statuses))
return get_parser().help_str() + longhelp
def complete(options, args, parser):
diff --git a/becommands/subscribe.py b/becommands/subscribe.py
new file mode 100644
index 0000000..b754937
--- /dev/null
+++ b/becommands/subscribe.py
@@ -0,0 +1,370 @@
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""(Un)subscribe to change notification"""
+from libbe import cmdutil, bugdir, tree
+import os, copy
+__desc__ = __doc__
+
+TAG="SUBSCRIBE:"
+
+class SubscriptionType (tree.Tree):
+ """
+ Trees of subscription types to allow users to select exactly what
+ notifications they want to subscribe to.
+ """
+ def __init__(self, type_name, *args, **kwargs):
+ tree.Tree.__init__(self, *args, **kwargs)
+ self.type = type_name
+ def __str__(self):
+ return self.type
+ def __repr__(self):
+ return "<SubscriptionType: %s>" % str(self)
+ def string_tree(self, indent=0):
+ lines = []
+ for depth,node in self.thread():
+ lines.append("%s%s" % (" "*(indent+2*depth), node))
+ return "\n".join(lines)
+
+BUGDIR_TYPE_NEW = SubscriptionType("new")
+BUGDIR_TYPE_ALL = SubscriptionType("all", [BUGDIR_TYPE_NEW])
+
+# same name as BUGDIR_TYPE_ALL for consistency
+BUG_TYPE_ALL = SubscriptionType(str(BUGDIR_TYPE_ALL))
+
+INVALID_TYPE = SubscriptionType("INVALID")
+
+class InvalidType (ValueError):
+ def __init__(self, type_name, type_root):
+ msg = "Invalid type %s for tree:\n%s" \
+ % (type_name, type_root.string_tree(4))
+ ValueError.__init__(self, msg)
+ self.type_name = type_name
+ self.type_root = type_root
+
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
+ >>> os.chdir(bd.root)
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ []
+ >>> execute(["-s","John Doe <j@doe.com>", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ John Doe <j@doe.com> all *
+ >>> bd._clear_bugs() # resync our copy of bug
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ ['SUBSCRIBE:John Doe <j@doe.com>\\tall\\t*']
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "a.com,b.net", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all a.com,b.net
+ John Doe <j@doe.com> all *
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "a.edu", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all a.com,a.edu,b.net
+ John Doe <j@doe.com> all *
+ >>> execute(["-u", "-s","Jane Doe <J@doe.com>", "-S", "a.com", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all a.edu,b.net
+ John Doe <j@doe.com> all *
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "*", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all *
+ John Doe <j@doe.com> all *
+ >>> execute(["-u", "-s","Jane Doe <J@doe.com>", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ John Doe <j@doe.com> all *
+ >>> execute(["-u", "-s","John Doe <j@doe.com>", "a"], manipulate_encodings=False)
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-t", "new", "DIR"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for bug directory:
+ Jane Doe <J@doe.com> new *
+ >>> execute(["-s","Jane Doe <J@doe.com>", "DIR"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for bug directory:
+ Jane Doe <J@doe.com> all *
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True})
+
+ if len(args) > 1:
+ help()
+ raise cmdutil.UsageError("Too many arguments.")
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+
+ subscriber = options.subscriber
+ if subscriber == None:
+ subscriber = bd.user_id
+ if options.unsubscribe == True:
+ if options.servers == None:
+ options.servers = "INVALID"
+ if options.types == None:
+ options.types = "INVALID"
+ else:
+ if options.servers == None:
+ options.servers = "*"
+ if options.types == None:
+ options.types = "all"
+ servers = options.servers.split(",")
+ types = options.types.split(",")
+
+ if len(args) == 0 or args[0] == "DIR": # directory-wide subscriptions
+ type_root = BUGDIR_TYPE_ALL
+ entity = bd
+ entity_name = "bug directory"
+ else: # bug-specific subscriptions
+ type_root = BUG_TYPE_ALL
+ bug = bd.bug_from_shortname(args[0])
+ entity = bug
+ entity_name = bug.uuid
+
+ types = [type_from_name(name, type_root, default=INVALID_TYPE,
+ default_ok=options.unsubscribe)
+ for name in types]
+ estrs = entity.extra_strings
+ if options.unsubscribe == True:
+ estrs = unsubscribe(estrs, subscriber, types, servers, type_root)
+ else: # add the tag
+ estrs = subscribe(estrs, subscriber, types, servers, type_root)
+ entity.extra_strings = estrs # reassign to notice change
+
+ subscriptions = []
+ for estr in entity.extra_strings:
+ if estr.startswith(TAG):
+ subscriptions.append(estr[len(TAG):])
+
+ if len(subscriptions) > 0:
+ print "Subscriptions for %s:" % entity_name
+ print '\n'.join(subscriptions)
+
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be subscribe ID")
+ parser.add_option("-u", "--unsubscribe", action="store_true",
+ dest="unsubscribe", default=False,
+ help="Unsubscribe instead of subscribing.")
+ parser.add_option("-s", "--subscriber", dest="subscriber",
+ metavar="SUBSCRIBER",
+ help="Email address of the subscriber (defaults to bugdir.user_id).")
+ parser.add_option("-S", "--servers", dest="servers", metavar="SERVERS",
+ help="Servers from which you want notification.")
+ parser.add_option("-t", "--type", dest="types", metavar="TYPES",
+ help="Types of changes you wish to be notified about.")
+ return parser
+
+longhelp="""
+ID can be either a bug id, or blank/"DIR", in which case it refers to the
+whole bug directory.
+
+SERVERS specifies the servers from which you would like to receive
+notification. Multiple severs may be specified in a comma-separated
+list, or you can use "*" to match all servers (the default). If you
+have not selected a server, it should politely refrain from notifying
+you of changes, although there is no way to guarantee this behavior.
+
+Available TYPES:
+ For bugs:
+%s
+ For DIR :
+%s
+
+For unsubscription, any listed SERVERS and TYPES are removed from your
+subscription. Either the catch-all server "*" or type "%s" will
+remove SUBSCRIBER entirely from the specified ID.
+
+This command is intended for use primarily by public interfaces, since
+if you're just hacking away on your private repository, you'll known
+what's changed ;). This command just (un)sets the appropriate
+subscriptions, and leaves it up to each interface to perform the
+notification.
+""" % (BUG_TYPE_ALL.string_tree(6), BUGDIR_TYPE_ALL.string_tree(6),
+ BUGDIR_TYPE_ALL)
+
+def help():
+ return get_parser().help_str() + longhelp
+
+# internal helper functions
+
+def _generate_string(subscriber, types, servers):
+ types = sorted([str(t) for t in types])
+ servers = sorted(servers)
+ return "%s%s\t%s\t%s" % (TAG,subscriber,",".join(types),",".join(servers))
+
+def _parse_string(string, type_root):
+ assert string.startswith(TAG), string
+ string = string[len(TAG):]
+ subscriber,types,servers = string.split("\t")
+ types = [type_from_name(name, type_root) for name in types.split(",")]
+ return (subscriber,types,servers.split(","))
+
+def _get_subscriber(extra_strings, subscriber, type_root):
+ for i,string in enumerate(extra_strings):
+ if string.startswith(TAG):
+ s,ts,srvs = _parse_string(string, type_root)
+ if s == subscriber:
+ return i,s,ts,srvs # match!
+ return None # no match
+
+# functions exposed to other modules
+
+def type_from_name(name, type_root, default=None, default_ok=False):
+ if name == str(type_root):
+ return type_root
+ for t in type_root.traverse():
+ if name == str(t):
+ return t
+ if default_ok:
+ return default
+ raise InvalidType(name, type_root)
+
+def subscribe(extra_strings, subscriber, types, servers, type_root):
+ args = _get_subscriber(extra_strings, subscriber, type_root)
+ if args == None: # no match
+ extra_strings.append(_generate_string(subscriber, types, servers))
+ return extra_strings
+ # Alter matched string
+ i,s,ts,srvs = args
+ for t in types:
+ if t not in ts:
+ ts.append(t)
+ # remove descendant types
+ all_ts = copy.copy(ts)
+ for t in all_ts:
+ for tt in all_ts:
+ if tt in ts and t.has_descendant(tt):
+ ts.remove(tt)
+ if "*" in servers+srvs:
+ srvs = ["*"]
+ else:
+ srvs = list(set(servers+srvs))
+ extra_strings[i] = _generate_string(subscriber, ts, srvs)
+ return extra_strings
+
+def unsubscribe(extra_strings, subscriber, types, servers, type_root):
+ args = _get_subscriber(extra_strings, subscriber, type_root)
+ if args == None: # no match
+ return extra_strings # pass
+ # Remove matched string
+ i,s,ts,srvs = args
+ all_ts = copy.copy(ts)
+ for t in types:
+ for tt in all_ts:
+ if tt in ts and t.has_descendant(tt):
+ ts.remove(tt)
+ if "*" in servers+srvs:
+ srvs = []
+ else:
+ for srv in servers:
+ if srv in srvs:
+ srvs.remove(srv)
+ if len(ts) == 0 or len(srvs) == 0:
+ extra_strings.pop(i)
+ else:
+ extra_strings[i] = _generate_string(subscriber, ts, srvs)
+ return extra_strings
+
+def get_subscribers(extra_strings, type, server, type_root,
+ match_ancestor_types=False,
+ match_descendant_types=False):
+ """
+ Set match_ancestor_types=True if you want to find eveyone who
+ cares about your particular type.
+
+ Set match_descendant_types=True if you want to find subscribers
+ who may only care about some subset of your type. This is useful
+ for generating lists of all the subscribers in a given set of
+ extra_strings.
+
+ >>> def sgs(*args, **kwargs):
+ ... return sorted(get_subscribers(*args, **kwargs))
+ >>> es = []
+ >>> es = subscribe(es, "John Doe <j@doe.com>", [BUGDIR_TYPE_ALL], ["a.com"], BUGDIR_TYPE_ALL)
+ >>> es = subscribe(es, "Jane Doe <J@doe.com>", [BUGDIR_TYPE_NEW], ["*"], BUGDIR_TYPE_ALL)
+ >>> sgs(es, BUGDIR_TYPE_ALL, "a.com", BUGDIR_TYPE_ALL)
+ ['John Doe <j@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_ALL, "a.com", BUGDIR_TYPE_ALL, match_descendant_types=True)
+ ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_ALL, "b.net", BUGDIR_TYPE_ALL, match_descendant_types=True)
+ ['Jane Doe <J@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_NEW, "a.com", BUGDIR_TYPE_ALL)
+ ['Jane Doe <J@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_NEW, "a.com", BUGDIR_TYPE_ALL, match_ancestor_types=True)
+ ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>']
+ """
+ for string in extra_strings:
+ subscriber,types,servers = _parse_string(string, type_root)
+ type_match = False
+ if type in types:
+ type_match = True
+ if type_match == False and match_ancestor_types == True:
+ for t in types:
+ if t.has_descendant(type):
+ type_match = True
+ break
+ if type_match == False and match_descendant_types == True:
+ for t in types:
+ if type.has_descendant(t):
+ type_match = True
+ break
+ server_match = False
+ if server in servers or servers == ["*"]:
+ server_match = True
+ if type_match == True and server_match == True:
+ yield subscriber
+
+def get_bugdir_subscribers(bugdir, server):
+ """
+ I have a bugdir. Who cares about it, and what do they care about?
+ Returns a dict of dicts:
+ subscribers[user][id] = types
+ where id is either a bug.uuid (in the case of a bug subscription)
+ or "DIR" (in the case of a bugdir subscription).
+
+ >>> bd = bugdir.SimpleBugDir(sync_with_disk=False)
+ >>> a = bd.bug_from_shortname("a")
+ >>> bd.extra_strings = subscribe(bd.extra_strings, "John Doe <j@doe.com>", [BUGDIR_TYPE_ALL], ["a.com"], BUGDIR_TYPE_ALL)
+ >>> bd.extra_strings = subscribe(bd.extra_strings, "Jane Doe <J@doe.com>", [BUGDIR_TYPE_NEW], ["*"], BUGDIR_TYPE_ALL)
+ >>> a.extra_strings = subscribe(a.extra_strings, "John Doe <j@doe.com>", [BUG_TYPE_ALL], ["a.com"], BUG_TYPE_ALL)
+ >>> subscribers = get_bugdir_subscribers(bd, "a.com")
+ >>> subscribers["Jane Doe <J@doe.com>"]["DIR"]
+ [<SubscriptionType: new>]
+ >>> subscribers["John Doe <j@doe.com>"]["DIR"]
+ [<SubscriptionType: all>]
+ >>> subscribers["John Doe <j@doe.com>"]["a"]
+ [<SubscriptionType: all>]
+ >>> get_bugdir_subscribers(bd, "b.net")
+ {'Jane Doe <J@doe.com>': {'DIR': [<SubscriptionType: new>]}}
+ >>> bd.cleanup()
+ """
+ subscribers = {}
+ for sub in get_subscribers(bugdir.extra_strings, BUGDIR_TYPE_ALL, server,
+ BUGDIR_TYPE_ALL, match_descendant_types=True):
+ i,s,ts,srvs = _get_subscriber(bugdir.extra_strings,sub,BUGDIR_TYPE_ALL)
+ subscribers[sub] = {"DIR":ts}
+ for bug in bugdir:
+ for sub in get_subscribers(bug.extra_strings, BUG_TYPE_ALL, server,
+ BUG_TYPE_ALL, match_descendant_types=True):
+ i,s,ts,srvs = _get_subscriber(bug.extra_strings,sub,BUG_TYPE_ALL)
+ if sub in subscribers:
+ subscribers[sub][bug.uuid] = ts
+ else:
+ subscribers[sub] = {bug.uuid:ts}
+ return subscribers
diff --git a/becommands/tag.py b/becommands/tag.py
index ab0324e..ecd853f 100644
--- a/becommands/tag.py
+++ b/becommands/tag.py
@@ -1,50 +1,51 @@
# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Tag a bug, or search bugs for tags"""
from libbe import cmdutil, bugdir
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
>>> os.chdir(bd.root)
>>> a = bd.bug_from_shortname("a")
>>> print a.extra_strings
[]
- >>> execute(["a", "GUI"], test=True)
+ >>> execute(["a", "GUI"], manipulate_encodings=False)
Tags for a:
GUI
>>> bd._clear_bugs() # resync our copy of bug
>>> a = bd.bug_from_shortname("a")
>>> print a.extra_strings
['TAG:GUI']
- >>> execute(["a", "later"], test=True)
+ >>> execute(["a", "later"], manipulate_encodings=False)
Tags for a:
GUI
later
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
Tags for a:
GUI
later
- >>> execute(["--list"], test=True)
+ >>> execute(["--list"], manipulate_encodings=False)
GUI
later
- >>> execute(["a", "Alphabetically first"], test=True)
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
Tags for a:
Alphabetically first
GUI
@@ -56,16 +57,16 @@ def execute(args, test=False):
>>> a.extra_strings = []
>>> print a.extra_strings
[]
- >>> a.save()
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
>>> bd._clear_bugs() # resync our copy of bug
>>> a = bd.bug_from_shortname("a")
>>> print a.extra_strings
[]
- >>> execute(["a", "Alphabetically first"], test=True)
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
Tags for a:
Alphabetically first
- >>> execute(["--remove", "a", "Alphabetically first"], test=True)
+ >>> execute(["--remove", "a", "Alphabetically first"], manipulate_encodings=False)
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -78,7 +79,8 @@ def execute(args, test=False):
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if options.list:
bd.load_all_bugs()
tags = []
@@ -92,7 +94,7 @@ def execute(args, test=False):
if len(tags) > 0:
print '\n'.join(tags)
return
- bug = bd.bug_from_shortname(args[0])
+ bug = cmdutil.bug_from_shortname(bd, args[0])
if len(args) == 2:
given_tag = args[1]
estrs = bug.extra_strings
@@ -102,7 +104,6 @@ def execute(args, test=False):
else: # add the tag
estrs.append(tag_string)
bug.extra_strings = estrs # reassign to notice change
- bug.save()
tags = []
for estr in bug.extra_strings:
diff --git a/becommands/target.py b/becommands/target.py
index 283998a..7e41451 100644
--- a/becommands/target.py
+++ b/becommands/target.py
@@ -4,40 +4,40 @@
# Marien Zwart <marienz@gentoo.org>
# Thomas Gerigk <tgerigk@gmx.de>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Show or change a bug's target for fixing"""
-from libbe import cmdutil, bugdir, settings_object
+from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
No target assigned.
- >>> execute(["a", "tomorrow"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
tomorrow
- >>> execute(["--list"], test=True)
+ >>> execute(["--list"], manipulate_encodings=False)
tomorrow
- >>> execute(["a", "none"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "none"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
No target assigned.
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -47,16 +47,17 @@ def execute(args, test=False):
if len(args) not in (1, 2):
if not (options.list == True and len(args) == 0):
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if options.list:
ts = set([bd.bug_from_uuid(bug).target for bug in bd.list_uuids()])
for target in sorted(ts):
if target and isinstance(target,str):
print target
return
- bug = bd.bug_from_shortname(args[0])
+ bug = cmdutil.bug_from_shortname(bd, args[0])
if len(args) == 1:
- if bug.target is None or bug.target is settings_object.EMPTY:
+ if bug.target is None:
print "No target assigned."
else:
print bug.target
@@ -66,7 +67,6 @@ def execute(args, test=False):
bug.target = None
else:
bug.target = args[1]
- bd.save()
def get_parser():
parser = cmdutil.CmdOptionParser("be target BUG-ID [TARGET]\nor: be target --list")
diff --git a/doc/module.mk b/doc/module.mk
index 828f15f..7791f48 100644
--- a/doc/module.mk
+++ b/doc/module.mk
@@ -4,9 +4,21 @@
# Part of Bugs Everywhere, a distributed bug tracking system.
#
# Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org>
-# This is free software; you may copy, modify and/or distribute this work
-# under the terms of the GNU General Public License, version 2 or later.
-# No warranty expressed or implied. See the file COPYING for details.
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# Makefile module for documentation
diff --git a/interfaces/README b/interfaces/README
new file mode 100644
index 0000000..4d74580
--- /dev/null
+++ b/interfaces/README
@@ -0,0 +1,34 @@
+Removing spam commits from the history
+======================================
+
+arch bzr darcs git hg none
+
+In the case that some spam or inappropriate comment makes its way
+through you interface, you can remove the offending commit XYZ with:
+
+ If the offending commit is the last commit:
+
+ arch:
+ bzr: bzr uncommit && bzr revert
+ darcs: darcs obliterate --last=1
+ git: git reset --hard HEAD^
+ hg: hg rollback && hg revert
+
+ If the offending commit is not the last commit:
+
+ arch:
+ bzr: bzr rebase -r <XYZ+1>..-1 --onto before:XYZ .
+ (requires bzr-rebase plugin, note, you have to increment XYZ by
+ hand for <XYZ+1>, because bzr does not support "after:XYZ".)
+ darcs: darcs obliterate --matches 'name XYZ'
+ git: git rebase --onto XYZ~1 XYZ
+ hg: -not-supported-
+ (From http://hgbook.red-bean.com/read/finding-and-fixing-mistakes.html#id394667
+ "Mercurial also does not provide a way to make a file or
+ changeset completely disappear from history, because there is no
+ way to enforce its disappearance")
+
+Note that all of these _change_the_repo_history_, so only do this on
+your interface-specific repo before it interacts with any other repo.
+Otherwise, you'll have to survive by cherry-picking only the good
+commits.
diff --git a/xml/catmutt b/interfaces/email/catmutt
index 601f14f..601f14f 100755
--- a/xml/catmutt
+++ b/interfaces/email/catmutt
diff --git a/interfaces/email/interactive/README b/interfaces/email/interactive/README
new file mode 100644
index 0000000..8954383
--- /dev/null
+++ b/interfaces/email/interactive/README
@@ -0,0 +1,145 @@
+Overview
+========
+
+The interactive email interface to Bugs Everywhere (BE) attempts to
+provide a Debian-bug-tracking-system-style interface to a BE
+repository. Users can mail in bug reports, comments, or control
+requests, which will be committed to the served repository.
+Developers can then pull the changes they approve of from the served
+repository into their other repositories and push updates back onto
+the served repository.
+
+For details about the Debian bug tracking system that inspired this
+interface, see http://www.debian.org/Bugs .
+
+Architecture
+============
+
+In order to reduce setup costs, the entire interface can piggyback on
+an existing email address, although from a security standpoint it's
+probably best to create a dedicated user. Incoming email is filtered
+by procmail, with matching emails being piped into be-handle-mail for
+execution.
+
+Once be-handle-mail receives the email, the parsing method is selected
+according to the subject tag that procmail used grab the email in the
+first place. There are three parsing styles:
+ Style Subject
+ creating bugs [be-bug:submit] new bug summary
+ commenting on bugs [be-bug:<bug-id>] human-specific subject
+ control [be-bug] human-specific subject
+These are analogous to submit@bugs.debian.org, nnn@bugs.debian.org,
+and control@bugs.debian.org respectively.
+
+Creating bugs
+=============
+
+This interface creates a bug whose summary is given by the email's
+post-tag subject. The body of the email must begin with a
+pseudo-header containing at least the "Version" field. Anything after
+the pseudo-header and before a line starting with '--' is, if present,
+attached as the bug's first comment.
+
+ From jdoe@example.com Fri Apr 18 12:00:00 2008
+ From: John Doe <jdoe@example.com>
+ Date: Fri, 18 Apr 2008 12:00:00 +0000
+ Content-Type: text/plain; charset=UTF-8
+ Content-Transfer-Encoding: 8bit
+ Subject: [be-bug:submit] Need tests for the email interface.
+
+ Version: XYZ
+ Severity: minor
+
+ Someone should write up a series of test emails to send into
+ be-handle mail so we can test changes quickly without having to
+ use procmail.
+
+ --
+ Goofy tagline not included.
+
+Available pseudo-headers are Version, Reporter, Assign, Depend,
+Severity, Status, Tag, and Target.
+
+Commenting on bugs
+==================
+
+This interface appends a comment to the bug specified in the subject
+tag. The the first non-multipart body is attached with the
+appropriate content-type. In the case of "text/plain" contents,
+anything following a line starting with '--' is stripped.
+
+ From jdoe@example.com Fri Apr 18 12:00:00 2008
+ From: John Doe <jdoe@example.com>
+ Date: Fri, 18 Apr 2008 12:00:00 +0000
+ Content-Type: text/plain; charset=UTF-8
+ Content-Transfer-Encoding: 8bit
+ Subject: [be-bug:XYZ] Isolated problem in baz()
+
+ Finally tracked it down to the bar() call. Some sort of
+ string<->unicode conversion problem. Solution ideas?
+
+ --
+ Goofy tagline not included.
+
+Controlling bugs
+================
+
+This interface consists of a list of allowed be commands, with one
+command per line. Blank lines and lines beginning with '#' are
+ignored, as well anything following a line starting with '--'. All
+the listed commands are executed in order and their output returned.
+The commands are split into arguments with the POSIX-compliant
+shlex.split().
+
+ From jdoe@example.com Fri Apr 18 12:00:00 2008
+ From: John Doe <jdoe@example.com>
+ Date: Fri, 18 Apr 2008 12:00:00 +0000
+ Content-Type: text/plain; charset=UTF-8
+ Content-Transfer-Encoding: 8bit
+ Subject: [be-bug] I'll handle XYZ by release 1.2.3
+
+ assign XYZ "John Doe <jdoe@example.com>"
+ status XYZ assigned
+ severity XYZ critical
+ target XYZ 1.2.3
+
+ --
+ Goofy tagline ignored.
+
+Example emails
+==============
+
+Take a look at my interfaces/email/interactive/examples for some
+more examples.
+
+Procmail rules
+==============
+
+The file _procmailrc as it stands is fairly appropriate for as a
+dedicated user's ~/.procmailrc. It forwards matching mail to
+be-handle-mail, which should be installed somewhere in the user's
+path. All non-matching mail is dumped into /dev/null. Everything
+procmail does will be logged to ~/be-mail/procmail.log.
+
+If you're piggybacking the interface on top of an existing account,
+you probably only need to add the be-handle-mail stanza to your
+existing ~/.procmailrc, since you will still want to receive non-bug
+emails.
+
+Note that you will probably have to add a
+ --be-dir /path/to/served/repository
+option to the be-handle-mail invocation so it knows what repository to
+serve.
+
+Multiple repositories may be served by the same email address by adding
+multiple be-handle-mail stanzas, each matching a different tag, for
+example the "[be-bug" portion of the stanza could be "[projectX-bug",
+"[projectY-bug", etc. If you change the base tag, be sure to add a
+ --tag-base "projectX-bug"
+or equivalent to your be-handle-mail invocation.
+
+Testing
+=======
+
+Send test emails in to be-handle-mail with something like
+ cat examples/blank | ./be-handle-mail -o -l - -a
diff --git a/interfaces/email/interactive/_procmailrc b/interfaces/email/interactive/_procmailrc
new file mode 100644
index 0000000..d42c0cf
--- /dev/null
+++ b/interfaces/email/interactive/_procmailrc
@@ -0,0 +1,22 @@
+# .procmailrc
+#
+# see man procmail, procmailrc, and procmailex
+#
+# If you already have a ~/.procmailrc file, you probably only need to
+# insert the bug-email grabbing stanza in your ~/.procmailrc.
+#
+# This file is released to the Public Domain.
+
+MAILDIR=$HOME/be-mail
+LOGFILE=$MAILDIR/procmail.log
+
+# Grab all incoming bug emails (but not replies). This rule eats
+# matching emails (i.e. no further procmail processing).
+:0
+* ^Subject: \[be-bug
+* !^Subject:.*\[be-bug].*Re:
+| be-handle-mail
+
+# Drop everything else
+:0
+/dev/null
diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail
new file mode 100755
index 0000000..1d02ccf
--- /dev/null
+++ b/interfaces/email/interactive/be-handle-mail
@@ -0,0 +1,893 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Provide and email interface to the distributed bugtracker Bugs
+Everywhere. Recieves incoming email via procmail. Provides an
+interface similar to the Debian Bug Tracker. There are currently
+three distinct email types: submits, comments, and controls. The
+email types are differentiated by tags in the email subject. See
+SUBJECT_TAG* for the current values.
+
+Submit emails create a bug (and optionally add some intitial
+comments). The post-tag subject is used as the bug summary, and the
+email body is parsed for a pseudo-header. Any text after the
+psuedo-header but before a possible line starting with BREAK is added
+as the initial bug comment.
+
+Comment emails...
+
+Control emails...
+
+Any changes made to the repository are commited after the email is
+executed, with the email's post-tag subject as the commit message.
+"""
+
+import codecs
+import cStringIO as StringIO
+import email
+from email.mime.multipart import MIMEMultipart
+import email.utils
+import os
+import os.path
+import re
+import shlex
+import sys
+import time
+import traceback
+import doctest
+import unittest
+
+from becommands import subscribe
+import libbe.cmdutil, libbe.encoding, libbe.utility, libbe.diff, \
+ libbe.bugdir, libbe.bug, libbe.comment
+import send_pgp_mime
+
+THIS_SERVER = u"thor.physics.drexel.edu"
+THIS_ADDRESS = u"BE Bugs <wking@thor.physics.drexel.edu>"
+
+_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
+BE_DIR = _THIS_DIR
+LOGPATH = os.path.join(_THIS_DIR, u"be-handle-mail.log")
+LOGFILE = None
+
+# Tag strings generated by generate_global_tags()
+SUBJECT_TAG_BASE = u"be-bug"
+SUBJECT_TAG_RESPONSE = None
+SUBJECT_TAG_START = None
+SUBJECT_TAG_NEW = None
+SUBJECT_TAG_COMMENT = None
+SUBJECT_TAG_CONTROL = None
+
+BREAK = u"--"
+NEW_REQUIRED_PSEUDOHEADERS = [u"Version"]
+NEW_OPTIONAL_PSEUDOHEADERS = [u"Reporter", u"Assign", u"Depend", u"Severity",
+ u"Status", u"Tag", u"Target"]
+CONTROL_COMMENT = u"#"
+ALLOWED_COMMANDS = [u"assign", u"comment", u"commit", u"depend", u"help",
+ u"list", u"merge", u"new", u"open", u"severity", u"show",
+ u"status", u"subscribe", u"tag", u"target"]
+
+AUTOCOMMIT = True
+
+libbe.encoding.ENCODING = u"utf-8" # force default encoding
+ENCODING = libbe.encoding.get_encoding()
+
+class InvalidEmail (ValueError):
+ def __init__(self, msg, message):
+ ValueError.__init__(self, message)
+ self.msg = msg
+ def response(self):
+ header = self.msg.response_header
+ body = [u"Error processing email:\n",
+ self.response_body(), u""]
+ response_generator = \
+ send_pgp_mime.PGPMimeMessageFactory(u"\n".join(body))
+ response = MIMEMultipart()
+ response.attach(response_generator.plain())
+ response.attach(self.msg.msg)
+ ret = send_pgp_mime.attach_root(header, response)
+ return ret
+ def response_body(self):
+ err_text = [unicode(self)]
+ return u"\n".join(err_text)
+
+class InvalidSubject (InvalidEmail):
+ def __init__(self, msg, message=None):
+ if message == None:
+ message = u"Invalid subject"
+ InvalidEmail.__init__(self, msg, message)
+ def response_body(self):
+ err_text = u"\n".join([unicode(self), u"",
+ u"full subject was:",
+ self.msg.subject()])
+ return err_text
+
+class InvalidPseudoHeader (InvalidEmail):
+ def response_body(self):
+ err_text = [u"Invalid pseudo-header:\n",
+ unicode(self)]
+ return u"\n".join(err_text)
+
+class InvalidCommand (InvalidEmail):
+ def __init__(self, msg, command, message=None):
+ bigmessage = u"Invalid execution command '%s'" % command
+ if message != None:
+ bigmessage += u"\n%s" % message
+ InvalidEmail.__init__(self, msg, bigmessage)
+ self.command = command
+
+class InvalidOption (InvalidCommand):
+ def __init__(self, msg, option, message=None):
+ bigmessage = u"Invalid option '%s'" % (option)
+ if message != None:
+ bigmessage += u"\n%s" % message
+ InvalidCommand.__init__(self, msg, info, command, bigmessage)
+ self.option = option
+
+class NotificationFailed (Exception):
+ def __init__(self, msg):
+ bigmessage = "Notification failed: %s" % msg
+ Exception.__init__(self, bigmessage)
+ self.short_msg = msg
+
+class ID (object):
+ """
+ Sometimes you want to reference the output of a command that
+ hasn't been executed yet. ID is there for situations like
+ > a = Command(msg, "new", ["create a bug"])
+ > b = Command(msg, "comment", [ID(a), "and comment on it"])
+ """
+ def __init__(self, command):
+ self.command = command
+ def extract_id(self):
+ if hasattr(self, "cached_id"):
+ return self.cached_id
+ assert self.command.ret == 0, self.command.ret
+ if self.command.command == u"new":
+ regexp = re.compile(u"Created bug with ID (.*)")
+ else:
+ raise NotImplementedError, self.command.command
+ match = regexp.match(self.command.stdout)
+ assert len(match.groups()) == 1, str(match.groups())
+ self.cached_id = match.group(1)
+ return self.cached_id
+ def __str__(self):
+ if self.command.ret != 0:
+ return "<id for %s>" % repr(self.command)
+ return "<id %s>" % self.extract_id()
+
+class Command (object):
+ """
+ A becommands command wrapper.
+ Doesn't validate input, so do that before initializing.
+
+ Initialize with
+ Command(msg, command, args=None, stdin=None)
+ where
+ msg: the Message instance prompting this command
+ command: name of becommand to execute, e.g. "new"
+ args: list of arguments to pass to the command
+ stdin: if non-null, a string to pipe into the command's stdin
+ """
+ def __init__(self, msg, command, args=None, stdin=None):
+ self.msg = msg
+ self.command = command
+ if args == None:
+ self.args = []
+ else:
+ self.args = args
+ self.stdin = stdin
+ self.ret = None
+ self.stdout = None
+ self.stderr = None
+ self.err = None
+ def __str__(self):
+ return "<command: %s %s>" % (self.command, " ".join([str(s) for s in self.args]))
+ def normalize_args(self):
+ """
+ Expand any ID placeholders in self.args.
+ """
+ for i,arg in enumerate(self.args):
+ if isinstance(arg, ID):
+ self.args[i] = arg.extract_id()
+ def run(self):
+ """
+ Attempt to execute the command whose info is given in the dictionary
+ info. Returns the exit code, stdout, and stderr produced by the
+ command.
+ """
+ if self.command in [None, u""]: # don't accept blank commands
+ raise InvalidCommand(self.msg, self, "Blank")
+ elif self.command not in ALLOWED_COMMANDS:
+ raise InvalidCommand(self.msg, self, "Not allowed")
+ assert self.ret == None, u"running %s twice!" % unicode(self)
+ self.normalize_args()
+ # set stdin and catch stdout and stderr
+ if self.stdin != None:
+ new_stdin = StringIO.StringIO(self.stdin)
+ orig___stdin = sys.__stdin__
+ sys.__stdin__ = new_stdin
+ orig_stdin = sys.stdin
+ sys.stdin = new_stdin
+ new_stdout = codecs.getwriter(ENCODING)(StringIO.StringIO())
+ new_stderr = codecs.getwriter(ENCODING)(StringIO.StringIO())
+ orig_stdout = sys.stdout
+ orig_stderr = sys.stderr
+ sys.stdout = new_stdout
+ sys.stderr = new_stderr
+ # run the command
+ os.chdir(BE_DIR)
+ try:
+ self.ret = libbe.cmdutil.execute(self.command, self.args,
+ manipulate_encodings=False)
+ except libbe.cmdutil.GetHelp:
+ print libbe.cmdutil.help(command)
+ except libbe.cmdutil.GetCompletions:
+ self.err = InvalidOption(self.msg, self.command, u"--complete")
+ except libbe.cmdutil.UsageError, e:
+ self.err = InvalidCommand(self.msg, self,
+ "%s\n%s" % (type(e), unicode(e)))
+ except libbe.cmdutil.UserError, e:
+ self.err = InvalidCommand(self.msg, self,
+ "%s\n%s" % (type(e), unicode(e)))
+ # restore stdin, stdout, and stderr
+ if self.stdin != None:
+ sys.__stdin__ = new_stdin
+ sys.__stdin__ = orig___stdin
+ sys.stdin = orig_stdin
+ sys.stdout.flush()
+ sys.stderr.flush()
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+ self.stdout = codecs.decode(new_stdout.getvalue(), ENCODING)
+ self.stderr = codecs.decode(new_stderr.getvalue(), ENCODING)
+ if self.err != None:
+ raise self.err
+ return (self.ret, self.stdout, self.stderr)
+ def response_msg(self):
+ response_body = [u"Results of running: (exit code %d)" % self.ret,
+ u" %s %s" % (self.command, u" ".join(self.args))]
+ if self.stdout != None and len(self.stdout) > 0:
+ response_body.extend([u"", u"stdout:", u"", self.stdout])
+ if self.stderr != None and len(self.stderr) > 0:
+ response_body.extend([u"", u"stderr:", u"", self.stderr])
+ response_body.append(u"") # trailing endline
+ response_generator = \
+ send_pgp_mime.PGPMimeMessageFactory(u"\n".join(response_body))
+ return response_generator.plain()
+
+class DiffTree (libbe.diff.DiffTree):
+ """
+ In order to avoid tons of tiny MIMEText attachments, bug-level
+ nodes set .add_child_text=True (in .join()), which is propogated
+ on to their descendents. Instead of creating their own
+ attachement, each of these descendents appends his data_part to
+ the end of the bug-level MIMEText attachment.
+
+ For the example tree in the libbe.diff.Diff unittests:
+ bugdir
+ bugdir/settings
+ bugdir/bugs
+ bugdir/bugs/new
+ bugdir/bugs/new/c <- sets .add_child_text
+ bugdir/bugs/rem
+ bugdir/bugs/rem/b <- sets .add_child_text
+ bugdir/bugs/mod
+ bugdir/bugs/mod/a <- sets .add_child_text
+ bugdir/bugs/mod/a/settings
+ bugdir/bugs/mod/a/comments
+ bugdir/bugs/mod/a/comments/new
+ bugdir/bugs/mod/a/comments/new/acom
+ bugdir/bugs/mod/a/comments/rem
+ bugdir/bugs/mod/a/comments/mod
+ """
+ def report_or_none(self):
+ report = self.report()
+ payload = report.get_payload()
+ if payload == None or len(payload) == 0:
+ return None
+ return report
+ def report_string(self):
+ report = self.report_or_none()
+ if report == None:
+ return "No changes"
+ else:
+ return send_pgp_mime.flatten(self.report(), to_unicode=True)
+ def make_root(self):
+ return MIMEMultipart()
+ def join(self, root, parent, data_part):
+ if hasattr(parent, "attach_child_text"):
+ self.attach_child_text = True
+ if data_part != None:
+ send_pgp_mime.append_text(parent.data_mime_part, u"\n\n%s" % (data_part))
+ self.data_mime_part = parent.data_mime_part
+ else:
+ self.data_mime_part = None
+ if data_part != None:
+ self.data_mime_part = send_pgp_mime.encodedMIMEText(data_part)
+ if parent != None and parent.name in [u"new", u"rem", u"mod"]:
+ self.attach_child_text = True
+ if data_part == None: # make blank data_mime_part for children's appends
+ self.data_mime_part = send_pgp_mime.encodedMIMEText(u"")
+ if self.data_mime_part != None:
+ self.data_mime_part[u"Content-Description"] = self.name
+ root.attach(self.data_mime_part)
+ def data_part(self, depth, indent=False):
+ return libbe.diff.DiffTree.data_part(self, depth, indent=indent)
+
+class Diff (libbe.diff.Diff):
+ def bug_add_string(self, bug):
+ return bug.string(show_comments=True)
+ def _comment_summary_string(self, comment):
+ return comment.string()
+ def comment_add_string(self, comment):
+ return self._comment_summary_string(comment)
+ def comment_rem_string(self, comment):
+ return self._comment_summary_string(comment)
+
+class Message (object):
+ def __init__(self, email_text=None, disable_parsing=False):
+ if disable_parsing == False:
+ self.text = email_text
+ p=email.Parser.Parser()
+ self.msg=p.parsestr(self.text)
+ if LOGFILE != None:
+ LOGFILE.write(u"handling %s\n" % self.author_addr())
+ LOGFILE.write(u"\n%s\n\n" % self.text)
+ def author_tuple(self):
+ """
+ Extract and normalize the sender's email address. Returns a
+ (name, email) tuple.
+ """
+ if not hasattr(self, "author_tuple_cache"):
+ self.author_tuple_cache = \
+ send_pgp_mime.source_email(self.msg, return_realname=True)
+ return self.author_tuple_cache
+ def author_addr(self):
+ return email.utils.formataddr(self.author_tuple())
+ def author_name(self):
+ return self.author_tuple()[0]
+ def author_email(self):
+ return self.author_tuple()[1]
+ def default_msg_attribute_access(self, attr_name, default=None):
+ if attr_name in self.msg:
+ return self.msg[attr_name]
+ return default
+ def message_id(self, default=None):
+ return self.default_msg_attribute_access("message-id", default=default)
+ def subject(self):
+ if "subject" not in self.msg:
+ raise InvalidSubject(self, u"Email must contain a subject")
+ return self.msg["subject"]
+ def _split_subject(self):
+ """
+ Returns (tag, subject), with missing values replaced by None.
+ """
+ if hasattr(self, "_split_subject_cache"):
+ return self._split_subject_cache
+ args = self.subject().split(u"]",1)
+ if len(args) < 1:
+ self._split_subject_cache = (None, None)
+ elif len(args) < 2:
+ self._split_subject_cache = (args[0]+u"]", None)
+ else:
+ self._split_subject_cache = (args[0]+u"]", args[1].strip())
+ return self._split_subject_cache
+ def _subject_tag_type(self):
+ """
+ Parse subject tag, return (type, value), where type is one of
+ None, "new", "comment", or "control"; and value is None except
+ in the case of "comment", in which case it's the bug
+ ID/shortname.
+ """
+ tag,subject = self._split_subject()
+ type = None
+ value = None
+ if tag == SUBJECT_TAG_NEW:
+ type = u"new"
+ elif tag == SUBJECT_TAG_CONTROL:
+ type = u"control"
+ else:
+ match = SUBJECT_TAG_COMMENT.match(tag)
+ if len(match.groups()) == 1:
+ type = u"comment"
+ value = match.group(1)
+ return (type, value)
+ def validate_subject(self):
+ """
+ Validate the subject line.
+ """
+ tag,subject = self._split_subject()
+ if not tag.startswith(SUBJECT_TAG_START):
+ raise InvalidSubject(
+ self, u"Subject must start with '%s'" % SUBJECT_TAG_START)
+ tag_type,value = self._subject_tag_type()
+ if tag_type == None:
+ raise InvalidSubject(self, u"Invalid tag '%s'" % tag)
+ elif tag_type == u"new" and len(subject) == 0:
+ raise InvalidSubject(self, u"Cannot create a bug with blank title")
+ elif tag_type == u"comment" and len(value) == 0:
+ raise InvalidSubject(self, u"Must specify a bug ID to comment")
+ def _get_bodies_and_mime_types(self):
+ """
+ Traverse the email message returning (body, mime_type) for
+ each non-mulitpart portion of the message.
+ """
+ for part in self.msg.walk():
+ if part.is_multipart():
+ continue
+ body,mime_type=(part.get_payload(decode=1),part.get_content_type())
+ yield (body, mime_type)
+ def _parse_body_pseudoheaders(self, body, required, optional,
+ dictionary=None):
+ """
+ Grab any pseudo-headers from the beginning of body. Raise
+ InvalidPseudoHeader on errors. Returns the body text after
+ the pseudo-header and a dictionary of set options. If you
+ like, you can initialize the dictionary with some defaults
+ and pass your initialized dict in as dictionary.
+ """
+ if dictionary == None:
+ dictionary = {}
+ body_lines = body.splitlines()
+ all = required+optional
+ for i,line in enumerate(body_lines):
+ line = line.strip()
+ if len(line) == 0:
+ break
+ if ":" not in line:
+ raise InvalidPseudoheader(self, line)
+ key,value = line.split(":", 1)
+ value = value.strip()
+ if key not in all:
+ raise InvalidPseudoHeader(self, key)
+ if len(value) == 0:
+ raise InvalidEmail(
+ self, u"Blank value for: %s" % key)
+ dictionary[key] = value
+ missing = []
+ for key in required:
+ if key not in dictionary:
+ missing.append(key)
+ if len(missing) > 0:
+ raise InvalidPseudoHeader(self,
+ u"Missing required pseudo-headers:\n%s"
+ % u", ".join(missing))
+ remaining_body = u"\n".join(body_lines[i:]).strip()
+ return (remaining_body, dictionary)
+ def _strip_footer(self, body):
+ body_lines = body.splitlines()
+ for i,line in enumerate(body_lines):
+ if line.startswith(BREAK):
+ break
+ return u"\n".join(body_lines[:i]).strip()
+ def parse(self):
+ """
+ Parse the commands given in the email. Raises assorted
+ subclasses of InvalidEmail in the case of invalid messages,
+ otherwise returns a list of suggested commands to run.
+ """
+ self.validate_subject()
+ tag_type,value = self._subject_tag_type()
+ commands = []
+ if tag_type == u"new":
+ command = u"new"
+ tag,subject = self._split_subject()
+ summary = subject
+ options = {u"Reporter": self.author_addr()}
+ body,mime_type = list(self._get_bodies_and_mime_types())[0]
+ comment_body,options = \
+ self._parse_body_pseudoheaders(body,
+ NEW_REQUIRED_PSEUDOHEADERS,
+ NEW_OPTIONAL_PSEUDOHEADERS,
+ options)
+ args = [u"--reporter", options[u"Reporter"]]
+ args.append(summary)
+ commands.append(Command(self, command, args))
+ comment_body = self._strip_footer(comment_body)
+ id = ID(commands[0])
+ if len(comment_body) > 0:
+ command = u"comment"
+ comment = u"Version: %s\n\n"%options[u"Version"] + comment_body
+ args = [u"--author", self.author_addr(),
+ u"--alt-id", self.message_id(),
+ u"--content-type", mime_type]
+ args.append(id)
+ args.append(u"-")
+ commands.append(Command(self, u"comment", args, stdin=comment))
+ for key,value in options.items():
+ if key in [u"Version", u"Reporter"]:
+ continue # we've already handled this option
+ command = key.lower()
+ args = [id, value]
+ commands.append(Command(self, command, args))
+ elif tag_type == u"comment":
+ command = u"comment"
+ bug_id = value
+ author = self.author_addr()
+ alt_id = self.message_id()
+ body,mime_type = list(self._get_bodies_and_mime_types())[0]
+ if mime_type == "text/plain":
+ body = self._strip_footer(body)
+ content_type = mime_type
+ args = [u"--author", author, u"--alt-id", alt_id,
+ u"--content-type", content_type, bug_id, u"-"]
+ commands.append(Command(self, command, args, stdin=body))
+ elif tag_type == u"control":
+ body,mime_type = list(self._get_bodies_and_mime_types())[0]
+ for line in body.splitlines():
+ line = line.strip()
+ if line.startswith(CONTROL_COMMENT) or len(line) == 0:
+ continue
+ if line.startswith(BREAK):
+ break
+ fields = shlex.split(line)
+ command,args = (fields[0], fields[1:])
+ commands.append(Command(self, command, args))
+ if len(commands) == 0:
+ raise InvalidEmail(self, u"No commands in control email.")
+ else:
+ raise Exception, u"Unrecognized tag type '%s'" % tag_type
+ return commands
+ def run(self):
+ self._begin_response()
+ commands = self.parse()
+ try:
+ for command in commands:
+ command.run()
+ self._add_response(command.response_msg())
+ finally:
+ if AUTOCOMMIT == True:
+ tag,subject = self._split_subject()
+ self.commit_command = Command(self, "commit", [subject])
+ self.commit_command.run()
+ if LOGFILE != None:
+ LOGFILE.write(u"Autocommit:\n%s\n\n" %
+ send_pgp_mime.flatten(self.commit_command.response_msg(),
+ to_unicode=True))
+ def _begin_response(self):
+ tag,subject = self._split_subject()
+ response_header = [u"From: %s" % THIS_ADDRESS,
+ u"To: %s" % self.author_addr(),
+ u"Date: %s" % libbe.utility.time_to_str(time.time()),
+ u"Subject: %s Re: %s"%(SUBJECT_TAG_RESPONSE,subject)
+ ]
+ if self.message_id() != None:
+ response_header.append(u"In-reply-to: %s" % self.message_id())
+ self.response_header = \
+ send_pgp_mime.header_from_text(text=u"\n".join(response_header))
+ self._response_messages = []
+ def _add_response(self, response_message):
+ self._response_messages.append(response_message)
+ def response_email(self):
+ assert len(self._response_messages) > 0
+ if len(self._response_messages) == 1:
+ response_body = self._response_messages[0]
+ else:
+ response_body = MIMEMultipart()
+ for message in self._response_messages:
+ response_body.attach(message)
+ return send_pgp_mime.attach_root(self.response_header, response_body)
+ def subscriber_emails(self, previous_revision=None):
+ if previous_revision == None:
+ if AUTOCOMMIT != True: # no way to tell what's changed
+ raise NotificationFailed("Autocommit dissabled")
+ if len(self._response_messages) == 0:
+ raise NotificationFailed("Initial email failed.")
+ if self.commit_command.ret != 0:
+ # commit failed. Error already logged.
+ raise NotificationFailed("Commit failed")
+
+ # read only bugdir.
+ bd = libbe.bugdir.BugDir(from_disk=True,
+ manipulate_encodings=False)
+ if bd.rcs.versioned == False: # no way to tell what's changed
+ raise NotificationFailed("Not versioned")
+
+ subscribers = subscribe.get_bugdir_subscribers(bd, THIS_SERVER)
+ if len(subscribers) == 0:
+ return []
+
+ before_bd, after_bd = self._get_before_and_after_bugdirs(bd, previous_revision)
+ diff = Diff(before_bd, after_bd)
+ diff_tree = diff.report_tree(diff_tree=DiffTree)
+ bug_index = {}
+ for child in diff_tree.child_by_path("/bugs/new"):
+ bug_index[child.name] = ("added", child)
+ for child in diff_tree.child_by_path("/bugs/mod"):
+ bug_index[child.name] = ("modified", child)
+ for child in diff_tree.child_by_path("/bugs/rem"):
+ bug_index[child.name] = ("removed", child)
+ header = self._subscriber_header(bd, previous_revision)
+
+ emails = []
+ for subscriber,subscriptions in subscribers.items():
+ header.replace_header("to", subscriber)
+ parts = []
+ for id,types in subscriptions.items():
+ if id == "DIR":
+ if subscribe.BUGDIR_TYPE_ALL in types:
+ parts.append(diff_tree.report_or_none())
+ break
+ if subscribe.BUGDIR_TYPE_NEW in types:
+ new = diff_tree.child_by_path("/bugs/new")
+ parts.append(new.report_or_none())
+ continue # move on to next id
+ assert types == [subscribe.BUG_TYPE_ALL], types
+ type,bug_root = bug_index[id]
+ parts.append(bug_root.report_or_none())
+ parts = [p for p in parts if p != None]
+ if len(parts) == 0:
+ continue # no email to this subscriber
+ elif len(parts) == 1:
+ root = parts[0]
+ else: # join subscription parts into a single body
+ root = MIMEMultipart()
+ root[u"Content-Description"] = u"Multiple subscription trees."
+ for part in parts:
+ root.attach(part)
+ emails.append(send_pgp_mime.attach_root(header, root))
+ if LOGFILE != None:
+ LOGFILE.write(u"Preparing to notify %s of changes\n" % subscriber)
+ return emails
+ def _get_before_and_after_bugdirs(self, bd, previous_revision=None):
+ if previous_revision == None:
+ commit_msg = self.commit_command.stdout
+ assert commit_msg.startswith("Committed "), commit_msg
+ after_revision = commit_msg[len("Committed "):]
+ before_revision = bd.rcs.revision_id(-2)
+ else:
+ before_revision = previous_revision
+ if before_revision == None:
+ # this commit was the initial commit
+ before_bd = libbe.bugdir.BugDir(from_disk=False,
+ manipulate_encodings=False)
+ else:
+ before_bd = bd.duplicate_bugdir(before_revision)
+ #after_bd = bd.duplicate_bugdir(after_revision)
+ after_bd = bd # assume no changes since commit a few cycles ago
+ return (before_bd, after_bd)
+ def _subscriber_header(self, bd, previous_revision=None):
+ root_dir = os.path.basename(bd.root)
+ if previous_revision == None:
+ subject = "Changes to %s on %s by %s" \
+ % (root_dir, THIS_SERVER, self.author_addr())
+ else:
+ subject = "Changes to %s on %s since revision %s" \
+ % (root_dir, THIS_SERVER, previous_revision)
+ header = [u"From: %s" % THIS_ADDRESS,
+ u"To: %s" % u"DUMMY-AUTHOR",
+ u"Date: %s" % libbe.utility.time_to_str(time.time()),
+ u"Subject: %s Re: %s" % (SUBJECT_TAG_RESPONSE, subject)
+ ]
+ return send_pgp_mime.header_from_text(text=u"\n".join(header))
+
+def generate_global_tags(tag_base=u"be-bug"):
+ """
+ Generate a series of tags from a base tag string.
+ """
+ global SUBJECT_TAG_BASE, SUBJECT_TAG_START, SUBJECT_TAG_RESPONSE, \
+ SUBJECT_TAG_NEW, SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL
+ SUBJECT_TAG_BASE = tag_base
+ SUBJECT_TAG_START = u"[%s" % tag_base
+ SUBJECT_TAG_RESPONSE = u"[%s]" % tag_base
+ SUBJECT_TAG_NEW = u"[%s:submit]" % tag_base
+ SUBJECT_TAG_COMMENT = re.compile(u"\[%s:([\-0-9a-z]*)]" % tag_base)
+ SUBJECT_TAG_CONTROL = SUBJECT_TAG_RESPONSE
+
+def open_logfile(logpath=None):
+ """
+ If logpath=None, default to global LOGPATH.
+ Special logpath strings:
+ "-" set LOGFILE to sys.stderr
+ "none" disable logging
+ Relative logpaths are expanded relative to _THIS_DIR
+ """
+ global LOGPATH, LOGFILE
+ if logpath != None:
+ if logpath == u"-":
+ LOGPATH = u"stderr"
+ LOGFILE = sys.stderr
+ elif logpath == u"none":
+ LOGPATH = u"none"
+ LOGFILE = None
+ elif os.path.isabs(logpath):
+ LOGPATH = logpath
+ else:
+ LOGPATH = os.path.join(_THIS_DIR, logpath)
+ if LOGFILE == None and LOGPATH != u"none":
+ LOGFILE = codecs.open(LOGPATH, u"a+", ENCODING)
+ LOGFILE.write(u"Default encoding: %s\n" % ENCODING)
+
+def close_logfile():
+ if LOGFILE != None and LOGPATH not in [u"stderr", u"none"]:
+ LOGFILE.close()
+
+def test():
+ unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+ suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
+ result = unittest.TextTestRunner(verbosity=2).run(suite)
+ num_errors = len(result.errors)
+ num_failures = len(result.failures)
+ num_bad = num_errors + num_failures
+ return num_bad
+
+def main(args):
+ from optparse import OptionParser
+ global AUTOCOMMIT, BE_DIR
+
+ usage="be-handle-mail [options]\n\n%s" % (__doc__)
+ parser = OptionParser(usage=usage)
+ parser.add_option('-b', '--be-dir', dest='be_dir', default=BE_DIR,
+ metavar="DIR",
+ help='Select the BE directory to serve (%default).')
+ parser.add_option('-t', '--tag-base', dest='tag_base',
+ default=SUBJECT_TAG_BASE, metavar="TAG",
+ help='Set the subject tag base (%default).')
+ parser.add_option('-o', '--output', dest='output', action='store_true',
+ help="Don't mail the generated message, print it to stdout instead. Useful for testing be-handle-mail functionality without the whole mail transfer agent and procmail setup.")
+ parser.add_option('-l', '--logfile', dest='logfile', metavar='LOGFILE',
+ help='Set the logfile to LOGFILE. Relative paths are relative to the location of this be-handle-mail file (%s). The special value of "-" directs the log output to stderr, and "none" disables logging.' % _THIS_DIR)
+ parser.add_option('-a', '--disable-autocommit', dest='autocommit',
+ default=True, action='store_false',
+ help='Disable the autocommit after parsing the email.')
+ parser.add_option('-s', '--disable-subscribers', dest='subscribers',
+ default=True, action='store_false',
+ help='Disable subscriber notification emails.')
+ parser.add_option('--notify-since', dest='notify_since', metavar='REVISION',
+ help='Notify subscribers of all changes since REVISION. When this option is set, no input email parsing is done.')
+ parser.add_option('--test', dest='test', action='store_true',
+ help='Run internal unit-tests and exit.')
+
+ pargs = args
+ options,args = parser.parse_args(args[1:])
+
+ if options.test == True:
+ num_bad = test()
+ if num_bad > 126:
+ num_bad = 1
+ sys.exit(num_bad)
+
+ BE_DIR = options.be_dir
+ AUTOCOMMIT = options.autocommit
+
+ if options.notify_since == None:
+ msg_text = sys.stdin.read()
+
+ libbe.encoding.set_IO_stream_encodings(ENCODING) # _after_ reading message
+ open_logfile(options.logfile)
+ generate_global_tags(options.tag_base)
+
+ if options.notify_since != None:
+ if options.subscribers == True:
+ if LOGFILE != None:
+ LOGFILE.write(u"Checking for subscribers to notify since revision %s\n"
+ % options.notify_since)
+ try:
+ m = Message(disable_parsing=True)
+ emails = m.subscriber_emails(options.notify_since)
+ except NotificationFailed, e:
+ if LOGFILE != None:
+ LOGFILE.write(unicode(e) + u"\n")
+ else:
+ for msg in emails:
+ if options.output == True:
+ print send_pgp_mime.flatten(msg, to_unicode=True)
+ else:
+ send_pgp_mime.mail(msg, send_pgp_mime.sendmail)
+ close_logfile()
+ sys.exit(0)
+
+ if len(msg_text.strip()) == 0: # blank email!?
+ if LOGFILE != None:
+ LOGFILE.write(u"Blank email!\n")
+ close_logfile()
+ sys.exit(1)
+ try:
+ m = Message(msg_text)
+ m.run()
+ except InvalidEmail, e:
+ response = e.response()
+ except Exception, e:
+ if LOGFILE != None:
+ LOGFILE.write(u"Uncaught exception:\n%s\n" % (e,))
+ traceback.print_tb(sys.exc_traceback, file=LOGFILE)
+ close_logfile()
+ sys.exit(1)
+ else:
+ response = m.response_email()
+ if options.output == True:
+ print send_pgp_mime.flatten(response, to_unicode=True)
+ else:
+ if LOGFILE != None:
+ LOGFILE.write(u"sending response to %s\n" % m.author_addr())
+ LOGFILE.write(u"\n%s\n\n" % send_pgp_mime.flatten(response,
+ to_unicode=True))
+ send_pgp_mime.mail(response, send_pgp_mime.sendmail)
+ if options.subscribers == True:
+ if LOGFILE != None:
+ LOGFILE.write(u"Checking for subscribers\n")
+ try:
+ emails = m.subscriber_emails()
+ except NotificationFailed, e:
+ if LOGFILE != None:
+ LOGFILE.write(unicode(e) + u"\n")
+ else:
+ for msg in emails:
+ if options.output == True:
+ print send_pgp_mime.flatten(msg, to_unicode=True)
+ else:
+ send_pgp_mime.mail(msg, send_pgp_mime.sendmail)
+
+ close_logfile()
+
+
+class GenerateGlobalTagsTestCase (unittest.TestCase):
+ def setUp(self):
+ super(GenerateGlobalTagsTestCase, self).setUp()
+ self.save_global_tags()
+ def tearDown(self):
+ self.restore_global_tags()
+ super(GenerateGlobalTagsTestCase, self).tearDown()
+ def save_global_tags(self):
+ self.saved_globals = [SUBJECT_TAG_BASE, SUBJECT_TAG_START,
+ SUBJECT_TAG_RESPONSE, SUBJECT_TAG_NEW,
+ SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL]
+ def restore_global_tags(self):
+ global SUBJECT_TAG_BASE, SUBJECT_TAG_START, SUBJECT_TAG_RESPONSE, \
+ SUBJECT_TAG_NEW, SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL
+ SUBJECT_TAG_BASE, SUBJECT_TAG_START, SUBJECT_TAG_RESPONSE, \
+ SUBJECT_TAG_NEW, SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL = \
+ self.saved_globals
+ def test_restore_global_tags(self):
+ "Test global tag restoration by teardown function."
+ global SUBJECT_TAG_BASE
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u"be-bug")
+ SUBJECT_TAG_BASE = "projectX-bug"
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u"projectX-bug")
+ self.restore_global_tags()
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u"be-bug")
+ def test_subject_tag_base(self):
+ "Should set SUBJECT_TAG_BASE global correctly"
+ generate_global_tags(u"projectX-bug")
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u"projectX-bug")
+ def test_subject_tag_start(self):
+ "Should set SUBJECT_TAG_START global correctly"
+ generate_global_tags(u"projectX-bug")
+ self.failUnlessEqual(SUBJECT_TAG_START, u"[projectX-bug")
+ def test_subject_tag_response(self):
+ "Should set SUBJECT_TAG_RESPONSE global correctly"
+ generate_global_tags(u"projectX-bug")
+ self.failUnlessEqual(SUBJECT_TAG_RESPONSE, u"[projectX-bug]")
+ def test_subject_tag_new(self):
+ "Should set SUBJECT_TAG_NEW global correctly"
+ generate_global_tags(u"projectX-bug")
+ self.failUnlessEqual(SUBJECT_TAG_NEW, u"[projectX-bug:submit]")
+ def test_subject_tag_control(self):
+ "Should set SUBJECT_TAG_CONTROL global correctly"
+ generate_global_tags(u"projectX-bug")
+ self.failUnlessEqual(SUBJECT_TAG_CONTROL, u"[projectX-bug]")
+ def test_subject_tag_comment(self):
+ "Should set SUBJECT_TAG_COMMENT global correctly"
+ generate_global_tags(u"projectX-bug")
+ m = SUBJECT_TAG_COMMENT.match("[projectX-bug:xyz-123]")
+ self.failUnlessEqual(len(m.groups()), 1)
+ self.failUnlessEqual(m.group(1), u"xyz-123")
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/interfaces/email/interactive/becommands b/interfaces/email/interactive/becommands
new file mode 120000
index 0000000..8af773c
--- /dev/null
+++ b/interfaces/email/interactive/becommands
@@ -0,0 +1 @@
+../../../becommands \ No newline at end of file
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe b/interfaces/email/interactive/examples/blank
index e69de29..e69de29 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe
+++ b/interfaces/email/interactive/examples/blank
diff --git a/interfaces/email/interactive/examples/comment b/interfaces/email/interactive/examples/comment
new file mode 100644
index 0000000..f22e4b2
--- /dev/null
+++ b/interfaces/email/interactive/examples/comment
@@ -0,0 +1,11 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <xyz@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug:a1d] Subject ignored
+
+We sure do.
+--
+Goofy tagline ignored
diff --git a/interfaces/email/interactive/examples/failing_multiples b/interfaces/email/interactive/examples/failing_multiples
new file mode 100644
index 0000000..cf50211
--- /dev/null
+++ b/interfaces/email/interactive/examples/failing_multiples
@@ -0,0 +1,16 @@
+From jdoe@example.com Fri Apr 18 12:00:00 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] Commit message...
+
+new "test bug"
+new "test bug 2"
+failing-command
+new "test bug 3"
+
+--
+This message fails partway through, but the partial changes should be
+recorded in a commit...
diff --git a/interfaces/email/interactive/examples/invalid_command b/interfaces/email/interactive/examples/invalid_command
new file mode 100644
index 0000000..f2963c7
--- /dev/null
+++ b/interfaces/email/interactive/examples/invalid_command
@@ -0,0 +1,11 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug]
+
+close
+--
+Close is currently disabled for the email interface.
diff --git a/interfaces/email/interactive/examples/invalid_subject b/interfaces/email/interactive/examples/invalid_subject
new file mode 100644
index 0000000..1e2eb88
--- /dev/null
+++ b/interfaces/email/interactive/examples/invalid_subject
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: Spam!
+
+This should elicit an "invalid subject" response email.
diff --git a/interfaces/email/interactive/examples/list b/interfaces/email/interactive/examples/list
new file mode 100644
index 0000000..acba424
--- /dev/null
+++ b/interfaces/email/interactive/examples/list
@@ -0,0 +1,11 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] Subject ignored
+
+list --status all
+--
+Dummy content
diff --git a/interfaces/email/interactive/examples/missing_command b/interfaces/email/interactive/examples/missing_command
new file mode 100644
index 0000000..bb390fc
--- /dev/null
+++ b/interfaces/email/interactive/examples/missing_command
@@ -0,0 +1,11 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] Subject ignored
+
+abcde
+--
+This should elicit a "invalid command 'abcde'" response email.
diff --git a/interfaces/email/interactive/examples/multiple_commands b/interfaces/email/interactive/examples/multiple_commands
new file mode 100644
index 0000000..41ef730
--- /dev/null
+++ b/interfaces/email/interactive/examples/multiple_commands
@@ -0,0 +1,14 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] Subject ignored
+
+help
+list --status=all
+list --status=fixed
+show --xml 361
+--
+Goofy tagline ignored.
diff --git a/interfaces/email/interactive/examples/new b/interfaces/email/interactive/examples/new
new file mode 100644
index 0000000..c64db93
--- /dev/null
+++ b/interfaces/email/interactive/examples/new
@@ -0,0 +1,19 @@
+From jdoe@example.com Fri Apr 18 12:00:00 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug:submit] Need tests for the email interface.
+
+Version: XYZ
+Reporter: Jane Doe
+Assign: Dick Tracy
+Depend: 00f
+Severity: critical
+Status: assigned
+Tag: topsecret
+Target: Law&Order
+
+--
+Goofy tagline not included, and no comment added.
diff --git a/interfaces/email/interactive/examples/new_with_comment b/interfaces/email/interactive/examples/new_with_comment
new file mode 100644
index 0000000..1077f0f
--- /dev/null
+++ b/interfaces/email/interactive/examples/new_with_comment
@@ -0,0 +1,13 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug:submit] Need tests for the email interface.
+
+Version: XYZ
+
+I think so anyway.
+--
+Goofy tagline not included.
diff --git a/interfaces/email/interactive/examples/show b/interfaces/email/interactive/examples/show
new file mode 100644
index 0000000..c5f8a4d
--- /dev/null
+++ b/interfaces/email/interactive/examples/show
@@ -0,0 +1,11 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] Subject ignored
+
+show --xml 361
+--
+Can we show a bug?
diff --git a/interfaces/email/interactive/examples/unicode b/interfaces/email/interactive/examples/unicode
new file mode 100644
index 0000000..f0e8001
--- /dev/null
+++ b/interfaces/email/interactive/examples/unicode
@@ -0,0 +1,11 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] Subject ignored
+
+show --xml f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a
+--
+Can we handle unicode output?
diff --git a/interfaces/email/interactive/libbe b/interfaces/email/interactive/libbe
new file mode 120000
index 0000000..7d18612
--- /dev/null
+++ b/interfaces/email/interactive/libbe
@@ -0,0 +1 @@
+../../../libbe \ No newline at end of file
diff --git a/interfaces/email/interactive/send_pgp_mime.py b/interfaces/email/interactive/send_pgp_mime.py
new file mode 100644
index 0000000..55767b3
--- /dev/null
+++ b/interfaces/email/interactive/send_pgp_mime.py
@@ -0,0 +1,611 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Python module and command line tool for sending pgp/mime email.
+
+Mostly uses subprocess to call gpg and a sendmail-compatible mailer.
+If you lack gpg, either don't use the encryption functions or adjust
+the pgp_* commands. You may need to adjust the sendmail command to
+point to whichever sendmail-compatible mailer you have on your system.
+"""
+
+from cStringIO import StringIO
+import os
+import re
+#import GnuPGInterface # Maybe should use this instead of subprocess
+import smtplib
+import subprocess
+import sys
+import tempfile
+import types
+
+try:
+ from email import Message
+ from email.mime.text import MIMEText
+ from email.mime.multipart import MIMEMultipart
+ from email.mime.application import MIMEApplication
+ from email.encoders import encode_7or8bit
+ from email.generator import Generator
+ from email.parser import Parser
+ from email.utils import getaddress
+except ImportError:
+ # adjust to old python 2.4
+ from email import Message
+ from email.MIMEText import MIMEText
+ from email.MIMEMultipart import MIMEMultipart
+ from email.MIMENonMultipart import MIMENonMultipart
+ from email.Encoders import encode_7or8bit
+ from email.Generator import Generator
+ from email.parser import Parser
+ from email.Utils import getaddresses
+
+ getaddress = getaddresses
+ class MIMEApplication (MIMENonMultipart):
+ def __init__(self, _data, _subtype, _encoder, **params):
+ MIMENonMultipart.__init__(self, 'application', _subtype, **params)
+ self.set_payload(_data)
+ _encoder(self)
+
+usage="""usage: %prog [options]
+
+Scriptable PGP MIME email using gpg.
+
+You can use gpg-agent for passphrase caching if your key requires a
+passphrase (it better!). Example usage would be to install gpg-agent,
+and then run
+ export GPG_TTY=`tty`
+ eval $(gpg-agent --daemon)
+in your shell before invoking this script. See gpg-agent(1) for more
+details. Alternatively, you can send your passphrase in on stdin
+ echo 'passphrase' | %prog [options]
+or use the --passphrase-file option
+ %prog [options] --passphrase-file FILE [more options]
+Both of these alternatives are much less secure than gpg-agent. You
+have been warned.
+"""
+
+verboseInvoke = False
+PGP_SIGN_AS = None
+PASSPHRASE = None
+
+# The following commands are adapted from my .mutt/pgp configuration
+#
+# Printf-like sequences:
+# %a The value of PGP_SIGN_AS.
+# %f Expands to the name of a file with text to be signed/encrypted.
+# %p Expands to the passphrase argument.
+# %R A string with some number (0 on up) of pgp_reciepient_arg
+# strings.
+# %r One key ID (e.g. recipient email address) to build a
+# pgp_reciepient_arg string.
+#
+# The above sequences can be used to optionally print a string if
+# their length is nonzero. For example, you may only want to pass the
+# -u/--local-user argument to gpg if PGP_SIGN_AS is defined. To
+# optionally print a string based upon one of the above sequences, the
+# following construct is used
+# %?<sequence_char>?<optional_string>?
+# where sequence_char is a character from the table above, and
+# optional_string is the string you would like printed if status_char
+# is nonzero. optional_string may contain other sequence as well as
+# normal text, but it may not contain any question marks.
+#
+# see http://codesorcery.net/old/mutt/mutt-gnupg-howto
+# http://www.mutt.org/doc/manual/manual-6.html#pgp_autosign
+# http://tldp.org/HOWTO/Mutt-GnuPG-PGP-HOWTO-8.html
+# for more details
+
+pgp_recipient_arg='-r "%r"'
+pgp_stdin_passphrase_arg='--passphrase-fd 0'
+pgp_sign_command='/usr/bin/gpg --no-verbose --quiet --batch %p --output - --detach-sign --armor --textmode %?a?-u "%a"? %f'
+pgp_encrypt_only_command='/usr/bin/gpg --no-verbose --quiet --batch --output - --encrypt --armor --textmode --always-trust --encrypt-to "%a" %R -- %f'
+pgp_encrypt_sign_command='/usr/bin/gpg --no-verbose --quiet --batch %p --output - --encrypt --sign %?a?-u "%a"? --armor --textmode --always-trust --encrypt-to "%a" %R -- %f'
+sendmail='/usr/sbin/sendmail -t'
+
+def mail(msg, sendmail=None):
+ """
+ Send an email Message instance on its merry way.
+
+ We can shell out to the user specified sendmail in case
+ the local host doesn't have an SMTP server set up
+ for easy smtplib usage.
+ """
+ if sendmail != None:
+ execute(sendmail, stdin=flatten(msg))
+ return None
+ s = smtplib.SMTP()
+ s.connect()
+ s.sendmail(from_addr=source_email(msg),
+ to_addrs=target_emails(msg),
+ msg=flatten(msg))
+ s.close()
+
+def header_from_text(text, encoding="us-ascii"):
+ """
+ Simple wrapper for instantiating an email.Message from text.
+ >>> header = header_from_text('\\n'.join(['From: me@big.edu','To: you@big.edu','Subject: testing']))
+ >>> print flatten(header)
+ From: me@big.edu
+ To: you@big.edu
+ Subject: testing
+ <BLANKLINE>
+ <BLANKLINE>
+ """
+ text = text.strip()
+ if type(text) == types.UnicodeType:
+ text = text.encode(encoding)
+ # assume StringType arguments are already encoded
+ p = Parser()
+ return p.parsestr(text, headersonly=True)
+
+def guess_encoding(text):
+ if type(text) == types.StringType:
+ encoding = "us-ascii"
+ elif type(text) == types.UnicodeType:
+ for encoding in ["us-ascii", "iso-8859-1", "utf-8"]:
+ try:
+ text.encode(encoding)
+ except UnicodeError:
+ pass
+ else:
+ break
+ assert encoding != None
+ return encoding
+
+def encodedMIMEText(body, encoding=None):
+ if encoding == None:
+ encoding = guess_encoding(body)
+ if encoding == "us-ascii":
+ return MIMEText(body)
+ else:
+ # Create the message ('plain' stands for Content-Type: text/plain)
+ return MIMEText(body.encode(encoding), 'plain', encoding)
+
+def append_text(text_part, new_text):
+ original_payload = text_part.get_payload(decode=True)
+ new_payload = u"%s%s" % (original_payload, new_text)
+ new_encoding = guess_encoding(new_payload)
+ text_part.set_payload(new_payload.encode(new_encoding), new_encoding)
+
+def attach_root(header, root_part):
+ """
+ Attach the email.Message root_part to the email.Message header
+ without generating a multi-part message.
+ """
+ for k,v in header.items():
+ root_part[k] = v
+ return root_part
+
+def execute(args, stdin=None, expect=(0,)):
+ """
+ Execute a command (allows us to drive gpg).
+ """
+ if verboseInvoke == True:
+ print >> sys.stderr, '$ '+args
+ try:
+ p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, close_fds=True)
+ except OSError, e:
+ strerror = '%s\nwhile executing %s' % (e.args[1], args)
+ raise Exception, strerror
+ output, error = p.communicate(input=stdin)
+ status = p.wait()
+ if verboseInvoke == True:
+ print >> sys.stderr, '(status: %d)\n%s%s' % (status, output, error)
+ if status not in expect:
+ strerror = '%s\nwhile executing %s\n%s\n%d' % (args[1], args, error, status)
+ raise Exception, strerror
+ return status, output, error
+
+def replace(template, format_char, replacement_text):
+ """
+ >>> replace('--textmode %?a?-u %a? %f', 'f', 'file.in')
+ '--textmode %?a?-u %a? file.in'
+ >>> replace('--textmode %?a?-u %a? %f', 'a', '0xHEXKEY')
+ '--textmode -u 0xHEXKEY %f'
+ >>> replace('--textmode %?a?-u %a? %f', 'a', '')
+ '--textmode %f'
+ """
+ if replacement_text == None:
+ replacement_text = ""
+ regexp = re.compile('%[?]'+format_char+'[?]([^?]*)[?]')
+ if len(replacement_text) > 0:
+ str = regexp.sub('\g<1>', template)
+ else:
+ str = regexp.sub('', template)
+ regexp = re.compile('%'+format_char)
+ str = regexp.sub(replacement_text, str)
+ return str
+
+def flatten(msg, to_unicode=False):
+ """
+ Produce flat text output from an email Message instance.
+ """
+ assert msg != None
+ fp = StringIO()
+ g = Generator(fp, mangle_from_=False)
+ g.flatten(msg)
+ text = fp.getvalue()
+ if to_unicode == True:
+ encoding = msg.get_content_charset() or "utf-8"
+ text = unicode(text, encoding=encoding)
+ return text
+
+def source_email(msg, return_realname=False):
+ """
+ Search the header of an email Message instance to find the
+ sender's email address.
+ """
+ froms = msg.get_all('from', [])
+ from_tuples = getaddresses(froms) # [(realname, email_address), ...]
+ assert len(from_tuples) == 1
+ if return_realname == True:
+ return from_tuples[0] # (realname, email_address)
+ return from_tuples[0][1] # email_address
+
+def target_emails(msg):
+ """
+ Search the header of an email Message instance to find a
+ list of recipient's email addresses.
+ """
+ tos = msg.get_all('to', [])
+ ccs = msg.get_all('cc', [])
+ bccs = msg.get_all('bcc', [])
+ resent_tos = msg.get_all('resent-to', [])
+ resent_ccs = msg.get_all('resent-cc', [])
+ resent_bccs = msg.get_all('resent-bcc', [])
+ all_recipients = getaddresses(tos + ccs + bccs + resent_tos
+ + resent_ccs + resent_bccs)
+ return [addr[1] for addr in all_recipients]
+
+class PGPMimeMessageFactory (object):
+ """
+ See http://www.ietf.org/rfc/rfc3156.txt for specification details.
+ >>> from_addr = "me@big.edu"
+ >>> to_addr = "you@you.edu"
+ >>> header = header_from_text('\\n'.join(['From: %s'%from_addr,'To: %s'%to_addr,'Subject: testing']))
+ >>> source_email(header) == from_addr
+ True
+ >>> target_emails(header) == [to_addr]
+ True
+ >>> m = EncryptedMessageFactory('check 1 2\\ncheck 1 2\\n')
+ >>> print flatten(m.clearBodyPart())
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: inline
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ >>> print flatten(m.plain())
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ >>> signed = m.sign(header)
+ >>> signed.set_boundary('boundsep')
+ >>> print flatten(signed).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ Content-Type: multipart/signed; protocol="application/pgp-signature";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: inline
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Description: signature
+ Content-Type: application/pgp-signature; name="signature.asc";
+ charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP SIGNATURE-----
+ ...
+ -----END PGP SIGNATURE-----
+ <BLANKLINE>
+ --boundsep--
+ >>> encrypted = m.encrypt(header)
+ >>> encrypted.set_boundary('boundsep')
+ >>> print flatten(encrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ Content-Type: multipart/encrypted;
+ protocol="application/pgp-encrypted";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: application/pgp-encrypted
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ Version: 1
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Type: application/octet-stream; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP MESSAGE-----
+ ...
+ -----END PGP MESSAGE-----
+ <BLANKLINE>
+ --boundsep--
+ >>> signedAndEncrypted = m.signAndEncrypt(header)
+ >>> signedAndEncrypted.set_boundary('boundsep')
+ >>> print flatten(signedAndEncrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ Content-Type: multipart/encrypted;
+ protocol="application/pgp-encrypted";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: application/pgp-encrypted
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ Version: 1
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Type: application/octet-stream; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP MESSAGE-----
+ ...
+ -----END PGP MESSAGE-----
+ <BLANKLINE>
+ --boundsep--
+ """
+ def __init__(self, body):
+ self.body = body
+ def clearBodyPart(self):
+ body = encodedMIMEText(self.body)
+ body.add_header('Content-Disposition', 'inline')
+ return body
+ def passphrase_arg(self, passphrase=None):
+ if passphrase == None and PASSPHRASE != None:
+ passphrase = PASSPHRASE
+ if passphrase == None:
+ return (None,'')
+ return (passphrase, pgp_stdin_passphrase_arg)
+ def plain(self):
+ """
+ text/plain
+ """
+ return encodedMIMEText(self.body)
+ def sign(self, header, passphrase=None):
+ """
+ multipart/signed
+ +-> text/plain (body)
+ +-> application/pgp-signature (signature)
+ """
+ passphrase,pass_arg = self.passphrase_arg(passphrase)
+ body = self.clearBodyPart()
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ args = replace(pgp_sign_command, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % source_email(header)
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ args = replace(args, 'p', pass_arg)
+ status,output,error = execute(args, stdin=passphrase)
+ signature = output
+
+ sig = MIMEApplication(_data=signature,
+ _subtype='pgp-signature; name="signature.asc"',
+ _encoder=encode_7or8bit)
+ sig['Content-Description'] = 'signature'
+ sig.set_charset('us-ascii')
+
+ msg = MIMEMultipart('signed', micalg='pgp-sha1',
+ protocol='application/pgp-signature')
+ msg.attach(body)
+ msg.attach(sig)
+
+ msg['Content-Disposition'] = 'inline'
+ return msg
+ def encrypt(self, header, passphrase=None):
+ """
+ multipart/encrypted
+ +-> application/pgp-encrypted (control information)
+ +-> application/octet-stream (body)
+ """
+ body = self.clearBodyPart()
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ recipients = [replace(pgp_recipient_arg, 'r', recipient)
+ for recipient in target_emails(header)]
+ recipient_string = ' '.join(recipients)
+ args = replace(pgp_encrypt_only_command, 'R', recipient_string)
+ args = replace(args, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % source_email(header)
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ status,output,error = execute(args)
+ encrypted = output
+
+ enc = MIMEApplication(_data=encrypted, _subtype='octet-stream',
+ _encoder=encode_7or8bit)
+ enc.set_charset('us-ascii')
+
+ control = MIMEApplication(_data='Version: 1\n', _subtype='pgp-encrypted',
+ _encoder=encode_7or8bit)
+
+ msg = MIMEMultipart('encrypted', micalg='pgp-sha1',
+ protocol='application/pgp-encrypted')
+ msg.attach(control)
+ msg.attach(enc)
+
+ msg['Content-Disposition'] = 'inline'
+ return msg
+ def signAndEncrypt(self, header, passphrase=None):
+ """
+ multipart/encrypted
+ +-> application/pgp-encrypted (control information)
+ +-> application/octet-stream (body)
+ """
+ passphrase,pass_arg = self.passphrase_arg(passphrase)
+ body = self.sign(header, passphrase)
+ body.__delitem__('Bcc')
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ recipients = [replace(pgp_recipient_arg, 'r', recipient)
+ for recipient in target_emails(header)]
+ recipient_string = ' '.join(recipients)
+ args = replace(pgp_encrypt_only_command, 'R', recipient_string)
+ args = replace(args, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % source_email(header)
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ args = replace(args, 'p', pass_arg)
+ status,output,error = execute(args, stdin=passphrase)
+ encrypted = output
+
+ enc = MIMEApplication(_data=encrypted, _subtype='octet-stream',
+ _encoder=encode_7or8bit)
+ enc.set_charset('us-ascii')
+
+ control = MIMEApplication(_data='Version: 1\n',
+ _subtype='pgp-encrypted',
+ _encoder=encode_7or8bit)
+
+ msg = MIMEMultipart('encrypted', micalg='pgp-sha1',
+ protocol='application/pgp-encrypted')
+ msg.attach(control)
+ msg.attach(enc)
+
+ msg['Content-Disposition'] = 'inline'
+ return msg
+
+def test():
+ import doctest
+ doctest.testmod()
+
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser(usage=usage)
+ parser.add_option('-t', '--test', dest='test', action='store_true',
+ help='Run doctests and exit')
+
+ parser.add_option('-H', '--header-file', dest='header_filename',
+ help='file containing email header', metavar='FILE')
+ parser.add_option('-B', '--body-file', dest='body_filename',
+ help='file containing email body', metavar='FILE')
+
+ parser.add_option('-P', '--passphrase-file', dest='passphrase_file',
+ help='file containing gpg passphrase', metavar='FILE')
+ parser.add_option('-p', '--passphrase-fd', dest='passphrase_fd',
+ help='file descriptor from which to read gpg passphrase (0 for stdin)',
+ type="int", metavar='DESCRIPTOR')
+
+ parser.add_option('--mode', dest='mode', default='sign',
+ help="One of 'sign', 'encrypt', 'sign-encrypt', or 'plain'. Defaults to %default.",
+ metavar='MODE')
+
+ parser.add_option('-a', '--sign-as', dest='sign_as',
+ help="The gpg key to sign with (gpg's -u/--local-user)",
+ metavar='KEY')
+
+ parser.add_option('--output', dest='output', action='store_true',
+ help="Don't mail the generated message, print it to stdout instead.")
+
+ (options, args) = parser.parse_args()
+
+ stdin_used = False
+
+ if options.passphrase_file != None:
+ PASSPHRASE = file(options.passphrase_file, 'r').read()
+ elif options.passphrase_fd != None:
+ if options.passphrase_fd == 0:
+ stdin_used = True
+ PASSPHRASE = sys.stdin.read()
+ else:
+ PASSPHRASE = os.read(options.passphrase_fd)
+
+ if options.sign_as:
+ PGP_SIGN_AS = options.sign_as
+
+ if options.test == True:
+ test()
+ sys.exit(0)
+
+ header = None
+ if options.header_filename != None:
+ if options.header_filename == '-':
+ assert stdin_used == False
+ stdin_used = True
+ header = sys.stdin.read()
+ else:
+ header = file(options.header_filename, 'r').read()
+ if header == None:
+ raise Exception, "missing header"
+ headermsg = header_from_text(header)
+ body = None
+ if options.body_filename != None:
+ if options.body_filename == '-':
+ assert stdin_used == False
+ stdin_used = True
+ body = sys.stdin.read()
+ else:
+ body = file(options.body_filename, 'r').read()
+ if body == None:
+ raise Exception, "missing body"
+
+ m = EncryptedMessageFactory(body)
+ if options.mode == "sign":
+ bodymsg = m.sign(header)
+ elif options.mode == "encrypt":
+ bodymsg = m.encrypt(header)
+ elif options.mode == "sign-encrypt":
+ bodymsg = m.signAndEncrypt(header)
+ elif options.mode == "plain":
+ bodymsg = m.plain()
+ else:
+ print "Unrecognized mode '%s'" % options.mode
+
+ message = attach_root(headermsg, bodymsg)
+ if options.output == True:
+ message = flatten(message)
+ print message
+ else:
+ mail(message, sendmail)
diff --git a/misc/gui/beg b/interfaces/gui/beg/beg
index 55e537d..55e537d 100755
--- a/misc/gui/beg
+++ b/interfaces/gui/beg/beg
diff --git a/misc/gui/table.py b/interfaces/gui/beg/table.py
index 2865f28..2865f28 100644
--- a/misc/gui/table.py
+++ b/interfaces/gui/beg/table.py
diff --git a/misc/gui/wxbe b/interfaces/gui/wxbe/wxbe
index e71ae0c..e71ae0c 100755
--- a/misc/gui/wxbe
+++ b/interfaces/gui/wxbe/wxbe
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt
index def18b1..def18b1 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt
diff --git a/Bugs-Everywhere-Web/beweb/__init__.py b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe
index e69de29..e69de29 100644
--- a/Bugs-Everywhere-Web/beweb/__init__.py
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt
index 88b15cb..88b15cb 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt
index 7f7cbad..7f7cbad 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt
index 6455be9..6455be9 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO
index 6cb6ad2..6cb6ad2 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt
index ab62ee4..ab62ee4 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt
index 8b13789..8b13789 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe
index 8b13789..8b13789 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt
index 14fec70..14fec70 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt
index 5fd6f71..5fd6f71 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt
index 7f7cbad..7f7cbad 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt
diff --git a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt
index 74a8358..74a8358 100644
--- a/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt
diff --git a/Bugs-Everywhere-Web/README.txt b/interfaces/web/Bugs-Everywhere-Web/README.txt
index 10774df..10774df 100644
--- a/Bugs-Everywhere-Web/README.txt
+++ b/interfaces/web/Bugs-Everywhere-Web/README.txt
diff --git a/Bugs-Everywhere-Web/beweb/templates/__init__.py b/interfaces/web/Bugs-Everywhere-Web/beweb/__init__.py
index e69de29..e69de29 100644
--- a/Bugs-Everywhere-Web/beweb/templates/__init__.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/__init__.py
diff --git a/Bugs-Everywhere-Web/beweb/app.cfg b/interfaces/web/Bugs-Everywhere-Web/beweb/app.cfg
index 024fa8a..024fa8a 100644
--- a/Bugs-Everywhere-Web/beweb/app.cfg
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/app.cfg
diff --git a/Bugs-Everywhere-Web/beweb/config.py.example b/interfaces/web/Bugs-Everywhere-Web/beweb/config.py.example
index 8745c6d..8745c6d 100644
--- a/Bugs-Everywhere-Web/beweb/config.py.example
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/config.py.example
diff --git a/Bugs-Everywhere-Web/beweb/config/app.cfg b/interfaces/web/Bugs-Everywhere-Web/beweb/config/app.cfg
index 15555b7..15555b7 100644
--- a/Bugs-Everywhere-Web/beweb/config/app.cfg
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/config/app.cfg
diff --git a/Bugs-Everywhere-Web/beweb/config/log.cfg b/interfaces/web/Bugs-Everywhere-Web/beweb/config/log.cfg
index ce776f8..ce776f8 100644
--- a/Bugs-Everywhere-Web/beweb/config/log.cfg
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/config/log.cfg
diff --git a/Bugs-Everywhere-Web/beweb/controllers.py b/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py
index a0d0ff9..a0d0ff9 100644
--- a/Bugs-Everywhere-Web/beweb/controllers.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py
diff --git a/Bugs-Everywhere-Web/beweb/formatting.py b/interfaces/web/Bugs-Everywhere-Web/beweb/formatting.py
index 1278414..1278414 100644
--- a/Bugs-Everywhere-Web/beweb/formatting.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/formatting.py
diff --git a/Bugs-Everywhere-Web/beweb/json.py b/interfaces/web/Bugs-Everywhere-Web/beweb/json.py
index 6e100c3..6e100c3 100644
--- a/Bugs-Everywhere-Web/beweb/json.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/json.py
diff --git a/Bugs-Everywhere-Web/beweb/model.py b/interfaces/web/Bugs-Everywhere-Web/beweb/model.py
index aa4b6b6..aa4b6b6 100644
--- a/Bugs-Everywhere-Web/beweb/model.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/model.py
diff --git a/Bugs-Everywhere-Web/beweb/prest.py b/interfaces/web/Bugs-Everywhere-Web/beweb/prest.py
index 9a6505d..9a6505d 100644
--- a/Bugs-Everywhere-Web/beweb/prest.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/prest.py
diff --git a/Bugs-Everywhere-Web/beweb/release.py b/interfaces/web/Bugs-Everywhere-Web/beweb/release.py
index 9d64bf7..9d64bf7 100644
--- a/Bugs-Everywhere-Web/beweb/release.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/release.py
diff --git a/Bugs-Everywhere-Web/beweb/static/css/style.css b/interfaces/web/Bugs-Everywhere-Web/beweb/static/css/style.css
index 6fe197f..6fe197f 100644
--- a/Bugs-Everywhere-Web/beweb/static/css/style.css
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/css/style.css
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-b.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.png
index 790e438..790e438 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-b.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png
index 5b43259..5b43259 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-br.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.png
index 6cfd62c..6cfd62c 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-br.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-l.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.png
index a6ce3ce..a6ce3ce 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-l.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-r.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.png
index 1ffd6f8..1ffd6f8 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-r.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-t.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.png
index 0129b0c..0129b0c 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-t.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png
index d616b77..d616b77 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png
index 18e542e..18e542e 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png
index 05a190e..05a190e 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png
index 0c3ea4c..0c3ea4c 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/favicon.ico b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico
index 339d09c..339d09c 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/favicon.ico
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/favicon.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png
index 6dc53ee..6dc53ee 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/favicon.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png
index cb4b56c..cb4b56c 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/header_inner.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.png
index 2b2d87d..2b2d87d 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/header_inner.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/info.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png
index 329c523..329c523 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/info.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-b.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.png
index 25d3cfa..25d3cfa 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-b.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-bl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.png
index f496223..f496223 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-bl.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-br.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.png
index 74cbd91..74cbd91 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-br.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-l.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.png
index dd567fa..dd567fa 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-l.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-r.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.png
index 9ac4486..9ac4486 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-r.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-t.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.png
index fbb06c8..fbb06c8 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-t.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-tl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.png
index 9336290..9336290 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-tl.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/is-tr.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.png
index de74808..de74808 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/is-tr.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/ok.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png
index fee6751..fee6751 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/ok.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/shadows.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png
index 9ddc676..9ddc676 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/shadows.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/spiral.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png
index b4bcb1e..b4bcb1e 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/spiral.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png
index bc9c79c..bc9c79c 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png
index 90e84b7..90e84b7 100644
--- a/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png
Binary files differ
diff --git a/Bugs-Everywhere-Web/beweb/tests/__init__.py b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/__init__.py
index e69de29..e69de29 100644
--- a/Bugs-Everywhere-Web/beweb/tests/__init__.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/__init__.py
diff --git a/Bugs-Everywhere-Web/beweb/templates/about.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/about.kid
index fa3548a..fa3548a 100644
--- a/Bugs-Everywhere-Web/beweb/templates/about.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/about.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/bugs.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/bugs.kid
index 198aa94..198aa94 100644
--- a/Bugs-Everywhere-Web/beweb/templates/bugs.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/bugs.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid
index 276f610..276f610 100644
--- a/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid
index 2b522d4..2b522d4 100644
--- a/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/error.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/error.kid
index bc55615..bc55615 100644
--- a/Bugs-Everywhere-Web/beweb/templates/error.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/error.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/login.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/login.kid
index e7ad852..e7ad852 100644
--- a/Bugs-Everywhere-Web/beweb/templates/login.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/login.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/master.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/master.kid
index 0772524..0772524 100644
--- a/Bugs-Everywhere-Web/beweb/templates/master.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/master.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/projects.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/projects.kid
index d5f9fd3..d5f9fd3 100644
--- a/Bugs-Everywhere-Web/beweb/templates/projects.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/projects.kid
diff --git a/Bugs-Everywhere-Web/beweb/templates/welcome.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/welcome.kid
index 08abd21..08abd21 100644
--- a/Bugs-Everywhere-Web/beweb/templates/welcome.kid
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/welcome.kid
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py
diff --git a/Bugs-Everywhere-Web/beweb/tests/test_controllers.py b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_controllers.py
index 0c77afe..0c77afe 100644
--- a/Bugs-Everywhere-Web/beweb/tests/test_controllers.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_controllers.py
diff --git a/Bugs-Everywhere-Web/beweb/tests/test_model.py b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_model.py
index 74c4e83..74c4e83 100644
--- a/Bugs-Everywhere-Web/beweb/tests/test_model.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_model.py
diff --git a/Bugs-Everywhere-Web/dev.cfg b/interfaces/web/Bugs-Everywhere-Web/dev.cfg
index eda9e6c..eda9e6c 100644
--- a/Bugs-Everywhere-Web/dev.cfg
+++ b/interfaces/web/Bugs-Everywhere-Web/dev.cfg
diff --git a/interfaces/web/Bugs-Everywhere-Web/libbe b/interfaces/web/Bugs-Everywhere-Web/libbe
new file mode 120000
index 0000000..7d18612
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/libbe
@@ -0,0 +1 @@
+../../../libbe \ No newline at end of file
diff --git a/Bugs-Everywhere-Web/prod.cfg b/interfaces/web/Bugs-Everywhere-Web/prod.cfg
index c0d4aca..c0d4aca 100644
--- a/Bugs-Everywhere-Web/prod.cfg
+++ b/interfaces/web/Bugs-Everywhere-Web/prod.cfg
diff --git a/Bugs-Everywhere-Web/sample-prod.cfg b/interfaces/web/Bugs-Everywhere-Web/sample-prod.cfg
index d1052f8..d1052f8 100644
--- a/Bugs-Everywhere-Web/sample-prod.cfg
+++ b/interfaces/web/Bugs-Everywhere-Web/sample-prod.cfg
diff --git a/Bugs-Everywhere-Web/server.log b/interfaces/web/Bugs-Everywhere-Web/server.log
index fe02ade..fe02ade 100644
--- a/Bugs-Everywhere-Web/server.log
+++ b/interfaces/web/Bugs-Everywhere-Web/server.log
diff --git a/Bugs-Everywhere-Web/setup-tables.py b/interfaces/web/Bugs-Everywhere-Web/setup-tables.py
index 161d7c7..161d7c7 100644
--- a/Bugs-Everywhere-Web/setup-tables.py
+++ b/interfaces/web/Bugs-Everywhere-Web/setup-tables.py
diff --git a/Bugs-Everywhere-Web/setup.py b/interfaces/web/Bugs-Everywhere-Web/setup.py
index 8ba3da2..8ba3da2 100644
--- a/Bugs-Everywhere-Web/setup.py
+++ b/interfaces/web/Bugs-Everywhere-Web/setup.py
diff --git a/Bugs-Everywhere-Web/start-beweb.py b/interfaces/web/Bugs-Everywhere-Web/start-beweb.py
index 4070abd..4070abd 100755
--- a/Bugs-Everywhere-Web/start-beweb.py
+++ b/interfaces/web/Bugs-Everywhere-Web/start-beweb.py
diff --git a/interfaces/xml/be-mbox-to-xml b/interfaces/xml/be-mbox-to-xml
new file mode 100755
index 0000000..dc6a1c5
--- /dev/null
+++ b/interfaces/xml/be-mbox-to-xml
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Convert an mbox into xml suitable for imput into be.
+ $ cat mbox | be-mbox-to-xml | be comment --xml <ID> -
+mbox is a flat-file format, consisting of a series of messages.
+Messages begin with a a From_ line, followed by RFC 822 email,
+followed by a blank line.
+"""
+
+import base64
+import email.utils
+from libbe.encoding import get_encoding, set_IO_stream_encodings
+from mailbox import mbox, Message # the mailbox people really want an on-disk copy
+from time import asctime, gmtime
+import types
+from xml.sax.saxutils import escape
+
+DEFAULT_ENCODING = get_encoding()
+set_IO_stream_encodings(DEFAULT_ENCODING)
+
+KNOWN_IDS = []
+
+def comment_message_to_xml(message, fields=None):
+ if fields == None:
+ fields = {}
+ new_fields = {}
+ new_fields[u'alt-id'] = message[u'message-id']
+ new_fields[u'in-reply-to'] = message[u'in-reply-to']
+ new_fields[u'author'] = message[u'from']
+ new_fields[u'date'] = message[u'date']
+ new_fields[u'content-type'] = message.get_content_type()
+ for k,v in new_fields.items():
+ if v != None and type(v) != types.UnicodeType:
+ fields[k] = unicode(v, encoding=DEFAULT_ENCODING)
+ elif v == None and k in fields:
+ new_fields[k] = fields[k]
+ for k,v in fields.items():
+ if k not in new_fields:
+ new_fields.k = fields[k]
+ fields = new_fields
+
+ if fields[u'in-reply-to'] == None:
+ if message[u'references'] != None:
+ refs = message[u'references'].split()
+ for ref in refs: # search for a known reference id.
+ if ref in KNOWN_IDS:
+ fields[u'in-reply-to'] = ref
+ break
+ if fields[u'in-reply-to'] == None and len(refs) > 0:
+ fields[u'in-reply-to'] = refs[0] # default to the first
+ else: # check for mutliple in-reply-to references.
+ refs = fields[u'in-reply-to'].split()
+ for ref in refs: # search for a known reference id.
+ if ref in KNOWN_IDS:
+ fields[u'in-reply-to'] = ref
+ break
+ if fields[u'in-reply-to'] == None and len(refs) > 0:
+ fields[u'in-reply-to'] = refs[0] # default to the first
+
+ if fields['alt-id'] != None:
+ KNOWN_IDS.append(fields['alt-id'])
+
+ if message.is_multipart():
+ ret = []
+ alt_id = fields[u'alt-id']
+ from_str = fields[u'author']
+ date = fields[u'date']
+ for m in message.walk():
+ if m == message:
+ continue
+ fields[u'author'] = from_str
+ fields[u'date'] = date
+ if len(ret) > 0: # we've added one part already
+ fields.pop(u'alt-id') # don't pass alt-id to other parts
+ fields[u'in-reply-to'] = alt_id # others respond to first
+ ret.append(comment_message_to_xml(m, fields))
+ return u'\n'.join(ret)
+
+ charset = message.get_content_charset(DEFAULT_ENCODING).lower()
+ #assert charset == DEFAULT_ENCODING.lower(), \
+ # u"Unknown charset: %s" % charset
+
+ if message[u'content-transfer-encoding'] == None:
+ encoding = DEFAULT_ENCODING
+ else:
+ encoding = message[u'content-transfer-encoding'].lower()
+ body = message.get_payload(decode=True) # attempt to decode
+ assert body != None, "Unable to decode?"
+ if fields[u'content-type'].startswith(u"text/"):
+ body = unicode(body, encoding=charset).rstrip(u'\n')
+ else:
+ body = base64.encode(body)
+ fields[u'body'] = body
+ lines = [u"<comment>"]
+ for tag,body in fields.items():
+ if body != None:
+ ebody = escape(body)
+ lines.append(u" <%s>%s</%s>" % (tag, ebody, tag))
+ lines.append(u"</comment>")
+ return u'\n'.join(lines)
+
+def main(mbox_filename):
+ mb = mbox(mbox_filename)
+ print u'<?xml version="1.0" encoding="%s" ?>' % DEFAULT_ENCODING
+ print u"<comment-list>"
+ for message in mb:
+ print comment_message_to_xml(message)
+ print u"</comment-list>"
+
+
+if __name__ == "__main__":
+ import sys
+ main(sys.argv[1])
diff --git a/xml/be-xml-to-mbox b/interfaces/xml/be-xml-to-mbox
index 80db634..c630447 100755
--- a/xml/be-xml-to-mbox
+++ b/interfaces/xml/be-xml-to-mbox
@@ -2,19 +2,19 @@
# Copyright (C) 2009 Chris Ball <cjb@laptop.org>
# W. Trevor King <wking@drexel.edu>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
Convert xml output of `be list --xml` into mbox format for browsing
with a mail reader. For example
@@ -26,14 +26,16 @@ followed by a blank line.
"""
#from mailbox import mbox, Message # the mailbox people really want an on-disk copy
+import codecs
import email.utils
-import types
-
from libbe.encoding import get_encoding, set_IO_stream_encodings
from libbe.utility import str_to_time as rfc2822_to_gmtime_integer
from time import asctime, gmtime
-from xml.sax import make_parser
-from xml.sax.handler import ContentHandler
+import types
+try: # import core module, Python >= 2.5
+ from xml.etree import ElementTree
+except ImportError: # look for non-core module
+ from elementtree import ElementTree
from xml.sax.saxutils import unescape
@@ -83,7 +85,7 @@ class Bug (LimitedAttrDict):
u"created",
u"summary",
u"comments",
- u"extra_strings"]
+ u"extra-strings"]
def print_to_mbox(self):
name,addr = email.utils.parseaddr(self["creator"])
print "From %s %s" % (addr, rfc2822_to_asctime(self["created"]))
@@ -96,28 +98,62 @@ class Bug (LimitedAttrDict):
print ""
print self["summary"]
print ""
- if len(self["extra_strings"]) > 0:
+ if "extra-strings" in self:
print "extra strings:\n ",
print '\n '.join(self["extra_strings"])
print ""
- for comment in self["comments"]:
- comment.print_to_mbox(self)
+ if "comments" in self:
+ for comment in self["comments"]:
+ comment.print_to_mbox(self)
+ def init_from_etree(self, element):
+ assert element.tag == "bug", element.tag
+ for field in element.getchildren():
+ text = unescape(unicode(field.text).decode("unicode_escape").strip())
+ if field.tag == "comment":
+ comm = Comment()
+ comm.init_from_etree(field)
+ if "comments" in self:
+ self["comments"].append(comm)
+ else:
+ self["comments"] = [comm]
+ elif field.tag == "extra-string":
+ if "extra-strings" in self:
+ self["extra-strings"].append(text)
+ else:
+ self["extra-strings"] = [text]
+ else:
+ self[field.tag] = text
class Comment (LimitedAttrDict):
_attrs = [u"uuid",
+ u"alt-id",
u"short-name",
u"in-reply-to",
- u"from",
+ u"author",
u"date",
u"content-type",
u"body"]
- def print_to_mbox(self, bug):
- name,addr = email.utils.parseaddr(self["from"])
+ def print_to_mbox(self, bug=None):
+ if bug == None:
+ bug = Bug()
+ bug[u"uuid"] = u"no-uuid"
+ name,addr = email.utils.parseaddr(self["author"])
print "From %s %s" % (addr, rfc2822_to_asctime(self["date"]))
- print "Message-ID: <%s@%s>" % (self["uuid"], DEFAULT_DOMAIN)
+ if "uuid" in self: id = self["uuid"]
+ elif "alt-id" in self: id = self["alt-id"]
+ else: id = None
+ if id != None:
+ print "Message-ID: <%s@%s>" % (id, DEFAULT_DOMAIN)
print "Date: %s" % self["date"]
- print "From: %s" % self["from"]
- print "Subject: %s: %s" % (self["short-name"], bug["summary"])
+ print "From: %s" % self["author"]
+ subject = ""
+ if "short-name" in self:
+ subject += self["short-name"]+u": "
+ if "summary" in bug:
+ subject += bug["summary"]
+ else:
+ subject += u"no-subject"
+ print "Subject: %s" % subject
if "in-reply-to" not in self.keys():
self["in-reply-to"] = bug["uuid"]
print "In-Reply-To: <%s@%s>" % (self["in-reply-to"], DEFAULT_DOMAIN)
@@ -129,72 +165,41 @@ class Comment (LimitedAttrDict):
else: # content type and transfer encoding already in XML MIME output
print self["body"]
print ""
+ def init_from_etree(self, element):
+ assert element.tag == "comment", element.tag
+ for field in element.getchildren():
+ text = unescape(unicode(field.text).decode("unicode_escape").strip())
+ if field.tag == "body":
+ text+="\n"
+ self[field.tag] = text
-class BE_list_handler (ContentHandler):
- def __init__(self):
- self.reset()
-
- def reset(self):
- self.bug = None
- self.comment = None
- self.extra_strings = None
- self.text_field = None
-
- def startElement(self, name, attributes):
- if name == "bug":
- assert self.bug == None, "Nested bugs?!"
- assert self.comment == None
- assert self.text_field == None
- self.bug = Bug(comments=[], extra_strings=[])
- elif name == "comment":
- assert self.bug != None, "<comment> not in <bug>?"
- assert self.comment == None, "Nested comments?!"
- assert self.text_field == None, "<comment> in text field %s?" % self.text_field
- self.comment = Comment()
- elif self.bug != None and self.comment == None:
- # parse bug text field
- self.text_field = name
- self.text_data = ""
- elif self.bug != None and self.comment != None:
- # parse comment text field
- self.text_field = name
- self.text_data = ""
-
- def endElement(self, name):
- if name == "bug":
- assert self.bug != None, "Invalid XML?"
- assert self.comment == None, "Invalid XML?"
- assert self.text_field == None, "Invalid XML?"
- self.bug.print_to_mbox()
- self.bug = None
- elif name == "comment":
- assert self.bug != None, "<comment> not in <bug>?"
- assert self.comment != None, "Invalid XML?"
- assert self.text_field == None, "<comment> in text field %s?" % self.text_field
- self.bug["comments"].append(self.comment)
- # comments printed by bug.print_to_mbox()
- self.comment = None
- elif self.bug != None and self.comment == None:
- # parse bug text field
- if self.text_field == "extra-string":
- self.bug["extra_strings"].append(unescape(self.text_data.strip()))
- else:
- self.bug[self.text_field] = unescape(self.text_data.strip())
- self.text_field = None
- self.text_data = None
- elif self.bug != None and self.comment != None:
- # parse comment text field
- self.comment[self.text_field] = unescape(self.text_data.strip())
- self.text_field = None
- self.text_data = None
-
- def characters(self, data):
- if self.text_field != None:
- self.text_data += data
+def print_to_mbox(element):
+ if element.tag == "bug":
+ b = Bug()
+ b.init_from_etree(element)
+ b.print_to_mbox()
+ elif element.tag == "comment":
+ c = Comment()
+ c.init_from_etree(element)
+ c.print_to_mbox()
+ elif element.tag in ["bugs", "bug-list"]:
+ for b_elt in element.getchildren():
+ b = Bug()
+ b.init_from_etree(b_elt)
+ b.print_to_mbox()
+ elif element.tag in ["comments", "comment-list"]:
+ for c_elt in element.getchildren():
+ c = Comment()
+ c.init_from_etree(c_elt)
+ c.print_to_mbox()
if __name__ == "__main__":
import sys
-
- parser = make_parser()
- parser.setContentHandler(BE_list_handler())
- parser.parse(sys.stdin)
+
+ if len(sys.argv) == 1: # no filename given, use stdin
+ xml_unicode = sys.stdin.read()
+ else:
+ xml_unicode = codecs.open(sys.argv[1], "r", DEFAULT_ENCODING).read()
+ xml_str = xml_unicode.encode("unicode_escape").replace(r"\n", "\n")
+ elist = ElementTree.XML(xml_str)
+ print_to_mbox(elist)
diff --git a/libbe/arch.py b/libbe/arch.py
index 3051b34..63e39e3 100644
--- a/libbe/arch.py
+++ b/libbe/arch.py
@@ -2,21 +2,20 @@
# Ben Finney <ben+python@benfinney.id.au>
# James Rowe <jnrowe@ukfsn.org>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import codecs
import os
@@ -63,7 +62,11 @@ class Arch(RCS):
self._create_project(path)
self._add_project_code(path)
def _create_archive(self, path):
- # Create a new archive
+ """
+ Create a temporary Arch archive in the directory PATH. This
+ archive will be removed by
+ __del__->cleanup->_rcs_cleanup->_remove_archive
+ """
# http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive
assert self._archive_name == None
id = self.get_user_id()
@@ -177,7 +180,6 @@ class Arch(RCS):
self._get_archive_project_name(root)
return root
-
def _get_archive_name(self, root):
status,output,error = self._u_invoke_client("archives")
lines = output.split('\n')
@@ -189,7 +191,6 @@ class Arch(RCS):
if os.path.realpath(location) == os.path.realpath(root):
self._archive_name = archive
assert self._archive_name != None
-
def _get_archive_project_name(self, root):
# get project names
status,output,error = self._u_invoke_client("tree-version", directory=root)
@@ -261,16 +262,17 @@ class Arch(RCS):
else:
status,output,error = \
self._u_invoke_client("get", revision,directory)
- def _rcs_commit(self, commitfile):
+ def _rcs_commit(self, commitfile, allow_empty=False):
+ if allow_empty == False:
+ # arch applies empty commits without complaining, so check first
+ status,output,error = self._u_invoke_client("changes",expect=(0,1))
+ if status == 0:
+ raise rcs.EmptyCommit()
summary,body = self._u_parse_commitfile(commitfile)
- #status,output,error = self._invoke_client("make-log")
- if body == None:
- status,output,error \
- = self._u_invoke_client("commit","--summary",summary)
- else:
- status,output,error \
- = self._u_invoke_client("commit","--summary",summary,
- "--log-message",body)
+ args = ["commit", "--summary", summary]
+ if body != None:
+ args.extend(["--log-message",body])
+ status,output,error = self._u_invoke_client(*args)
revision = None
revline = re.compile("[*] committed (.*)")
match = revline.search(output)
@@ -281,6 +283,16 @@ class Arch(RCS):
assert revpath.startswith(self._archive_project_name()+'--')
revision = revpath[len(self._archive_project_name()+'--'):]
return revpath
+ def _rcs_revision_id(self, index):
+ status,output,error = self._u_invoke_client("logs")
+ logs = output.splitlines()
+ first_log = logs.pop(0)
+ assert first_log == "base-0", first_log
+ try:
+ log = logs[index]
+ except IndexError:
+ return None
+ return "%s--%s" % (self._archive_project_name(), log)
class CantAddFile(Exception):
def __init__(self, file):
diff --git a/libbe/beuuid.py b/libbe/beuuid.py
index de67cb7..bc47208 100644
--- a/libbe/beuuid.py
+++ b/libbe/beuuid.py
@@ -1,19 +1,18 @@
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
Backwards compatibility support for Python 2.4. Once people give up
on 2.4 ;), the uuid call should be merged into bugdir.py
diff --git a/libbe/bug.py b/libbe/bug.py
index dfa49f2..7554318 100644
--- a/libbe/bug.py
+++ b/libbe/bug.py
@@ -1,21 +1,20 @@
# Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org>
# Thomas Habets <thomas@habets.pp.se>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import os.path
import errno
@@ -34,6 +33,11 @@ import comment
import utility
+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/
@@ -68,7 +72,7 @@ def load_severities(severity_def):
global severity_values
global severity_description
global severity_index
- if severity_def == settings_object.EMPTY:
+ if severity_def == None:
return
severity_values = tuple([val for val,description in severity_def])
severity_description = dict(severity_def)
@@ -88,9 +92,9 @@ def load_status(active_status_def, inactive_status_def):
global status_values
global status_description
global status_index
- if active_status_def == settings_object.EMPTY:
+ if active_status_def == None:
active_status_def = globals()["active_status_def"]
- if inactive_status_def == settings_object.EMPTY:
+ if inactive_status_def == None:
inactive_status_def = globals()["inactive_status_def"]
active_status_values = tuple([val for val,description in active_status_def])
inactive_status_values = tuple([val for val,description in inactive_status_def])
@@ -178,7 +182,7 @@ class Bug(settings_object.SavedSettingsObject):
def time_string(): return {}
def _get_time(self):
- if self.time_string == None or self.time_string == settings_object.EMPTY:
+ if self.time_string == None:
return None
return utility.str_to_time(self.time_string)
def _set_time(self, value):
@@ -188,15 +192,8 @@ class Bug(settings_object.SavedSettingsObject):
doc="An integer version of .time_string")
def _extra_strings_check_fn(value):
- "Require an iterable full of strings"
- if value == settings_object.EMPTY:
- return True
- elif not hasattr(value, "__iter__"):
- return False
- for x in value:
- if type(x) not in types.StringTypes:
- return False
- return True
+ return utility.iterable_full_of_strings(value, \
+ alternative=settings_object.EMPTY)
def _extra_strings_change_hook(self, old, new):
self.extra_strings.sort() # to make merging easier
self._prop_save_settings(old, new)
@@ -253,12 +250,19 @@ class Bug(settings_object.SavedSettingsObject):
def __repr__(self):
return "Bug(uuid=%r)" % self.uuid
+ def __str__(self):
+ return self.string(shortlist=True)
+
+ def __cmp__(self, other):
+ return cmp_full(self, other)
+
+ # serializing methods
+
def _setting_attr_string(self, setting):
value = getattr(self, setting)
- if value == settings_object.EMPTY:
+ if value == None:
return ""
- else:
- return str(value)
+ return str(value)
def xml(self, show_comments=False):
if self.bugdir == None:
@@ -283,7 +287,7 @@ class Bug(settings_object.SavedSettingsObject):
("summary", self.summary)]
ret = '<bug>\n'
for (k,v) in info:
- if v is not settings_object.EMPTY:
+ if v is not None:
ret += ' <%s>%s</%s>\n' % (k,xml.sax.saxutils.escape(v),k)
for estr in self.extra_strings:
ret += ' <extra-string>%s</extra-string>\n' % estr
@@ -335,11 +339,7 @@ class Bug(settings_object.SavedSettingsObject):
output = bugout
return output
- def __str__(self):
- return self.string(shortlist=True)
-
- def __cmp__(self, other):
- return cmp_full(self, other)
+ # methods for saving/loading/acessing settings and properties.
def get_path(self, name=None):
my_dir = os.path.join(self.bugdir.get_path("bugs"), self.uuid)
@@ -348,11 +348,47 @@ class Bug(settings_object.SavedSettingsObject):
assert name in ["values", "comments"]
return os.path.join(my_dir, name)
+ def set_sync_with_disk(self, value):
+ self.sync_with_disk = value
+ for comment in self.comments():
+ comment.set_sync_with_disk(value)
+
def load_settings(self):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("load settings")
self.settings = mapfile.map_load(self.rcs, self.get_path("values"))
self._setup_saved_settings()
+ def save_settings(self):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("save settings")
+ assert self.summary != None, "Can't save blank bug"
+ self.rcs.mkdir(self.get_path())
+ path = self.get_path("values")
+ mapfile.map_save(self.rcs, path, self._get_saved_settings())
+
+ def save(self):
+ """
+ Save any loaded contents to disk. Because of lazy loading of
+ comments, this is actually not too inefficient.
+
+ However, if self.sync_with_disk = True, then any changes are
+ automatically written to disk as soon as they happen, so
+ calling this method will just waste time (unless something
+ else has been messing with your on-disk files).
+ """
+ sync_with_disk = self.sync_with_disk
+ if sync_with_disk == False:
+ self.set_sync_with_disk(True)
+ self.save_settings()
+ if len(self.comment_root) > 0:
+ comment.saveComments(self)
+ if sync_with_disk == False:
+ self.set_sync_with_disk(False)
+
def load_comments(self, load_full=True):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("load comments")
if load_full == True:
# Force a complete load of the whole comment tree
self.comment_root = self._get_comment_root(load_full=True)
@@ -365,25 +401,15 @@ class Bug(settings_object.SavedSettingsObject):
self.comment_root = None
self.sync_with_disk = True
- def save_settings(self):
- assert self.summary != None, "Can't save blank bug"
-
- self.rcs.mkdir(self.get_path())
- path = self.get_path("values")
- mapfile.map_save(self.rcs, path, self._get_saved_settings())
-
- def save(self):
- self.save_settings()
-
- if len(self.comment_root) > 0:
- self.rcs.mkdir(self.get_path("comments"))
- comment.saveComments(self)
-
def remove(self):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("remove")
self.comment_root.remove()
path = self.get_path()
self.rcs.recursive_remove(path)
+ # methods for managing comments
+
def comments(self):
for comment in self.comment_root.traverse():
yield comment
@@ -477,8 +503,8 @@ def cmp_attr(bug_1, bug_2, attr, invert=False):
return 1
val_1 = getattr(bug_1, attr)
val_2 = getattr(bug_2, attr)
- if val_1 == settings_object.EMPTY: val_1 = None
- if val_2 == settings_object.EMPTY: val_2 = None
+ if val_1 == None: val_1 = None
+ if val_2 == None: val_2 = None
if invert == True :
return -cmp(val_1, val_2)
@@ -486,13 +512,35 @@ def cmp_attr(bug_1, bug_2, attr, invert=False):
return cmp(val_1, val_2)
# alphabetical rankings (a < z)
+cmp_uuid = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "uuid")
cmp_creator = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "creator")
cmp_assigned = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "assigned")
+cmp_target = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "target")
+cmp_reporter = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "reporter")
+cmp_summary = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "summary")
# chronological rankings (newer < older)
cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True)
+def cmp_comments(bug_1, bug_2):
+ """
+ Compare two bugs' comments lists. Doesn't load any new comments,
+ so you should call each bug's .load_comments() first if you want a
+ full comparison.
+ """
+ comms_1 = sorted(bug_1.comments(), key = lambda comm : comm.uuid)
+ comms_2 = sorted(bug_2.comments(), key = lambda comm : comm.uuid)
+ result = cmp(len(comms_1), len(comms_2))
+ if result != 0:
+ return result
+ for c_1,c_2 in zip(comms_1, comms_2):
+ result = cmp(c_1, c_2)
+ if result != 0:
+ return result
+ return 0
+
DEFAULT_CMP_FULL_CMP_LIST = \
- (cmp_status,cmp_severity,cmp_assigned,cmp_time,cmp_creator)
+ (cmp_status, cmp_severity, cmp_assigned, cmp_time, cmp_creator,
+ cmp_reporter, cmp_target, cmp_comments, cmp_summary, cmp_uuid)
class BugCompoundComparator (object):
def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST):
diff --git a/libbe/bugdir.py b/libbe/bugdir.py
index d4a39cb..0eb4a6c 100644
--- a/libbe/bugdir.py
+++ b/libbe/bugdir.py
@@ -3,26 +3,26 @@
# Chris Ball <cjb@laptop.org>
# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import copy
+import errno
import os
import os.path
-import errno
+import sys
import time
-import copy
import unittest
import doctest
@@ -52,7 +52,7 @@ class NoRootEntry(Exception):
class AlreadyInitialized(Exception):
def __init__(self, path):
self.path = path
- Exception.__init__(self,
+ Exception.__init__(self,
"Specified root is already initialized: %s" % path)
class MultipleBugMatches(ValueError):
@@ -63,6 +63,17 @@ class MultipleBugMatches(ValueError):
self.shortname = shortname
self.matches = matches
+class NoBugMatches(KeyError):
+ def __init__(self, shortname):
+ msg = "No bug matches %s" % shortname
+ KeyError.__init__(self, msg)
+ self.shortname = shortname
+
+class DiskAccessRequired (Exception):
+ def __init__(self, goal):
+ msg = "Cannot %s without accessing the disk" % goal
+ Exception.__init__(self, msg)
+
TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n"
@@ -71,7 +82,7 @@ class BugDir (list, settings_object.SavedSettingsObject):
"""
Sink to existing root
======================
-
+
Consider the following usage case:
You have a bug directory rooted in
/path/to/source
@@ -86,23 +97,35 @@ class BugDir (list, settings_object.SavedSettingsObject):
/path/to/source/GUI/.be miss
/path/to/source/.be hit!
So it still roots itself appropriately without much work for you.
-
+
File-system access
==================
-
- When rooted in non-bugdir directory, BugDirs live completely in
- memory until the first call to .save(). This creates a '.be'
- sub-directory containing configurations options, bugs, comments,
- etc. Once this sub-directory has been created (possibly by
- another BugDir instance) any changes to the BugDir in memory will
- be flushed to the file system automatically. However, the BugDir
- will only load information from the file system when it loads new
- bugs/comments that it doesn't already have in memory, or when it
- explicitly asked to do so (e.g. .load() or __init__(from_disk=True)).
-
+
+ BugDirs live completely in memory when .sync_with_disk is False.
+ This is the default configuration setup by BugDir(from_disk=False).
+ If .sync_with_disk == True (e.g. BugDir(from_disk=True)), then
+ any changes to the BugDir will be immediately written to disk.
+
+ If you want to change .sync_with_disk, we suggest you use
+ .set_sync_with_disk(), which propogates the new setting through to
+ all bugs/comments/etc. that have been loaded into memory. If
+ you've been living in memory and want to move to
+ .sync_with_disk==True, but you're not sure if anything has been
+ changed in memory, a call to save() immediately before the
+ .set_sync_with_disk(True) call is a safe move.
+
+ Regardless of .sync_with_disk, a call to .save() will write out
+ all the contents that the BugDir instance has loaded into memory.
+ If sync_with_disk has been True over the course of all interesting
+ changes, this .save() call will be a waste of time.
+
+ The BugDir will only load information from the file system when it
+ loads new settings/bugs/comments that it doesn't already have in
+ memory and .sync_with_disk == True.
+
Allow RCS initialization
========================
-
+
This one is for testing purposes. Setting it to True allows the
BugDir to search for an installed RCS backend and initialize it in
the root directory. This is a convenience option for supporting
@@ -110,7 +133,7 @@ class BugDir (list, settings_object.SavedSettingsObject):
Disable encoding manipulation
=============================
-
+
This one is for testing purposed. You might have non-ASCII
Unicode in your bugs, comments, files, etc. BugDir instances try
and support your preferred encoding scheme (e.g. "utf-8") when
@@ -136,16 +159,17 @@ class BugDir (list, settings_object.SavedSettingsObject):
return settings_object.versioned_property(**kwargs)
@_versioned_property(name="target",
- doc="The current project development target")
+ doc="The current project development target.")
def target(): return {}
def _guess_encoding(self):
return encoding.get_encoding()
def _check_encoding(value):
- if value != None and value != settings_object.EMPTY:
+ if value != None:
return encoding.known_encoding(value)
def _setup_encoding(self, new_encoding):
- if new_encoding != None and new_encoding != settings_object.EMPTY:
+ # change hook called before generator.
+ if new_encoding not in [None, settings_object.EMPTY]:
if self._manipulate_encodings == True:
encoding.set_IO_stream_encodings(new_encoding)
def _set_encoding(self, old_encoding, new_encoding):
@@ -160,7 +184,7 @@ class BugDir (list, settings_object.SavedSettingsObject):
def encoding(): return {}
def _setup_user_id(self, user_id):
- self.rcs.user_id = user_id
+ self.rcs.user_id = user_id
def _guess_user_id(self):
return self.rcs.get_user_id()
def _set_user_id(self, old_user_id, new_user_id):
@@ -215,7 +239,21 @@ settings easy. Don't set this attribute. Set .rcs instead, and
if uuid not in map:
map[uuid] = None
self._bug_map_value = map # ._bug_map_value used by @local_property
-
+
+ def _extra_strings_check_fn(value):
+ return utility.iterable_full_of_strings(value, \
+ alternative=settings_object.EMPTY)
+ def _extra_strings_change_hook(self, old, new):
+ self.extra_strings.sort() # to make merging easier
+ self._prop_save_settings(old, new)
+ @_versioned_property(name="extra_strings",
+ doc="Space for an array of extra strings. Useful for storing state for functionality implemented purely in becommands/<some_function>.py.",
+ default=[],
+ check_fn=_extra_strings_check_fn,
+ change_hook=_extra_strings_change_hook,
+ mutable=True)
+ def extra_strings(): return {}
+
@Property
@primed_property(primer=_bug_map_gen)
@local_property("bug_map")
@@ -223,7 +261,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and
def _bug_map(): return {}
def _setup_severities(self, severities):
- if severities != None and severities != settings_object.EMPTY:
+ if severities not in [None, settings_object.EMPTY]:
bug.load_severities(severities)
def _set_severities(self, old_severities, new_severities):
self._setup_severities(new_severities)
@@ -254,8 +292,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and
def __init__(self, root=None, sink_to_existing_root=True,
assert_new_BugDir=False, allow_rcs_init=False,
- manipulate_encodings=True,
- from_disk=False, rcs=None):
+ manipulate_encodings=True, from_disk=False, rcs=None):
list.__init__(self)
settings_object.SavedSettingsObject.__init__(self)
self._manipulate_encodings = manipulate_encodings
@@ -270,7 +307,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and
# get a temporary rcs until we've loaded settings
self.sync_with_disk = False
self.rcs = self._guess_rcs()
-
+
if from_disk == True:
self.sync_with_disk = True
self.load()
@@ -284,10 +321,19 @@ settings easy. Don't set this attribute. Set .rcs instead, and
self.rcs = rcs
self._setup_user_id(self.user_id)
+ def __del__(self):
+ self.cleanup()
+
+ def cleanup(self):
+ self.rcs.cleanup()
+
+ # methods for getting the BugDir situated in the filesystem
+
def _find_root(self, path):
"""
Search for an existing bug database dir and it's ancestors and
- return a BugDir rooted there.
+ return a BugDir rooted there. Only called by __init__, and
+ then only if sink_to_existing_root == True.
"""
if not os.path.exists(path):
raise NoRootEntry(path)
@@ -302,8 +348,78 @@ settings easy. Don't set this attribute. Set .rcs instead, and
if beroot == None:
raise NoBugDir(path)
return beroot
-
+
+ def _guess_rcs(self, allow_rcs_init=False):
+ """
+ Only called by __init__.
+ """
+ deepdir = self.get_path()
+ if not os.path.exists(deepdir):
+ deepdir = os.path.dirname(deepdir)
+ new_rcs = rcs.detect_rcs(deepdir)
+ install = False
+ if new_rcs.name == "None":
+ if allow_rcs_init == True:
+ new_rcs = rcs.installed_rcs()
+ new_rcs.init(self.root)
+ return new_rcs
+
+ # methods for saving/loading/accessing settings and properties.
+
+ def get_path(self, *args):
+ """
+ Return a path relative to .root.
+ """
+ my_dir = os.path.join(self.root, ".be")
+ if len(args) == 0:
+ return my_dir
+ assert args[0] in ["version", "settings", "bugs"], str(args)
+ return os.path.join(my_dir, *args)
+
+ def _get_settings(self, settings_path, for_duplicate_bugdir=False):
+ allow_no_rcs = not self.rcs.path_in_root(settings_path)
+ if allow_no_rcs == True:
+ assert for_duplicate_bugdir == True
+ if self.sync_with_disk == False and for_duplicate_bugdir == False:
+ # duplicates can ignore this bugdir's .sync_with_disk status
+ raise DiskAccessRequired("_get settings")
+ try:
+ settings = mapfile.map_load(self.rcs, settings_path, allow_no_rcs)
+ except rcs.NoSuchFile:
+ settings = {"rcs_name": "None"}
+ return settings
+
+ def _save_settings(self, settings_path, settings,
+ for_duplicate_bugdir=False):
+ allow_no_rcs = not self.rcs.path_in_root(settings_path)
+ if allow_no_rcs == True:
+ assert for_duplicate_bugdir == True
+ if self.sync_with_disk == False and for_duplicate_bugdir == False:
+ # duplicates can ignore this bugdir's .sync_with_disk status
+ raise DiskAccessRequired("_save settings")
+ self.rcs.mkdir(self.get_path(), allow_no_rcs)
+ mapfile.map_save(self.rcs, settings_path, settings, allow_no_rcs)
+
+ def load_settings(self):
+ self.settings = self._get_settings(self.get_path("settings"))
+ self._setup_saved_settings()
+ self._setup_user_id(self.user_id)
+ self._setup_encoding(self.encoding)
+ self._setup_severities(self.severities)
+ self._setup_status(self.active_status, self.inactive_status)
+ self.rcs = rcs.rcs_by_name(self.rcs_name)
+ self._setup_user_id(self.user_id)
+
+ def save_settings(self):
+ settings = self._get_saved_settings()
+ self._save_settings(self.get_path("settings"), settings)
+
def get_version(self, path=None, use_none_rcs=False):
+ """
+ Requires disk access.
+ """
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("get version")
if use_none_rcs == True:
RCS = rcs.rcs_by_name("None")
RCS.root(self.root)
@@ -317,29 +433,31 @@ settings easy. Don't set this attribute. Set .rcs instead, and
return tree_version
def set_version(self):
+ """
+ Requires disk access.
+ """
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("set version")
+ self.rcs.mkdir(self.get_path())
self.rcs.set_file_contents(self.get_path("version"),
TREE_VERSION_STRING)
- def get_path(self, *args):
- my_dir = os.path.join(self.root, ".be")
- if len(args) == 0:
- return my_dir
- assert args[0] in ["version", "settings", "bugs"], str(args)
- return os.path.join(my_dir, *args)
+ # methods controlling disk access
- def _guess_rcs(self, allow_rcs_init=False):
- deepdir = self.get_path()
- if not os.path.exists(deepdir):
- deepdir = os.path.dirname(deepdir)
- new_rcs = rcs.detect_rcs(deepdir)
- install = False
- if new_rcs.name == "None":
- if allow_rcs_init == True:
- new_rcs = rcs.installed_rcs()
- new_rcs.init(self.root)
- return new_rcs
+ def set_sync_with_disk(self, value):
+ """
+ Adjust .sync_with_disk for the BugDir and all it's children.
+ See the BugDir docstring for a description of the role of
+ .sync_with_disk.
+ """
+ self.sync_with_disk = value
+ for bug in self:
+ bug.set_sync_with_disk(value)
def load(self):
+ """
+ Reqires disk access
+ """
version = self.get_version(use_none_rcs=True)
if version != TREE_VERSION_STRING:
raise NotImplementedError, \
@@ -348,52 +466,44 @@ settings easy. Don't set this attribute. Set .rcs instead, and
if not os.path.exists(self.get_path()):
raise NoBugDir(self.get_path())
self.load_settings()
-
- self.rcs = rcs.rcs_by_name(self.rcs_name)
- self._setup_user_id(self.user_id)
def load_all_bugs(self):
- "Warning: this could take a while."
+ """
+ Requires disk access.
+ Warning: this could take a while.
+ """
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("load all bugs")
self._clear_bugs()
for uuid in self.list_uuids():
self._load_bug(uuid)
def save(self):
- self.rcs.mkdir(self.get_path())
+ """
+ Note that this command writes to disk _regardless_ of the
+ status of .sync_with_disk.
+
+ Save any loaded contents to disk. Because of lazy loading of
+ bugs and comments, this is actually not too inefficient.
+
+ However, if .sync_with_disk = True, then any changes are
+ automatically written to disk as soon as they happen, so
+ calling this method will just waste time (unless something
+ else has been messing with your on-disk files).
+
+ Requires disk access.
+ """
+ sync_with_disk = self.sync_with_disk
+ if sync_with_disk == False:
+ self.set_sync_with_disk(True)
self.set_version()
self.save_settings()
- self.rcs.mkdir(self.get_path("bugs"))
for bug in self:
bug.save()
+ if sync_with_disk == False:
+ self.set_sync_with_disk(sync_with_disk)
- def load_settings(self):
- self.settings = self._get_settings(self.get_path("settings"))
- self._setup_saved_settings()
- self._setup_user_id(self.user_id)
- self._setup_encoding(self.encoding)
- self._setup_severities(self.severities)
- self._setup_status(self.active_status, self.inactive_status)
-
- def _get_settings(self, settings_path):
- allow_no_rcs = not self.rcs.path_in_root(settings_path)
- # allow_no_rcs=True should only be for the special case of
- # configuring duplicate bugdir settings
-
- try:
- settings = mapfile.map_load(self.rcs, settings_path, allow_no_rcs)
- except rcs.NoSuchFile:
- settings = {"rcs_name": "None"}
- return settings
-
- def save_settings(self):
- settings = self._get_saved_settings()
- self._save_settings(self.get_path("settings"), settings)
-
- def _save_settings(self, settings_path, settings):
- allow_no_rcs = not self.rcs.path_in_root(settings_path)
- # allow_no_rcs=True should only be for the special case of
- # configuring duplicate bugdir settings
- mapfile.map_save(self.rcs, settings_path, settings, allow_no_rcs)
+ # methods for managing duplicate BugDirs
def duplicate_bugdir(self, revision):
duplicate_path = self.rcs.duplicate_repo(revision)
@@ -402,23 +512,27 @@ settings easy. Don't set this attribute. Set .rcs instead, and
# initialized for versioning
duplicate_settings_path = os.path.join(duplicate_path,
".be", "settings")
- duplicate_settings = self._get_settings(duplicate_settings_path)
+ duplicate_settings = self._get_settings(duplicate_settings_path,
+ for_duplicate_bugdir=True)
if "rcs_name" in duplicate_settings:
duplicate_settings["rcs_name"] = "None"
duplicate_settings["user_id"] = self.user_id
if "disabled" in bug.status_values:
# Hack to support old versions of BE bugs
duplicate_settings["inactive_status"] = self.inactive_status
- self._save_settings(duplicate_settings_path, duplicate_settings)
+ self._save_settings(duplicate_settings_path, duplicate_settings,
+ for_duplicate_bugdir=True)
return BugDir(duplicate_path, from_disk=True, manipulate_encodings=self._manipulate_encodings)
def remove_duplicate_bugdir(self):
self.rcs.remove_duplicate_repo()
+ # methods for managing bugs
+
def list_uuids(self):
uuids = []
- if os.path.exists(self.get_path()):
+ if self.sync_with_disk == True and os.path.exists(self.get_path()):
# list the uuids on disk
for uuid in os.listdir(self.get_path("bugs")):
if not (uuid.startswith('.')):
@@ -436,6 +550,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and
self._bug_map_gen()
def _load_bug(self, uuid):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("_load bug")
bg = bug.Bug(bugdir=self, uuid=uuid, from_disk=True)
self.append(bg)
self._bug_map_gen()
@@ -443,20 +559,24 @@ settings easy. Don't set this attribute. Set .rcs instead, and
def new_bug(self, uuid=None, summary=None):
bg = bug.Bug(bugdir=self, uuid=uuid, summary=summary)
+ bg.set_sync_with_disk(self.sync_with_disk)
+ if bg.sync_with_disk == True:
+ bg.save()
self.append(bg)
self._bug_map_gen()
return bg
def remove_bug(self, bug):
self.remove(bug)
- bug.remove()
+ if bug.sync_with_disk == True:
+ bug.remove()
def bug_shortname(self, bug):
"""
Generate short names from uuids. Picks the minimum number of
characters (>=3) from the beginning of the uuid such that the
short names are unique.
-
+
Obviously, as the number of bugs in the database grows, these
short names will cease to be unique. The complete uuid should be
used for long term reference.
@@ -471,12 +591,13 @@ settings easy. Don't set this attribute. Set .rcs instead, and
def bug_from_shortname(self, shortname):
"""
- >>> bd = simple_bug_dir()
+ >>> bd = SimpleBugDir(sync_with_disk=False)
>>> bug_a = bd.bug_from_shortname('a')
>>> print type(bug_a)
<class 'libbe.bug.Bug'>
>>> print bug_a
a:om: Bug A
+ >>> bd.cleanup()
"""
matches = []
self._bug_map_gen()
@@ -487,7 +608,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and
raise MultipleBugMatches(shortname, matches)
if len(matches) == 1:
return self.bug_from_uuid(matches[0])
- raise KeyError("No bug matches %s" % shortname)
+ raise NoBugMatches(shortname)
def bug_from_uuid(self, uuid):
if not self.has_bug(uuid):
@@ -503,43 +624,58 @@ settings easy. Don't set this attribute. Set .rcs instead, and
if bug_uuid not in self._bug_map:
return False
return True
-
-def simple_bug_dir():
+
+class SimpleBugDir (BugDir):
"""
- For testing
- >>> bugdir = simple_bug_dir()
- >>> ls = list(bugdir.list_uuids())
- >>> ls.sort()
- >>> print ls
+ For testing. Set sync_with_disk==False for a memory-only bugdir.
+ >>> bugdir = SimpleBugDir()
+ >>> uuids = list(bugdir.list_uuids())
+ >>> uuids.sort()
+ >>> print uuids
['a', 'b']
+ >>> bugdir.cleanup()
"""
- dir = utility.Dir()
- assert os.path.exists(dir.path)
- bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True,
+ def __init__(self, sync_with_disk=True):
+ if sync_with_disk == True:
+ dir = utility.Dir()
+ assert os.path.exists(dir.path)
+ root = dir.path
+ assert_new_BugDir = True
+ rcs_init = True
+ else:
+ root = "/"
+ assert_new_BugDir = False
+ rcs_init = False
+ BugDir.__init__(self, root, sink_to_existing_root=False,
+ assert_new_BugDir=assert_new_BugDir,
+ allow_rcs_init=rcs_init,
manipulate_encodings=False)
- bugdir._dir_ref = dir # postpone cleanup since dir.__del__() removes dir.
- bug_a = bugdir.new_bug("a", summary="Bug A")
- bug_a.creator = "John Doe <jdoe@example.com>"
- bug_a.time = 0
- bug_b = bugdir.new_bug("b", summary="Bug B")
- bug_b.creator = "Jane Doe <jdoe@example.com>"
- bug_b.time = 0
- bug_b.status = "closed"
- bugdir.save()
- return bugdir
-
+ if sync_with_disk == True: # postpone cleanup since dir.__del__() removes dir.
+ self._dir_ref = dir
+ bug_a = self.new_bug("a", summary="Bug A")
+ bug_a.creator = "John Doe <jdoe@example.com>"
+ bug_a.time = 0
+ bug_b = self.new_bug("b", summary="Bug B")
+ bug_b.creator = "Jane Doe <jdoe@example.com>"
+ bug_b.time = 0
+ bug_b.status = "closed"
+ if sync_with_disk == True:
+ self.save()
+ self.set_sync_with_disk(True)
+ def cleanup(self):
+ if hasattr(self, "_dir_ref"):
+ self._dir_ref.cleanup()
+ BugDir.cleanup(self)
class BugDirTestCase(unittest.TestCase):
- def __init__(self, *args, **kwargs):
- unittest.TestCase.__init__(self, *args, **kwargs)
def setUp(self):
self.dir = utility.Dir()
self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False,
allow_rcs_init=True)
self.rcs = self.bugdir.rcs
def tearDown(self):
- self.rcs.cleanup()
+ self.bugdir.cleanup()
self.dir.cleanup()
def fullPath(self, path):
return os.path.join(self.dir.path, path)
@@ -592,24 +728,29 @@ class BugDirTestCase(unittest.TestCase):
self.failUnless(bugA == bugAprime, "%s != %s" % (bugA, bugAprime))
self.bugdir.save()
self.versionTest()
- def testComments(self):
+ def testComments(self, sync_with_disk=False):
+ if sync_with_disk == True:
+ self.bugdir.set_sync_with_disk(True)
self.bugdir.new_bug(uuid="a", summary="Ant")
bug = self.bugdir.bug_from_uuid("a")
comm = bug.comment_root
rep = comm.new_reply("Ants are small.")
rep.new_reply("And they have six legs.")
- self.bugdir.save()
- self.bugdir._clear_bugs()
+ if sync_with_disk == False:
+ self.bugdir.save()
+ self.bugdir.set_sync_with_disk(True)
+ self.bugdir._clear_bugs()
bug = self.bugdir.bug_from_uuid("a")
bug.load_comments()
+ if sync_with_disk == False:
+ self.bugdir.set_sync_with_disk(False)
self.failUnless(len(bug.comment_root)==1, len(bug.comment_root))
for index,comment in enumerate(bug.comments()):
if index == 0:
repLoaded = comment
self.failUnless(repLoaded.uuid == rep.uuid, repLoaded.uuid)
- self.failUnless(comment.sync_with_disk == True,
+ self.failUnless(comment.sync_with_disk == sync_with_disk,
comment.sync_with_disk)
- #load_settings()
self.failUnless(comment.content_type == "text/plain",
comment.content_type)
self.failUnless(repLoaded.settings["Content-type"]=="text/plain",
@@ -623,6 +764,49 @@ class BugDirTestCase(unittest.TestCase):
comment.body)
else:
self.failIf(True, "Invalid comment: %d\n%s" % (index, comment))
+ def testSyncedComments(self):
+ self.testComments(sync_with_disk=True)
-unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase)
-suite = unittest.TestSuite([unitsuite])#, doctest.DocTestSuite()])
+class SimpleBugDirTestCase (unittest.TestCase):
+ def setUp(self):
+ # create a pre-existing bugdir in a temporary directory
+ self.dir = utility.Dir()
+ self.original_working_dir = os.getcwd()
+ os.chdir(self.dir.path)
+ self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False,
+ allow_rcs_init=True)
+ self.bugdir.new_bug("preexisting", summary="Hopefully not imported")
+ self.bugdir.save()
+ def tearDown(self):
+ os.chdir(self.original_working_dir)
+ self.bugdir.cleanup()
+ self.dir.cleanup()
+ def testOnDiskCleanLoad(self):
+ """SimpleBugDir(sync_with_disk==True) should not import preexisting bugs."""
+ bugdir = SimpleBugDir(sync_with_disk=True)
+ self.failUnless(bugdir.sync_with_disk==True, bugdir.sync_with_disk)
+ uuids = sorted([bug.uuid for bug in bugdir])
+ self.failUnless(uuids == ['a', 'b'], uuids)
+ bugdir._clear_bugs()
+ uuids = sorted([bug.uuid for bug in bugdir])
+ self.failUnless(uuids == [], uuids)
+ bugdir.load_all_bugs()
+ uuids = sorted([bug.uuid for bug in bugdir])
+ self.failUnless(uuids == ['a', 'b'], uuids)
+ bugdir.cleanup()
+ def testInMemoryCleanLoad(self):
+ """SimpleBugDir(sync_with_disk==False) should not import preexisting bugs."""
+ bugdir = SimpleBugDir(sync_with_disk=False)
+ self.failUnless(bugdir.sync_with_disk==False, bugdir.sync_with_disk)
+ uuids = sorted([bug.uuid for bug in bugdir])
+ self.failUnless(uuids == ['a', 'b'], uuids)
+ self.failUnlessRaises(DiskAccessRequired, bugdir.load_all_bugs)
+ uuids = sorted([bug.uuid for bug in bugdir])
+ self.failUnless(uuids == ['a', 'b'], uuids)
+ bugdir._clear_bugs()
+ uuids = sorted([bug.uuid for bug in bugdir])
+ self.failUnless(uuids == [], uuids)
+ bugdir.cleanup()
+
+unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
diff --git a/libbe/bzr.py b/libbe/bzr.py
index d73392a..9eb7de6 100644
--- a/libbe/bzr.py
+++ b/libbe/bzr.py
@@ -2,21 +2,20 @@
# Ben Finney <ben+python@benfinney.id.au>
# Marien Zwart <marienz@gentoo.org>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import re
@@ -72,9 +71,21 @@ class Bzr(RCS):
else:
self._u_invoke_client("branch", "--revision", revision,
".", directory)
- def _rcs_commit(self, commitfile):
- status,output,error = self._u_invoke_client("commit", "--unchanged",
- "--file", commitfile)
+ def _rcs_commit(self, commitfile, allow_empty=False):
+ args = ["commit", "--file", commitfile]
+ if allow_empty == True:
+ args.append("--unchanged")
+ status,output,error = self._u_invoke_client(*args)
+ else:
+ kwargs = {"expect":(0,3)}
+ status,output,error = self._u_invoke_client(*args, **kwargs)
+ if status != 0:
+ strings = ["ERROR: no changes to commit.", # bzr 1.3.1
+ "ERROR: No changes to commit."] # bzr 1.15.1
+ if self._u_any_in_string(strings, error) == True:
+ raise rcs.EmptyCommit()
+ else:
+ raise rcs.CommandError(args, status, error)
revision = None
revline = re.compile("Committed revision (.*)[.]")
match = revline.search(error)
@@ -82,20 +93,14 @@ class Bzr(RCS):
assert len(match.groups()) == 1
revision = match.groups()[0]
return revision
- def postcommit(self):
- try:
- self._u_invoke_client('merge')
- except rcs.CommandError, e:
- if ('No merge branch known or specified' in e.err_str or
- 'No merge location known or specified' in e.err_str):
- pass
- else:
- self._u_invoke_client('revert', '--no-backup',
- directory=directory)
- self._u_invoke_client('resolve', '--all', directory=directory)
- raise
- if len(self._u_invoke_client('status', directory=directory)[1]) > 0:
- self.commit('Merge from upstream')
+ def _rcs_revision_id(self, index):
+ status,output,error = self._u_invoke_client("revno")
+ current_revision = int(output)
+ if index >= current_revision or index < -current_revision:
+ return None
+ if index >= 0:
+ return str(index+1) # bzr commit 0 is the empty tree.
+ return str(current_revision+index+1)
rcs.make_rcs_testcase_subclasses(Bzr, sys.modules[__name__])
diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py
index edc6442..94a6856 100644
--- a/libbe/cmdutil.py
+++ b/libbe/cmdutil.py
@@ -1,21 +1,21 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import glob
import optparse
import os
from textwrap import TextWrapper
@@ -32,10 +32,10 @@ class UserError(Exception):
def __init__(self, msg):
Exception.__init__(self, msg)
-class UserErrorWrap(UserError):
- def __init__(self, exception):
- UserError.__init__(self, str(exception))
- self.exception = exception
+class UnknownCommand(UserError):
+ def __init__(self, cmd):
+ Exception.__init__(self, "Unknown command '%s'" % cmd)
+ self.cmd = cmd
class UsageError(Exception):
pass
@@ -56,24 +56,30 @@ def iter_commands():
def get_command(command_name):
"""Retrieves the module for a user command
- >>> get_command("asdf")
- Traceback (most recent call last):
- UserError: Unknown command asdf
+ >>> try:
+ ... get_command("asdf")
+ ... except UnknownCommand, e:
+ ... print e
+ Unknown command 'asdf'
>>> repr(get_command("list")).startswith("<module 'becommands.list' from ")
True
"""
cmd = plugin.get_plugin("becommands", command_name.replace("-", "_"))
if cmd is None:
- raise UserError("Unknown command %s" % command_name)
+ raise UnknownCommand(command_name)
return cmd
-def execute(cmd, args):
+def execute(cmd, args, manipulate_encodings=True):
enc = encoding.get_encoding()
- get_command(cmd).execute([a.decode(enc) for a in args])
- return 0
-
-def help(cmd=None):
+ cmd = get_command(cmd)
+ ret = cmd.execute([a.decode(enc) for a in args],
+ manipulate_encodings=manipulate_encodings)
+ if ret == None:
+ ret = 0
+ return ret
+
+def help(cmd=None, parser=None):
if cmd != None:
return get_command(cmd).help()
else:
@@ -82,17 +88,15 @@ def help(cmd=None):
cmdlist.append((name, module.__desc__))
longest_cmd_len = max([len(name) for name,desc in cmdlist])
ret = ["Bugs Everywhere - Distributed bug tracking",
- "",
- "usage: be [command] [command_options ...] [command_args ...]",
- "or: be help",
- "or: be help [command]",
- "",
- "Supported commands"]
+ "", "Supported commands"]
for name, desc in cmdlist:
numExtraSpaces = longest_cmd_len-len(name)
ret.append("be %s%*s %s" % (name, numExtraSpaces, "", desc))
-
- return "\n".join(ret)
+ ret.extend(["", "Run", " be help [command]", "for more information."])
+ longhelp = "\n".join(ret)
+ if parser == None:
+ return longhelp
+ return parser.help_str() + "\n" + longhelp
def completions(cmd):
parser = get_command(cmd).get_parser()
@@ -106,6 +110,13 @@ def raise_get_help(option, opt, value, parser):
def raise_get_completions(option, opt, value, parser):
print "got completion arg"
+ if hasattr(parser, "command") and parser.command == "be":
+ comps = []
+ for command, module in iter_commands():
+ comps.append(command)
+ for opt in parser.option_list:
+ comps.append(opt.get_opt_string())
+ raise GetCompletions(comps)
raise GetCompletions(completions(sys.argv[1]))
class CmdOptionParser(optparse.OptionParser):
@@ -141,7 +152,7 @@ def option_value_pairs(options, parser):
def default_complete(options, args, parser, bugid_args={}):
"""
- A dud complete implementation for becommands to that the
+ A dud complete implementation for becommands so that the
--complete argument doesn't cause any problems. Use this
until you've set up a command-specific complete function.
@@ -149,15 +160,25 @@ def default_complete(options, args, parser, bugid_args={}):
arguments taking bug shortnames and the values are functions for
filtering, since that's a common enough operation.
e.g. for "be open [options] BUGID"
- bugid_args = {0: lambda bug : bug.active == False}
+ bugid_args = {0: lambda bug : bug.active == False}
+ A positional argument of -1 specifies all remaining arguments
+ (e.g in the case of "be show BUGID BUGID ...").
"""
for option,value in option_value_pairs(options, parser):
if value == "--complete":
- raise cmdutil.GetCompletions()
+ raise GetCompletions()
+ if len(bugid_args.keys()) > 0:
+ max_pos_arg = max(bugid_args.keys())
+ else:
+ max_pos_arg = -1
for pos,value in enumerate(args):
if value == "--complete":
+ filter = None
if pos in bugid_args:
filter = bugid_args[pos]
+ if pos > max_pos_arg and -1 in bugid_args:
+ filter = bugid_args[-1]
+ if filter != None:
bugshortnames = []
try:
bd = bugdir.BugDir(from_disk=True,
@@ -170,6 +191,13 @@ def default_complete(options, args, parser, bugid_args={}):
raise GetCompletions(bugshortnames)
raise GetCompletions()
+def complete_path(path):
+ """List possible path completions for path."""
+ comps = glob.glob(path+"*") + glob.glob(path+"/*")
+ if len(comps) == 1 and os.path.isdir(comps[0]):
+ comps.extend(glob.glob(comps[0]+"/*"))
+ return comps
+
def underlined(instring):
"""Produces a version of a string that is underlined with '='
@@ -179,6 +207,15 @@ def underlined(instring):
return "%s\n%s" % (instring, "="*len(instring))
+def bug_from_shortname(bugdir, shortname):
+ """
+ Exception translation for the command-line interface.
+ """
+ try:
+ bug = bugdir.bug_from_shortname(shortname)
+ except (bugdir.MultipleBugMatches, bugdir.NoBugMatches), e:
+ raise UserError(e.message)
+ return bug
def _test():
import doctest
diff --git a/libbe/comment.py b/libbe/comment.py
index 9d4f744..b2fc556 100644
--- a/libbe/comment.py
+++ b/libbe/comment.py
@@ -2,28 +2,31 @@
# Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org>
# Thomas Habets <thomas@habets.pp.se>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA 02110-1301, USA
-import email.mime.base, email.encoders
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import base64
import os
import os.path
+import sys
import time
+import types
+try: # import core module, Python >= 2.5
+ from xml.etree import ElementTree
+except ImportError: # look for non-core module
+ from elementtree import ElementTree
import xml.sax.saxutils
-import textwrap
import doctest
from beuuid import uuid_gen
@@ -43,16 +46,38 @@ class InvalidShortname(KeyError):
self.shortname = shortname
self.shortnames = shortnames
+class InvalidXML(ValueError):
+ def __init__(self, element, comment):
+ msg = "Invalid comment xml: %s\n %s\n" \
+ % (comment, ElementTree.tostring(element))
+ ValueError.__init__(self, msg)
+ self.element = element
+ self.comment = comment
+
+class MissingReference(ValueError):
+ def __init__(self, comment):
+ msg = "Missing reference to %s" % (comment.in_reply_to)
+ ValueError.__init__(self, msg)
+ self.reference = comment.in_reply_to
+ self.comment = comment
+
+class DiskAccessRequired (Exception):
+ def __init__(self, goal):
+ msg = "Cannot %s without accessing the disk" % goal
+ Exception.__init__(self, msg)
INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!"
-def _list_to_root(comments, bug):
+def list_to_root(comments, bug, root=None,
+ ignore_missing_references=False):
"""
- Convert a raw list of comments to single (dummy) root comment. We
- use a dummy root comment, because there can be several comment
- threads rooted on the same parent bug. To simplify comment
- interaction, we condense these threads into a single thread with a
- Comment dummy root.
+ Convert a raw list of comments to single root comment. We use a
+ dummy root comment by default, because there can be several
+ comment threads rooted on the same parent bug. To simplify
+ comment interaction, we condense these threads into a single
+ thread with a Comment dummy root. Can also be used to append
+ a list of subcomments to a non-dummy root comment, so long as
+ all the new comments are descendants of the root comment.
No Comment method should use the dummy comment.
"""
@@ -61,23 +86,42 @@ def _list_to_root(comments, bug):
for comment in comments:
assert comment.uuid != None
uuid_map[comment.uuid] = comment
+ for comment in comments:
+ if comment.alt_id != None and comment.alt_id not in uuid_map:
+ uuid_map[comment.alt_id] = comment
+ if root == None:
+ root = Comment(bug, uuid=INVALID_UUID)
+ else:
+ uuid_map[root.uuid] = root
for comm in comments:
+ if comm.in_reply_to == INVALID_UUID:
+ comm.in_reply_to = None
rep = comm.in_reply_to
- if rep == None or rep == settings_object.EMPTY or rep == bug.uuid:
+ if rep == None or rep == bug.uuid:
root_comments.append(comm)
else:
parentUUID = comm.in_reply_to
- parent = uuid_map[parentUUID]
- parent.add_reply(comm)
- dummy_root = Comment(bug, uuid=INVALID_UUID)
- dummy_root.extend(root_comments)
- return dummy_root
+ try:
+ parent = uuid_map[parentUUID]
+ parent.add_reply(comm)
+ except KeyError, e:
+ if ignore_missing_references == True:
+ print >> sys.stderr, \
+ "Ignoring missing reference to %s" % parentUUID
+ comm.in_reply_to = None
+ root_comments.append(comm)
+ else:
+ raise MissingReference(comm)
+ root.extend(root_comments)
+ return root
def loadComments(bug, load_full=False):
"""
Set load_full=True when you want to load the comment completely
from disk *now*, rather than waiting and lazy loading as required.
"""
+ if bug.sync_with_disk == False:
+ raise DiskAccessRequired("load comments")
path = bug.get_path("comments")
if not os.path.isdir(path):
return Comment(bug, uuid=INVALID_UUID)
@@ -86,15 +130,16 @@ def loadComments(bug, load_full=False):
if uuid.startswith('.'):
continue
comm = Comment(bug, uuid, from_disk=True)
+ comm.set_sync_with_disk(bug.sync_with_disk)
if load_full == True:
comm.load_settings()
dummy = comm.body # force the body to load
comments.append(comm)
- return _list_to_root(comments, bug)
+ return list_to_root(comments, bug)
def saveComments(bug):
- path = bug.get_path("comments")
- bug.rcs.mkdir(path)
+ if bug.sync_with_disk == False:
+ raise DiskAccessRequired("save comments")
for comment in bug.comment_root.traverse():
comment.save()
@@ -122,9 +167,13 @@ class Comment(Tree, settings_object.SavedSettingsObject):
kwargs["required_saved_properties"]=required_saved_properties
return settings_object.versioned_property(**kwargs)
- @_versioned_property(name="From",
+ @_versioned_property(name="Alt-id",
+ doc="Alternate ID for linking imported comments. Internally comments are linked (via In-reply-to) to the parent's UUID. However, these UUIDs are generated internally, so Alt-id is provided as a user-controlled linking target.")
+ def alt_id(): return {}
+
+ @_versioned_property(name="Author",
doc="The author of the comment")
- def From(): return {}
+ def author(): return {}
@_versioned_property(name="In-reply-to",
doc="UUID for parent comment or bug")
@@ -138,17 +187,17 @@ class Comment(Tree, settings_object.SavedSettingsObject):
@_versioned_property(name="Date",
doc="An RFC 2822 timestamp for comment creation")
- def time_string(): return {}
+ def date(): return {}
def _get_time(self):
- if self.time_string == None:
+ if self.date == None:
return None
- return utility.str_to_time(self.time_string)
+ return utility.str_to_time(self.date)
def _set_time(self, value):
- self.time_string = utility.time_to_str(value)
+ self.date = utility.time_to_str(value)
time = property(fget=_get_time,
fset=_set_time,
- doc="An integer version of .time_string")
+ doc="An integer version of .date")
def _get_comment_body(self):
if self.rcs != None and self.sync_with_disk == True:
@@ -178,6 +227,20 @@ class Comment(Tree, settings_object.SavedSettingsObject):
@doc_property(doc="A revision control system instance.")
def rcs(): return {}
+ def _extra_strings_check_fn(value):
+ return utility.iterable_full_of_strings(value, \
+ alternative=settings_object.EMPTY)
+ def _extra_strings_change_hook(self, old, new):
+ self.extra_strings.sort() # to make merging easier
+ self._prop_save_settings(old, new)
+ @_versioned_property(name="extra_strings",
+ doc="Space for an array of extra strings. Useful for storing state for functionality implemented purely in becommands/<some_function>.py.",
+ default=[],
+ check_fn=_extra_strings_check_fn,
+ change_hook=_extra_strings_change_hook,
+ mutable=True)
+ def extra_strings(): return {}
+
def __init__(self, bug=None, uuid=None, from_disk=False,
in_reply_to=None, body=None):
"""
@@ -204,10 +267,29 @@ class Comment(Tree, settings_object.SavedSettingsObject):
self.uuid = uuid_gen()
self.time = int(time.time()) # only save to second precision
if self.rcs != None:
- self.From = self.rcs.get_user_id()
+ self.author = self.rcs.get_user_id()
self.in_reply_to = in_reply_to
self.body = body
+ def __cmp__(self, other):
+ return cmp_full(self, other)
+
+ def __str__(self):
+ """
+ >>> comm = Comment(bug=None, body="Some insightful remarks")
+ >>> comm.uuid = "com-1"
+ >>> comm.date = "Thu, 20 Nov 2008 15:55:11 +0000"
+ >>> comm.author = "Jane Doe <jdoe@example.com>"
+ >>> print comm
+ --------- Comment ---------
+ Name: com-1
+ From: Jane Doe <jdoe@example.com>
+ Date: Thu, 20 Nov 2008 15:55:11 +0000
+ <BLANKLINE>
+ Some insightful remarks
+ """
+ return self.string()
+
def traverse(self, *args, **kwargs):
"""Avoid working with the possible dummy root comment"""
for comment in Tree.traverse(self, *args, **kwargs):
@@ -215,23 +297,24 @@ class Comment(Tree, settings_object.SavedSettingsObject):
continue
yield comment
+ # serializing methods
+
def _setting_attr_string(self, setting):
value = getattr(self, setting)
- if value == settings_object.EMPTY:
+ if value == None:
return ""
- else:
- return str(value)
+ return str(value)
def xml(self, indent=0, shortname=None):
"""
>>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
>>> comm.uuid = "0123"
- >>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000"
+ >>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000"
>>> print comm.xml(indent=2, shortname="com-1")
<comment>
<uuid>0123</uuid>
<short-name>com-1</short-name>
- <from></from>
+ <author></author>
<date>Thu, 01 Jan 1970 00:00:00 +0000</date>
<content-type>text/plain</content-type>
<body>Some
@@ -248,27 +331,88 @@ class Comment(Tree, settings_object.SavedSettingsObject):
msg = email.mime.base.MIMEBase(maintype, subtype)
msg.set_payload(self.body or "")
email.encoders.encode_base64(msg)
- body = msg.as_string()
+ body = base64.encodestring(self.body or "")
info = [("uuid", self.uuid),
+ ("alt-id", self.alt_id),
("short-name", shortname),
("in-reply-to", self.in_reply_to),
- ("from", self._setting_attr_string("From")),
- ("date", self.time_string),
+ ("author", self._setting_attr_string("author")),
+ ("date", self.date),
("content-type", self.content_type),
("body", body)]
lines = ["<comment>"]
for (k,v) in info:
- if v not in [settings_object.EMPTY, None]:
+ if v != None:
lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
lines.append("</comment>")
istring = ' '*indent
sep = '\n' + istring
return istring + sep.join(lines).rstrip('\n')
+ def from_xml(self, xml_string, verbose=True):
+ """
+ Note: If alt-id is not given, translates any <uuid> fields to
+ <alt-id> fields.
+ >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
+ >>> commA.uuid = "0123"
+ >>> commA.date = "Thu, 01 Jan 1970 00:00:00 +0000"
+ >>> xml = commA.xml(shortname="com-1")
+ >>> commB = Comment()
+ >>> commB.from_xml(xml)
+ >>> attrs=['uuid','alt_id','in_reply_to','author','date','content_type','body']
+ >>> for attr in attrs: # doctest: +ELLIPSIS
+ ... if getattr(commB, attr) != getattr(commA, attr):
+ ... estr = "Mismatch on %s: '%s' should be '%s'"
+ ... args = (attr, getattr(commB, attr), getattr(commA, attr))
+ ... print estr % args
+ Mismatch on uuid: '...' should be '0123'
+ Mismatch on alt_id: '0123' should be 'None'
+ >>> print commB.alt_id
+ 0123
+ >>> commA.author
+ >>> commB.author
+ """
+ if type(xml_string) == types.UnicodeType:
+ xml_string = xml_string.strip().encode("unicode_escape")
+ comment = ElementTree.XML(xml_string)
+ if comment.tag != "comment":
+ raise InvalidXML(comment, "root element must be <comment>")
+ tags=['uuid','alt-id','in-reply-to','author','date','content-type','body']
+ uuid = None
+ body = None
+ for child in comment.getchildren():
+ if child.tag == "short-name":
+ pass
+ elif child.tag in tags:
+ if child.text == None or len(child.text) == 0:
+ text = settings_object.EMPTY
+ else:
+ text = xml.sax.saxutils.unescape(child.text)
+ text = unicode(text).decode("unicode_escape").strip()
+ if child.tag == "uuid":
+ uuid = text
+ continue # don't set the bug's uuid tag.
+ if child.tag == "body":
+ body = text
+ continue # don't set the bug's body yet.
+ else:
+ attr_name = child.tag.replace('-','_')
+ setattr(self, attr_name, text)
+ elif verbose == True:
+ print >> sys.stderr, "Ignoring unknown tag %s in %s" \
+ % (child.tag, comment.tag)
+ if self.alt_id == None and uuid not in [None, self.uuid]:
+ self.alt_id = uuid
+ if body != None:
+ if self.content_type.startswith("text/"):
+ self.body = body+"\n" # restore trailing newline
+ else:
+ self.body = base64.decodestring(body)
+
def string(self, indent=0, shortname=None):
"""
>>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
- >>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000"
+ >>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000"
>>> print comm.string(indent=2, shortname="com-1")
--------- Comment ---------
Name: com-1
@@ -284,87 +428,18 @@ class Comment(Tree, settings_object.SavedSettingsObject):
lines = []
lines.append("--------- Comment ---------")
lines.append("Name: %s" % shortname)
- lines.append("From: %s" % (self._setting_attr_string("From")))
- lines.append("Date: %s" % self.time_string)
+ lines.append("From: %s" % (self._setting_attr_string("author")))
+ lines.append("Date: %s" % self.date)
lines.append("")
- #lines.append(textwrap.fill(self.body or "",
- # width=(79-indent)))
if self.content_type.startswith("text/"):
lines.extend((self.body or "").splitlines())
else:
lines.append("Content type %s not printable. Try XML output instead" % self.content_type)
- # some comments shouldn't be wrapped...
istring = ' '*indent
sep = '\n' + istring
return istring + sep.join(lines).rstrip('\n')
- def __str__(self):
- """
- >>> comm = Comment(bug=None, body="Some insightful remarks")
- >>> comm.uuid = "com-1"
- >>> comm.time_string = "Thu, 20 Nov 2008 15:55:11 +0000"
- >>> comm.From = "Jane Doe <jdoe@example.com>"
- >>> print comm
- --------- Comment ---------
- Name: com-1
- From: Jane Doe <jdoe@example.com>
- Date: Thu, 20 Nov 2008 15:55:11 +0000
- <BLANKLINE>
- Some insightful remarks
- """
- return self.string()
-
- def get_path(self, name=None):
- my_dir = os.path.join(self.bug.get_path("comments"), self.uuid)
- if name is None:
- return my_dir
- assert name in ["values", "body"]
- return os.path.join(my_dir, name)
-
- def load_settings(self):
- self.settings = mapfile.map_load(self.rcs, self.get_path("values"))
- self._setup_saved_settings()
-
- def save_settings(self):
- parent_dir = os.path.dirname(self.get_path())
- self.rcs.mkdir(parent_dir)
- self.rcs.mkdir(self.get_path())
- path = self.get_path("values")
- mapfile.map_save(self.rcs, path, self._get_saved_settings())
-
- def save(self):
- assert self.body != None, "Can't save blank comment"
- #if self.in_reply_to == None:
- # raise Exception, str(self)+'\n'+str(self.settings)+'\n'+str(self._settings_loaded)
- #assert self.in_reply_to != None, "Comment must be a reply to something"
- self.save_settings()
- self._set_comment_body(new=self.body, force=True)
-
- def remove(self):
- for comment in self.traverse():
- path = comment.get_path()
- self.rcs.recursive_remove(path)
-
- def add_reply(self, reply, allow_time_inversion=False):
- if self.uuid != INVALID_UUID:
- reply.in_reply_to = self.uuid
- self.append(reply)
- #raise Exception, "adding reply \n%s\n%s" % (self, reply)
-
- def new_reply(self, body=None):
- """
- >>> comm = Comment(bug=None, body="Some insightful remarks")
- >>> repA = comm.new_reply("Critique original comment")
- >>> repB = repA.new_reply("Begin flamewar :p")
- >>> repB.in_reply_to == repA.uuid
- True
- """
- reply = Comment(self.bug, body=body)
- self.add_reply(reply)
- #raise Exception, "new reply added (%s),\n%s\n%s\n\t--%s--" % (body, self, reply, reply.in_reply_to)
- return reply
-
def string_thread(self, string_method_name="string", name_map={},
indent=0, flatten=True,
auto_name_map=False, bug_shortname=None):
@@ -382,7 +457,7 @@ class Comment(Tree, settings_object.SavedSettingsObject):
name_map = {}
for shortname,comment in comm.comment_shortnames(bug_shortname):
name_map[comment.uuid] = shortname
- comm.sort(key=lambda c : c.From) # your sort
+ comm.sort(key=lambda c : c.author) # your sort
comm.string_thread(name_map=name_map)
>>> a = Comment(bug=None, uuid="a", body="Insightful remarks")
@@ -469,6 +544,80 @@ class Comment(Tree, settings_object.SavedSettingsObject):
indent=indent, auto_name_map=auto_name_map,
bug_shortname=bug_shortname)
+ # methods for saving/loading/acessing settings and properties.
+
+ def get_path(self, name=None):
+ my_dir = os.path.join(self.bug.get_path("comments"), self.uuid)
+ if name is None:
+ return my_dir
+ assert name in ["values", "body"]
+ return os.path.join(my_dir, name)
+
+ def set_sync_with_disk(self, value):
+ self.sync_with_disk = value
+
+ def load_settings(self):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("load settings")
+ self.settings = mapfile.map_load(self.rcs, self.get_path("values"))
+ # hack to deal with old BE comments:
+ if "From" in self.settings:
+ self.settings["Author"] = self.settings.pop("From")
+ self._setup_saved_settings()
+
+ def save_settings(self):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("save settings")
+ self.rcs.mkdir(self.get_path())
+ path = self.get_path("values")
+ mapfile.map_save(self.rcs, path, self._get_saved_settings())
+
+ def save(self):
+ """
+ Save any loaded contents to disk.
+
+ However, if self.sync_with_disk = True, then any changes are
+ automatically written to disk as soon as they happen, so
+ calling this method will just waste time (unless something
+ else has been messing with your on-disk files).
+ """
+ sync_with_disk = self.sync_with_disk
+ if sync_with_disk == False:
+ self.set_sync_with_disk(True)
+ assert self.body != None, "Can't save blank comment"
+ self.save_settings()
+ self._set_comment_body(new=self.body, force=True)
+ if sync_with_disk == False:
+ self.set_sync_with_disk(False)
+
+ def remove(self):
+ if self.sync_with_disk == False and self.uuid != INVALID_UUID:
+ raise DiskAccessRequired("remove")
+ for comment in self.traverse():
+ path = comment.get_path()
+ self.rcs.recursive_remove(path)
+
+ def add_reply(self, reply, allow_time_inversion=False):
+ if self.uuid != INVALID_UUID:
+ reply.in_reply_to = self.uuid
+ self.append(reply)
+
+ def new_reply(self, body=None):
+ """
+ >>> comm = Comment(bug=None, body="Some insightful remarks")
+ >>> repA = comm.new_reply("Critique original comment")
+ >>> repB = repA.new_reply("Begin flamewar :p")
+ >>> repB.in_reply_to == repA.uuid
+ True
+ """
+ reply = Comment(self.bug, body=body)
+ if self.bug != None:
+ reply.set_sync_with_disk(self.bug.sync_with_disk)
+ if reply.sync_with_disk == True:
+ reply.save()
+ self.add_reply(reply)
+ return reply
+
def comment_shortnames(self, bug_shortname=None):
"""
Iterate through (id, comment) pairs, in time order.
@@ -535,4 +684,59 @@ class Comment(Tree, settings_object.SavedSettingsObject):
return comment
raise KeyError(uuid)
+def cmp_attr(comment_1, comment_2, attr, invert=False):
+ """
+ Compare a general attribute between two comments using the conventional
+ comparison rule for that attribute type. If invert == True, sort
+ *against* that convention.
+ >>> attr="author"
+ >>> commentA = Comment()
+ >>> commentB = Comment()
+ >>> commentA.author = "John Doe"
+ >>> commentB.author = "Jane Doe"
+ >>> cmp_attr(commentA, commentB, attr) > 0
+ True
+ >>> cmp_attr(commentA, commentB, attr, invert=True) < 0
+ True
+ >>> commentB.author = "John Doe"
+ >>> cmp_attr(commentA, commentB, attr) == 0
+ True
+ """
+ if not hasattr(comment_2, attr) :
+ return 1
+ val_1 = getattr(comment_1, attr)
+ val_2 = getattr(comment_2, attr)
+ if val_1 == None: val_1 = None
+ if val_2 == None: val_2 = None
+
+ if invert == True :
+ return -cmp(val_1, val_2)
+ else :
+ return cmp(val_1, val_2)
+
+# alphabetical rankings (a < z)
+cmp_uuid = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "uuid")
+cmp_author = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "author")
+cmp_in_reply_to = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "in_reply_to")
+cmp_content_type = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "content_type")
+cmp_body = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "body")
+# chronological rankings (newer < older)
+cmp_time = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "time", invert=True)
+
+DEFAULT_CMP_FULL_CMP_LIST = \
+ (cmp_time, cmp_author, cmp_content_type, cmp_body, cmp_in_reply_to,
+ cmp_uuid)
+
+class CommentCompoundComparator (object):
+ def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST):
+ self.cmp_list = cmp_list
+ def __call__(self, comment_1, comment_2):
+ for comparison in self.cmp_list :
+ val = comparison(comment_1, comment_2)
+ if val != 0 :
+ return val
+ return 0
+
+cmp_full = CommentCompoundComparator()
+
suite = doctest.DocTestSuite()
diff --git a/libbe/config.py b/libbe/config.py
index 7f600a5..5e343b9 100644
--- a/libbe/config.py
+++ b/libbe/config.py
@@ -1,20 +1,19 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import ConfigParser
import codecs
import locale
diff --git a/libbe/darcs.py b/libbe/darcs.py
index 43af99a..0720ed9 100644
--- a/libbe/darcs.py
+++ b/libbe/darcs.py
@@ -1,25 +1,30 @@
# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import codecs
import os
import re
import sys
-import unittest
+try: # import core module, Python >= 2.5
+ from xml.etree import ElementTree
+except ImportError: # look for non-core module
+ from elementtree import ElementTree
+from xml.sax.saxutils import unescape
import doctest
+import unittest
import rcs
from rcs import RCS
@@ -131,30 +136,43 @@ class Darcs(RCS):
RCS._rcs_duplicate_repo(self, directory, revision)
else:
self._u_invoke_client("put", "--to-patch", revision, directory)
- def _rcs_commit(self, commitfile):
+ def _rcs_commit(self, commitfile, allow_empty=False):
id = self.get_user_id()
if '@' not in id:
id = "%s <%s@invalid.com>" % (id, id)
- # Darcs doesn't like commitfiles without trailing endlines.
- f = codecs.open(commitfile, 'r', self.encoding)
- contents = f.read()
- f.close()
- if contents[-1] != '\n':
- f = codecs.open(commitfile, 'a', self.encoding)
- f.write('\n')
- f.close()
- status,output,error = self._u_invoke_client('record', '--all',
- '--author', id,
- '--logfile', commitfile)
- revision = None
-
- revline = re.compile("Finished recording patch '(.*)'")
- match = revline.search(output)
- assert match != None, output+error
- assert len(match.groups()) == 1
- revision = match.groups()[0]
+ args = ['record', '--all', '--author', id, '--logfile', commitfile]
+ status,output,error = self._u_invoke_client(*args)
+ empty_strings = ["No changes!"]
+ if self._u_any_in_string(empty_strings, output) == True:
+ if allow_empty == False:
+ raise rcs.EmptyCommit()
+ # note that darcs does _not_ make an empty revision.
+ # this returns the last non-empty revision id...
+ revision = self._rcs_revision_id(-1)
+ else:
+ revline = re.compile("Finished recording patch '(.*)'")
+ match = revline.search(output)
+ assert match != None, output+error
+ assert len(match.groups()) == 1
+ revision = match.groups()[0]
return revision
-
+ def _rcs_revision_id(self, index):
+ status,output,error = self._u_invoke_client("changes", "--xml")
+ revisions = []
+ xml_str = output.encode("unicode_escape").replace(r"\n", "\n")
+ element = ElementTree.XML(xml_str)
+ assert element.tag == "changelog", element.tag
+ for patch in element.getchildren():
+ assert patch.tag == "patch", patch.tag
+ for child in patch.getchildren():
+ if child.tag == "name":
+ text = unescape(unicode(child.text).decode("unicode_escape").strip())
+ revisions.append(text)
+ revisions.reverse()
+ try:
+ return revisions[index]
+ except IndexError:
+ return None
rcs.make_rcs_testcase_subclasses(Darcs, sys.modules[__name__])
diff --git a/libbe/diff.py b/libbe/diff.py
index a349e14..1b52620 100644
--- a/libbe/diff.py
+++ b/libbe/diff.py
@@ -1,117 +1,415 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Compare two bug trees"""
-from libbe import cmdutil, bugdir, bug
+from libbe import bugdir, bug, settings_object, tree
from libbe.utility import time_to_str
+import difflib
import doctest
-def diff(old_bugdir, new_bugdir):
- added = []
- removed = []
- modified = []
- for uuid in old_bugdir.list_uuids():
- old_bug = old_bugdir.bug_from_uuid(uuid)
- try:
- new_bug = new_bugdir.bug_from_uuid(uuid)
- if old_bug != new_bug:
- modified.append((old_bug, new_bug))
- except KeyError:
- removed.append(old_bug)
- for uuid in new_bugdir.list_uuids():
- if not old_bugdir.has_bug(uuid):
- new_bug = new_bugdir.bug_from_uuid(uuid)
- added.append(new_bug)
- return (removed, modified, added)
+class DiffTree (tree.Tree):
+ """
+ A tree holding difference data for easy report generation.
+ >>> bugdir = DiffTree("bugdir")
+ >>> bdsettings = DiffTree("settings", data="target: None -> 1.0")
+ >>> bugdir.append(bdsettings)
+ >>> bugs = DiffTree("bugs", "bug-count: 5 -> 6")
+ >>> bugdir.append(bugs)
+ >>> new = DiffTree("new", "new bugs: ABC, DEF")
+ >>> bugs.append(new)
+ >>> rem = DiffTree("rem", "removed bugs: RST, UVW")
+ >>> bugs.append(rem)
+ >>> print bugdir.report_string()
+ target: None -> 1.0
+ bug-count: 5 -> 6
+ new bugs: ABC, DEF
+ removed bugs: RST, UVW
+ >>> print "\\n".join(bugdir.paths())
+ bugdir
+ bugdir/settings
+ bugdir/bugs
+ bugdir/bugs/new
+ bugdir/bugs/rem
+ >>> bugdir.child_by_path("/") == bugdir
+ True
+ >>> bugdir.child_by_path("/bugs") == bugs
+ True
+ >>> bugdir.child_by_path("/bugs/rem") == rem
+ True
+ >>> bugdir.child_by_path("bugdir") == bugdir
+ True
+ >>> bugdir.child_by_path("bugdir/") == bugdir
+ True
+ >>> bugdir.child_by_path("bugdir/bugs") == bugs
+ True
+ >>> bugdir.child_by_path("/bugs").masked = True
+ >>> print bugdir.report_string()
+ target: None -> 1.0
+ """
+ def __init__(self, name, data=None, data_part_fn=str,
+ requires_children=False, masked=False):
+ tree.Tree.__init__(self)
+ self.name = name
+ self.data = data
+ self.data_part_fn = data_part_fn
+ self.requires_children = requires_children
+ self.masked = masked
+ def paths(self, parent_path=None):
+ paths = []
+ if parent_path == None:
+ path = self.name
+ else:
+ path = "%s/%s" % (parent_path, self.name)
+ paths.append(path)
+ for child in self:
+ paths.extend(child.paths(path))
+ return paths
+ def child_by_path(self, path):
+ if hasattr(path, "split"): # convert string path to a list of names
+ names = path.split("/")
+ if names[0] == "":
+ names[0] = self.name # replace root with self
+ if len(names) > 1 and names[-1] == "":
+ names = names[:-1] # strip empty tail
+ else: # it was already an array
+ names = path
+ assert len(names) > 0, path
+ if names[0] == self.name:
+ if len(names) == 1:
+ return self
+ for child in self:
+ if names[1] == child.name:
+ return child.child_by_path(names[1:])
+ if len(names) == 1:
+ raise KeyError, "%s doesn't match '%s'" % (names, self.name)
+ raise KeyError, "%s points to child not in %s" % (names, [c.name for c in self])
+ def report_string(self):
+ return "\n".join(self.report())
+ def report(self, root=None, parent=None, depth=0):
+ if root == None:
+ root = self.make_root()
+ if self.masked == True:
+ return None
+ data_part = self.data_part(depth)
+ if self.requires_children == True and len(self) == 0:
+ pass
+ else:
+ self.join(root, parent, data_part)
+ if data_part != None:
+ depth += 1
+ for child in self:
+ child.report(root, self, depth)
+ return root
+ def make_root(self):
+ return []
+ def join(self, root, parent, data_part):
+ if data_part != None:
+ root.append(data_part)
+ def data_part(self, depth, indent=True):
+ if self.data == None:
+ return None
+ if hasattr(self, "_cached_data_part"):
+ return self._cached_data_part
+ data_part = self.data_part_fn(self.data)
+ if indent == True:
+ data_part_lines = data_part.splitlines()
+ indent = " "*(depth)
+ line_sep = "\n"+indent
+ data_part = indent+line_sep.join(data_part_lines)
+ self._cached_data_part = data_part
+ return data_part
-def diff_report(diff_data, bug_dir):
- (removed, modified, added) = diff_data
- def modified_cmp(left, right):
- return bug.cmp_severity(left[1], right[1])
+class Diff (object):
+ """
+ Difference tree generator for BugDirs.
+ >>> import copy
+ >>> bd = bugdir.SimpleBugDir(sync_with_disk=False)
+ >>> bd.user_id = "John Doe <j@doe.com>"
+ >>> bd_new = copy.deepcopy(bd)
+ >>> bd_new.target = "1.0"
+ >>> a = bd_new.bug_from_uuid("a")
+ >>> rep = a.comment_root.new_reply("I'm closing this bug")
+ >>> rep.uuid = "acom"
+ >>> rep.date = "Thu, 01 Jan 1970 00:00:00 +0000"
+ >>> a.status = "closed"
+ >>> b = bd_new.bug_from_uuid("b")
+ >>> bd_new.remove_bug(b)
+ >>> c = bd_new.new_bug("c", "Bug C")
+ >>> d = Diff(bd, bd_new)
+ >>> r = d.report_tree()
+ >>> print "\\n".join(r.paths())
+ bugdir
+ bugdir/settings
+ bugdir/bugs
+ bugdir/bugs/new
+ bugdir/bugs/new/c
+ bugdir/bugs/rem
+ bugdir/bugs/rem/b
+ bugdir/bugs/mod
+ bugdir/bugs/mod/a
+ bugdir/bugs/mod/a/settings
+ bugdir/bugs/mod/a/comments
+ bugdir/bugs/mod/a/comments/new
+ bugdir/bugs/mod/a/comments/new/acom
+ bugdir/bugs/mod/a/comments/rem
+ bugdir/bugs/mod/a/comments/mod
+ >>> print r.report_string()
+ Changed bug directory settings:
+ target: None -> 1.0
+ New bugs:
+ c:om: Bug C
+ Removed bugs:
+ b:cm: Bug B
+ Modified bugs:
+ a:cm: Bug A
+ Changed bug settings:
+ status: open -> closed
+ New comments:
+ from John Doe <j@doe.com> on Thu, 01 Jan 1970 00:00:00 +0000
+ I'm closing this bug...
+ >>> bd.cleanup()
+ """
+ def __init__(self, old_bugdir, new_bugdir):
+ self.old_bugdir = old_bugdir
+ self.new_bugdir = new_bugdir
- added.sort(bug.cmp_severity)
- removed.sort(bug.cmp_severity)
- modified.sort(modified_cmp)
- lines = []
-
- if len(added) > 0:
- lines.append("New bug reports:")
- for bg in added:
- lines.extend(bg.string(shortlist=True).splitlines())
- lines.append("")
+ # data assembly methods
- if len(modified) > 0:
- printed = False
- for old_bug, new_bug in modified:
- change_str = bug_changes(old_bug, new_bug, bug_dir)
- if change_str is None:
- continue
- if not printed:
- printed = True
- lines.append("Modified bug reports:")
- lines.extend(change_str.splitlines())
- if printed == True:
- lines.append("")
-
- if len(removed) > 0:
- lines.append("Removed bug reports:")
- for bg in removed:
- lines.extend(bg.string(shortlist=True).splitlines())
- lines.append("")
-
- return '\n'.join(lines)
-
-def change_lines(old, new, attributes):
- change_list = []
- for attr in attributes:
- old_attr = getattr(old, attr)
- new_attr = getattr(new, attr)
- if old_attr != new_attr:
- change_list.append((attr, old_attr, new_attr))
- if len(change_list) >= 0:
- return change_list
- else:
+ def _changed_bugs(self):
+ """
+ Search for differences in all bugs between .old_bugdir and
+ .new_bugdir. Returns
+ (added_bugs, modified_bugs, removed_bugs)
+ where added_bugs and removed_bugs are lists of added and
+ removed bugs respectively. modified_bugs is a list of
+ (old_bug,new_bug) pairs.
+ """
+ if hasattr(self, "__changed_bugs"):
+ return self.__changed_bugs
+ added = []
+ removed = []
+ modified = []
+ for uuid in self.new_bugdir.list_uuids():
+ new_bug = self.new_bugdir.bug_from_uuid(uuid)
+ try:
+ old_bug = self.old_bugdir.bug_from_uuid(uuid)
+ except KeyError:
+ added.append(new_bug)
+ else:
+ if old_bug.sync_with_disk == True:
+ old_bug.load_comments()
+ if new_bug.sync_with_disk == True:
+ new_bug.load_comments()
+ if old_bug != new_bug:
+ modified.append((old_bug, new_bug))
+ for uuid in self.old_bugdir.list_uuids():
+ if not self.new_bugdir.has_bug(uuid):
+ old_bug = self.old_bugdir.bug_from_uuid(uuid)
+ removed.append(old_bug)
+ added.sort()
+ removed.sort()
+ modified.sort(self._bug_modified_cmp)
+ self.__changed_bugs = (added, modified, removed)
+ return self.__changed_bugs
+ def _bug_modified_cmp(self, left, right):
+ return cmp(left[1], right[1])
+ def _changed_comments(self, old, new):
+ """
+ Search for differences in all loaded comments between the bugs
+ old and new. Returns
+ (added_comments, modified_comments, removed_comments)
+ analogous to ._changed_bugs.
+ """
+ if hasattr(self, "__changed_comments"):
+ if new.uuid in self.__changed_comments:
+ return self.__changed_comments[new.uuid]
+ else:
+ self.__changed_comments = {}
+ added = []
+ removed = []
+ modified = []
+ old.comment_root.sort(key=lambda comm : comm.time)
+ new.comment_root.sort(key=lambda comm : comm.time)
+ old_comment_ids = [c.uuid for c in old.comments()]
+ new_comment_ids = [c.uuid for c in new.comments()]
+ for uuid in new_comment_ids:
+ new_comment = new.comment_from_uuid(uuid)
+ try:
+ old_comment = old.comment_from_uuid(uuid)
+ except KeyError:
+ added.append(new_comment)
+ else:
+ if old_comment != new_comment:
+ modified.append((old_comment, new_comment))
+ for uuid in old_comment_ids:
+ if uuid not in new_comment_ids:
+ new_comment = new.comment_from_uuid(uuid)
+ removed.append(new_comment)
+ self.__changed_comments[new.uuid] = (added, modified, removed)
+ return self.__changed_comments[new.uuid]
+ def _attribute_changes(self, old, new, attributes):
+ """
+ Take two objects old and new, and compare the value of *.attr
+ for attr in the list attribute names. Returns a list of
+ (attr_name, old_value, new_value)
+ tuples.
+ """
+ change_list = []
+ for attr in attributes:
+ old_value = getattr(old, attr)
+ new_value = getattr(new, attr)
+ if old_value != new_value:
+ change_list.append((attr, old_value, new_value))
+ if len(change_list) >= 0:
+ return change_list
return None
+ def _settings_properties_attribute_changes(self, old, new,
+ hidden_properties=[]):
+ properties = sorted(new.settings_properties)
+ for p in hidden_properties:
+ properties.remove(p)
+ attributes = [settings_object.setting_name_to_attr_name(None, p)
+ for p in properties]
+ return self._attribute_changes(old, new, attributes)
+ def _bugdir_attribute_changes(self):
+ return self._settings_properties_attribute_changes( \
+ self.old_bugdir, self.new_bugdir,
+ ["rcs_name"]) # tweaked by bugdir.duplicate_bugdir
+ def _bug_attribute_changes(self, old, new):
+ return self._settings_properties_attribute_changes(old, new)
+ def _comment_attribute_changes(self, old, new):
+ return self._settings_properties_attribute_changes(old, new)
-def bug_changes(old, new, bugs):
- change_list = change_lines(old, new, ("time", "creator", "severity",
- "target", "summary", "status", "assigned"))
+ # report generation methods
- old_comment_ids = [c.uuid for c in old.comments()]
- new_comment_ids = [c.uuid for c in new.comments()]
- change_strings = ["%s: %s -> %s" % f for f in change_list]
- for comment_id in new_comment_ids:
- if comment_id not in old_comment_ids:
- summary = comment_summary(new.comment_from_uuid(comment_id), "new")
- change_strings.append(summary)
- for comment_id in old_comment_ids:
- if comment_id not in new_comment_ids:
- summary = comment_summary(new.comment_from_uuid(comment_id),
- "removed")
- change_strings.append(summary)
+ def report_tree(self, diff_tree=DiffTree):
+ """
+ Pretty bare to make it easy to adjust to specific cases. You
+ can pass in a DiffTree subclass via diff_tree to override the
+ default report assembly process.
+ """
+ if hasattr(self, "__report_tree"):
+ return self.__report_tree
+ bugdir_settings = sorted(self.new_bugdir.settings_properties)
+ bugdir_settings.remove("rcs_name") # tweaked by bugdir.duplicate_bugdir
+ root = diff_tree("bugdir")
+ bugdir_attribute_changes = self._bugdir_attribute_changes()
+ if len(bugdir_attribute_changes) > 0:
+ bugdir = diff_tree("settings", bugdir_attribute_changes,
+ self.bugdir_attribute_change_string)
+ root.append(bugdir)
+ bug_root = diff_tree("bugs")
+ root.append(bug_root)
+ add,mod,rem = self._changed_bugs()
+ bnew = diff_tree("new", "New bugs:", requires_children=True)
+ bug_root.append(bnew)
+ for bug in add:
+ b = diff_tree(bug.uuid, bug, self.bug_add_string)
+ bnew.append(b)
+ brem = diff_tree("rem", "Removed bugs:", requires_children=True)
+ bug_root.append(brem)
+ for bug in rem:
+ b = diff_tree(bug.uuid, bug, self.bug_rem_string)
+ brem.append(b)
+ bmod = diff_tree("mod", "Modified bugs:", requires_children=True)
+ bug_root.append(bmod)
+ for old,new in mod:
+ b = diff_tree(new.uuid, (old,new), self.bug_mod_string)
+ bmod.append(b)
+ bug_attribute_changes = self._bug_attribute_changes(old, new)
+ if len(bug_attribute_changes) > 0:
+ bset = diff_tree("settings", bug_attribute_changes,
+ self.bug_attribute_change_string)
+ b.append(bset)
+ if old.summary != new.summary:
+ data = (old.summary, new.summary)
+ bsum = diff_tree("summary", data, self.bug_summary_change_string)
+ b.append(bsum)
+ cr = diff_tree("comments")
+ b.append(cr)
+ a,m,d = self._changed_comments(old, new)
+ cnew = diff_tree("new", "New comments:", requires_children=True)
+ for comment in a:
+ c = diff_tree(comment.uuid, comment, self.comment_add_string)
+ cnew.append(c)
+ crem = diff_tree("rem", "Removed comments:",requires_children=True)
+ for comment in d:
+ c = diff_tree(comment.uuid, comment, self.comment_rem_string)
+ crem.append(c)
+ cmod = diff_tree("mod","Modified comments:",requires_children=True)
+ for o,n in m:
+ c = diff_tree(n.uuid, (o,n), self.comment_mod_string)
+ cmod.append(c)
+ comm_attribute_changes = self._comment_attribute_changes(o, n)
+ if len(comm_attribute_changes) > 0:
+ cset = diff_tree("settings", comm_attribute_changes,
+ self.comment_attribute_change_string)
+ if o.body != n.body:
+ data = (o.body, n.body)
+ cbody = diff_tree("cbody", data,
+ self.comment_body_change_string)
+ c.append(cbody)
+ cr.extend([cnew, crem, cmod])
+ self.__report_tree = root
+ return self.__report_tree
- if len(change_strings) == 0:
- return None
- return "%s\n %s" % (new.string(shortlist=True),
- " \n".join(change_strings))
+ # change data -> string methods.
+ # Feel free to play with these in subclasses.
+ def attribute_change_string(self, attribute_changes, indent=0):
+ indent_string = " "*indent
+ change_strings = [u"%s: %s -> %s" % f for f in attribute_changes]
+ for i,change_string in enumerate(change_strings):
+ change_strings[i] = indent_string+change_string
+ return u"\n".join(change_strings)
+ def bugdir_attribute_change_string(self, attribute_changes):
+ return "Changed bug directory settings:\n%s" % \
+ self.attribute_change_string(attribute_changes, indent=1)
+ def bug_attribute_change_string(self, attribute_changes):
+ return "Changed bug settings:\n%s" % \
+ self.attribute_change_string(attribute_changes, indent=1)
+ def comment_attribute_change_string(self, attribute_changes):
+ return "Changed comment settings:\n%s" % \
+ self.attribute_change_string(attribute_changes, indent=1)
+ def bug_add_string(self, bug):
+ return bug.string(shortlist=True)
+ def bug_rem_string(self, bug):
+ return bug.string(shortlist=True)
+ def bug_mod_string(self, bugs):
+ old_bug,new_bug = bugs
+ return new_bug.string(shortlist=True)
+ def bug_summary_change_string(self, summaries):
+ old_summary,new_summary = summaries
+ return "summary changed:\n %s\n %s" % (old_summary, new_summary)
+ def _comment_summary_string(self, comment):
+ return "from %s on %s" % (comment.author, time_to_str(comment.time))
+ def comment_add_string(self, comment):
+ summary = self._comment_summary_string(comment)
+ first_line = comment.body.splitlines()[0]
+ return "%s\n %s..." % (summary, first_line)
+ def comment_rem_string(self, comment):
+ summary = self._comment_summary_string(comment)
+ first_line = comment.body.splitlines()[0]
+ return "%s\n %s..." % (summary, first_line)
+ def comment_mod_string(self, comments):
+ old_comment,new_comment = comments
+ return self._comment_summary_string(new_comment)
+ def comment_body_change_string(self, bodies):
+ old_body,new_body = bodies
+ return difflib.unified_diff(old_body, new_body)
-def comment_summary(comment, status):
- return "%8s comment from %s on %s" % (status, comment.From,
- time_to_str(comment.time))
suite = doctest.DocTestSuite()
diff --git a/libbe/editor.py b/libbe/editor.py
index 6638ed9..8f2bdbf 100644
--- a/libbe/editor.py
+++ b/libbe/editor.py
@@ -1,21 +1,19 @@
# Bugs Everywhere, a distributed bugtracker
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA 02110-1301, USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import codecs
import locale
@@ -33,7 +31,7 @@ class CantFindEditor(Exception):
Exception.__init__(self, "Can't find editor to get string from")
def editor_string(comment=None, encoding=None):
- """Invokes the editor, and returns the user_produced text as a string
+ """Invokes the editor, and returns the user-produced text as a string
>>> if "EDITOR" in os.environ:
... del os.environ["EDITOR"]
@@ -64,7 +62,8 @@ def editor_string(comment=None, encoding=None):
fhandle, fname = tempfile.mkstemp()
try:
if comment is not None:
- os.write(fhandle, '\n'+comment_string(comment))
+ cstring = u'\n'+comment_string(comment)
+ os.write(fhandle, cstring.encode(encoding))
os.close(fhandle)
oldmtime = os.path.getmtime(fname)
os.system("%s %s" % (editor, fname))
diff --git a/libbe/encoding.py b/libbe/encoding.py
index bdac98e..4af864e 100644
--- a/libbe/encoding.py
+++ b/libbe/encoding.py
@@ -1,31 +1,33 @@
# Bugs Everywhere, a distributed bugtracker
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA 02110-1301, USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import codecs
import locale
import sys
import doctest
+ENCODING = None # override get_encoding() output by setting this
+
def get_encoding():
"""
Guess a useful input/output/filesystem encoding... Maybe we need
seperate encodings for input/output and filesystem? Hmm...
"""
+ if ENCODING != None:
+ return ENCODING
encoding = locale.getpreferredencoding() or sys.getdefaultencoding()
if sys.platform != 'win32' or sys.version_info[:2] > (2, 3):
encoding = locale.getlocale(locale.LC_TIME)[1] or encoding
diff --git a/libbe/git.py b/libbe/git.py
index 31bbe32..53bc030 100644
--- a/libbe/git.py
+++ b/libbe/git.py
@@ -2,19 +2,19 @@
# Chris Ball <cjb@laptop.org>
# W. Trevor King <wking@drexel.edu>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import re
@@ -53,10 +53,18 @@ class Git(RCS):
def _rcs_init(self, path):
self._u_invoke_client("init", directory=path)
def _rcs_get_user_id(self):
- status,output,error = self._u_invoke_client("config", "user.name")
- name = output.rstrip('\n')
- status,output,error = self._u_invoke_client("config", "user.email")
- email = output.rstrip('\n')
+ status,output,error = \
+ self._u_invoke_client("config", "user.name", expect=(0,1))
+ if status == 0:
+ name = output.rstrip('\n')
+ else:
+ name = ""
+ status,output,error = \
+ self._u_invoke_client("config", "user.email", expect=(0,1))
+ if status == 0:
+ email = output.rstrip('\n')
+ else:
+ email = ""
if name != "" or email != "": # got something!
# guess missing info, if necessary
if name == "":
@@ -93,16 +101,41 @@ class Git(RCS):
#self._u_invoke_client("archive", revision, directory) # makes tarball
self._u_invoke_client("clone", "--no-checkout",".",directory)
self._u_invoke_client("checkout", revision, directory=directory)
- def _rcs_commit(self, commitfile):
- status,output,error = self._u_invoke_client('commit', '-a',
- '-F', commitfile)
+ def _rcs_commit(self, commitfile, allow_empty=False):
+ args = ['commit', '--all', '--file', commitfile]
+ if allow_empty == True:
+ args.append("--allow-empty")
+ status,output,error = self._u_invoke_client(*args)
+ else:
+ kwargs = {"expect":(0,1)}
+ status,output,error = self._u_invoke_client(*args, **kwargs)
+ strings = ["nothing to commit",
+ "nothing added to commit"]
+ if self._u_any_in_string(strings, output) == True:
+ raise rcs.EmptyCommit()
revision = None
revline = re.compile("(.*) (.*)[:\]] (.*)")
match = revline.search(output)
assert match != None, output+error
assert len(match.groups()) == 3
revision = match.groups()[1]
- return revision
+ full_revision = self._rcs_revision_id(-1)
+ assert full_revision.startswith(revision), \
+ "Mismatched revisions:\n%s\n%s" % (revision, full_revision)
+ return full_revision
+ def _rcs_revision_id(self, index):
+ args = ["rev-list", "--first-parent", "--reverse", "HEAD"]
+ kwargs = {"expect":(0,128)}
+ status,output,error = self._u_invoke_client(*args, **kwargs)
+ if status == 128:
+ if error.startswith("fatal: ambiguous argument 'HEAD': unknown "):
+ return None
+ raise rcs.CommandError(args, status, error)
+ commits = output.splitlines()
+ try:
+ return commits[index]
+ except IndexError:
+ return None
rcs.make_rcs_testcase_subclasses(Git, sys.modules[__name__])
diff --git a/libbe/hg.py b/libbe/hg.py
index 30b0470..31df1d0 100644
--- a/libbe/hg.py
+++ b/libbe/hg.py
@@ -2,19 +2,19 @@
# Ben Finney <ben+python@benfinney.id.au>
# W. Trevor King <wking@drexel.edu>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import re
@@ -58,7 +58,7 @@ class Hg(RCS):
def _rcs_add(self, path):
self._u_invoke_client("add", path)
def _rcs_remove(self, path):
- self._u_invoke_client("rm", path)
+ self._u_invoke_client("rm", "--force", path)
def _rcs_update(self, path):
pass
def _rcs_get_file_contents(self, path, revision=None, binary=False):
@@ -73,16 +73,24 @@ class Hg(RCS):
return RCS._rcs_duplicate_repo(self, directory, revision)
else:
self._u_invoke_client("archive", "--rev", revision, directory)
- def _rcs_commit(self, commitfile):
- self._u_invoke_client('commit', '--logfile', commitfile)
- status,output,error = self._u_invoke_client('identify')
- revision = None
- revline = re.compile("(.*) tip")
- match = revline.search(output)
- assert match != None, output+error
- assert len(match.groups()) == 1
- revision = match.groups()[0]
- return revision
+ def _rcs_commit(self, commitfile, allow_empty=False):
+ args = ['commit', '--logfile', commitfile]
+ status,output,error = self._u_invoke_client(*args)
+ if allow_empty == False:
+ strings = ["nothing changed"]
+ if self._u_any_in_string(strings, output) == True:
+ raise rcs.EmptyCommit()
+ return self._rcs_revision_id(-1)
+ def _rcs_revision_id(self, index, style="id"):
+ args = ["identify", "--rev", str(int(index)), "--%s" % style]
+ kwargs = {"expect": (0,255)}
+ status,output,error = self._u_invoke_client(*args, **kwargs)
+ if status == 0:
+ id = output.strip()
+ if id == '000000000000':
+ return None # before initial commit.
+ return id
+ return None
rcs.make_rcs_testcase_subclasses(Hg, sys.modules[__name__])
diff --git a/libbe/mapfile.py b/libbe/mapfile.py
index 9ff2215..b959d76 100644
--- a/libbe/mapfile.py
+++ b/libbe/mapfile.py
@@ -1,20 +1,19 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import yaml
import os.path
import errno
@@ -68,9 +67,9 @@ def generate(map):
assert(':' not in key)
assert(len(key) > 0)
except AssertionError:
- raise IllegalKey(key.encode('string_escape'))
+ raise IllegalKey(unicode(key).encode('unicode_escape'))
if "\n" in map[key]:
- raise IllegalValue(map[key].encode('string_escape'))
+ raise IllegalValue(unicode(map[key]).encode('unicode_escape'))
lines = []
for key in keys:
diff --git a/libbe/plugin.py b/libbe/plugin.py
index 3c7c753..0545fd7 100644
--- a/libbe/plugin.py
+++ b/libbe/plugin.py
@@ -1,21 +1,20 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# Marien Zwart <marienz@gentoo.org>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import os.path
import sys
diff --git a/libbe/properties.py b/libbe/properties.py
index 37204d6..09dd20e 100644
--- a/libbe/properties.py
+++ b/libbe/properties.py
@@ -1,18 +1,19 @@
# Bugs Everywhere - a distributed bugtracker
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
This module provides a series of useful decorators for defining
@@ -51,7 +52,7 @@ def Property(funcs):
args["fset"] = funcs.get("fset", None)
args["fdel"] = funcs.get("fdel", None)
args["doc"] = funcs.get("doc", None)
-
+
#print "Creating a property with"
#for key, val in args.items(): print key, value
return property(**args)
@@ -76,6 +77,9 @@ def local_property(name, null=None, mutable_null=False):
Define get/set access to per-parent-instance local storage. Uses
._<name>_value to store the value for a particular owner instance.
If the ._<name>_value attribute does not exist, returns null.
+
+ If mutable_null == True, we only release deepcopies of the null to
+ the outside world.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -156,20 +160,25 @@ def _get_cached_mutable_property(self, cacher_name, property_name, default=None)
if (cacher_name, property_name) not in self._mutable_property_cache_copy:
return default
return self._mutable_property_cache_copy[(cacher_name, property_name)]
-def _cmp_cached_mutable_property(self, cacher_name, property_name, value):
+def _cmp_cached_mutable_property(self, cacher_name, property_name, value, default=None):
_init_mutable_property_cache(self)
if (cacher_name, property_name) not in self._mutable_property_cache_hash:
- return 1 # any value > non-existant old hash
+ _set_cached_mutable_property(self, cacher_name, property_name, default)
old_hash = self._mutable_property_cache_hash[(cacher_name, property_name)]
return cmp(_hash_mutable_value(value), old_hash)
def defaulting_property(default=None, null=None,
- default_mutable=False,
- null_mutable=False):
+ mutable_default=False):
"""
Define a default value for get access to a property.
If the stored value is null, then default is returned.
+
+ If mutable_default == True, we only release deepcopies of the
+ default to the outside world.
+
+ null should never escape to the outside world, so don't worry
+ about it being a mutable.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -180,17 +189,14 @@ def defaulting_property(default=None, null=None,
def _fget(self):
value = fget(self)
if value == null:
- if default_mutable == True:
+ if mutable_default == True:
return copy.deepcopy(default)
else:
return default
return value
def _fset(self, value):
if value == default:
- if null_mutable == True:
- value = copy.deepcopy(null)
- else:
- value = null
+ value = null
fset(self, value)
funcs["fget"] = _fget
funcs["fset"] = _fset
@@ -260,7 +266,7 @@ def cached_property(generator, initVal=None, mutable=False):
If the input value is no longer initVal (e.g. a value has been
loaded from disk or set with fset), that value overrides any
cached value, and this property has no effect.
-
+
When the cache flag is False and the stored value is initVal, the
generator is not cached, but is called on every fget.
@@ -269,7 +275,7 @@ def cached_property(generator, initVal=None, mutable=False):
In the case that mutable == True, all caching is disabled and the
generator is called whenever the cached value would otherwise be
- used. This avoids uncertainties in the value of stored mutables.
+ used.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -295,7 +301,7 @@ def cached_property(generator, initVal=None, mutable=False):
def primed_property(primer, initVal=None):
"""
- Just like a generator_property, except that instead of returning a
+ Just like a cached_property, except that instead of returning a
new value and running fset to cache it, the primer performs some
background manipulation (e.g. loads data into instance.settings)
such that a _second_ pass through fget succeeds.
@@ -321,7 +327,7 @@ def primed_property(primer, initVal=None):
return funcs
return decorator
-def change_hook_property(hook, mutable=False):
+def change_hook_property(hook, mutable=False, default=None):
"""
Call the function hook(instance, old_value, new_value) whenever a
value different from the current value is set (instance is a a
@@ -330,6 +336,17 @@ def change_hook_property(hook, mutable=False):
called _after_ the new value has been stored, allowing you to
change the stored value if you want.
+ In the case of mutables, things are slightly trickier. Because
+ the property-owning class has no way of knowing when the value
+ changes. We work around this by caching a private deepcopy of the
+ mutable value, and checking for changes whenever the property is
+ set (obviously) or retrieved (to check for external changes). So
+ long as you're conscientious about accessing the property after
+ making external modifications, mutability woln't be a problem.
+ t.x.append(5) # external modification
+ t.x # dummy access notices change and triggers hook
+ See testChangeHookMutableProperty for an example of the expected
+ behavior.
"""
def decorator(funcs):
if hasattr(funcs, "__call__"):
@@ -338,10 +355,13 @@ def change_hook_property(hook, mutable=False):
fset = funcs.get("fset")
name = funcs.get("name", "<unknown>")
def _fget(self, new_value=None, from_fset=False): # only used if mutable == True
- value = fget(self)
- if _cmp_cached_mutable_property(self, "change hook property", name, value) != 0:
+ if from_fset == True:
+ value = new_value # compare new value with cached
+ else:
+ value = fget(self) # compare current value with cached
+ if _cmp_cached_mutable_property(self, "change hook property", name, value, default) != 0:
# there has been a change, cache new value
- old_value = _get_cached_mutable_property(self, "change hook property", name)
+ old_value = _get_cached_mutable_property(self, "change hook property", name, default)
_set_cached_mutable_property(self, "change hook property", name, value)
if from_fset == True: # return previously cached value
value = old_value
@@ -361,7 +381,7 @@ def change_hook_property(hook, mutable=False):
funcs["fset"] = _fset
return funcs
return decorator
-
+
class DecoratorTests(unittest.TestCase):
def testLocalDoc(self):
@@ -405,7 +425,7 @@ class DecoratorTests(unittest.TestCase):
@local_property(name="DEFAULT", null=5)
def x(): return {}
t = Test()
- self.failUnless(t.x == 5, str(t.x))
+ self.failUnless(t.x == 5, str(t.x))
t.x = 'x'
self.failUnless(t.x == 'y', str(t.x))
t.x = 'y'
@@ -574,14 +594,17 @@ class DecoratorTests(unittest.TestCase):
t.x = []
self.failUnless(t.old == None, t.old)
self.failUnless(t.new == [], t.new)
+ self.failUnless(t.hook_calls == 1, t.hook_calls)
a = t.x
a.append(5)
t.x = a
self.failUnless(t.old == [], t.old)
self.failUnless(t.new == [5], t.new)
+ self.failUnless(t.hook_calls == 2, t.hook_calls)
t.x = []
self.failUnless(t.old == [5], t.old)
self.failUnless(t.new == [], t.new)
+ self.failUnless(t.hook_calls == 3, t.hook_calls)
# now append without reassigning. this doesn't trigger the
# change, since we don't ever set t.x, only get it and mess
# with it. It does, however, update our t.new, since t.new =
@@ -589,25 +612,26 @@ class DecoratorTests(unittest.TestCase):
t.x.append(5)
self.failUnless(t.old == [5], t.old)
self.failUnless(t.new == [5], t.new)
+ self.failUnless(t.hook_calls == 3, t.hook_calls)
# however, the next t.x get _will_ notice the change...
a = t.x
self.failUnless(t.old == [], t.old)
self.failUnless(t.new == [5], t.new)
- self.failUnless(t.hook_calls == 6, t.hook_calls)
+ self.failUnless(t.hook_calls == 4, t.hook_calls)
t.x.append(6) # this append(6) is not noticed yet
self.failUnless(t.old == [], t.old)
self.failUnless(t.new == [5,6], t.new)
- self.failUnless(t.hook_calls == 6, t.hook_calls)
+ self.failUnless(t.hook_calls == 4, t.hook_calls)
# this append(7) is not noticed, but the t.x get causes the
# append(6) to be noticed
t.x.append(7)
self.failUnless(t.old == [5], t.old)
self.failUnless(t.new == [5,6,7], t.new)
- self.failUnless(t.hook_calls == 7, t.hook_calls)
+ self.failUnless(t.hook_calls == 5, t.hook_calls)
a = t.x # now the append(7) is noticed
self.failUnless(t.old == [5,6], t.old)
self.failUnless(t.new == [5,6,7], t.new)
- self.failUnless(t.hook_calls == 8, t.hook_calls)
+ self.failUnless(t.hook_calls == 6, t.hook_calls)
suite = unittest.TestLoader().loadTestsFromTestCase(DecoratorTests)
diff --git a/libbe/rcs.py b/libbe/rcs.py
index 2c416f4..0206bf6 100644
--- a/libbe/rcs.py
+++ b/libbe/rcs.py
@@ -3,21 +3,20 @@
# Ben Finney <ben+python@benfinney.id.au>
# Chris Ball <cjb@laptop.org>
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from subprocess import Popen, PIPE
import codecs
@@ -62,10 +61,13 @@ def installed_rcs():
class CommandError(Exception):
- def __init__(self, err_str, status):
- Exception.__init__(self, "Command failed (%d): %s" % (status, err_str))
- self.err_str = err_str
+ def __init__(self, command, status, err_str):
+ strerror = ["Command failed (%d):\n %s\n" % (status, err_str),
+ "while executing\n %s" % command]
+ Exception.__init__(self, "\n".join(strerror))
+ self.command = command
self.status = status
+ self.err_str = err_str
class SettingIDnotSupported(NotImplementedError):
pass
@@ -87,6 +89,10 @@ class NoSuchFile(Exception):
path = os.path.abspath(os.path.join(root, pathname))
Exception.__init__(self, "No such file: %s" % path)
+class EmptyCommit(Exception):
+ def __init__(self):
+ Exception.__init__(self, "No changes to commit")
+
def new():
return RCS()
@@ -187,7 +193,7 @@ class RCS(object):
if binary == False:
f = codecs.open(os.path.join(self.rootdir, path), "r", self.encoding)
else:
- f = open(path, "rb")
+ f = open(os.path.join(self.rootdir, path), "rb")
contents = f.read()
f.close()
return contents
@@ -198,11 +204,24 @@ class RCS(object):
dir specifies a directory to create the duplicate in.
"""
shutil.copytree(self.rootdir, directory, True)
- def _rcs_commit(self, commitfile):
+ def _rcs_commit(self, commitfile, allow_empty=False):
"""
Commit the current working directory, using the contents of
commitfile as the comment. Return the name of the old
- revision.
+ revision (or None if commits are not supported).
+
+ If allow_empty == False, raise EmptyCommit if there are no
+ changes to commit.
+ """
+ return None
+ def _rcs_revision_id(self, index):
+ """
+ Return the name of the <index>th revision. Index will be an
+ integer (possibly <= 0). The choice of which branch to follow
+ when crossing branches/merges is not defined.
+
+ Return None if revision IDs are not supported, or if the
+ specified revision does not exist.
"""
return None
def installed(self):
@@ -330,11 +349,15 @@ class RCS(object):
self.add(path)
else:
self.update(path)
- def mkdir(self, path, allow_no_rcs=False):
+ def mkdir(self, path, allow_no_rcs=False, check_parents=True):
"""
Create (if neccessary) a directory at path under version
control.
"""
+ if check_parents == True:
+ parent = os.path.dirname(path)
+ if not os.path.exists(parent): # recurse through parents
+ self.mkdir(parent, allow_no_rcs, check_parents)
if not os.path.exists(path):
os.mkdir(path)
if self._use_rcs(path, allow_no_rcs):
@@ -342,7 +365,9 @@ class RCS(object):
else:
assert os.path.isdir(path)
if self._use_rcs(path, allow_no_rcs):
- self.update(path)
+ #self.update(path)# Don't update directories. Changing files
+ pass # underneath them should be sufficient.
+
def duplicate_repo(self, revision=None):
"""
Get the repository as it was in a given revision.
@@ -365,30 +390,67 @@ class RCS(object):
shutil.rmtree(self._duplicateBasedir)
self._duplicateBasedir = None
self._duplicateDirname = None
- def commit(self, summary, body=None):
+ def commit(self, summary, body=None, allow_empty=False):
"""
Commit the current working directory, with a commit message
string summary and body. Return the name of the old revision
(or None if versioning is not supported).
+
+ If allow_empty == False (the default), raise EmptyCommit if
+ there are no changes to commit.
"""
+ summary = summary.strip()+'\n'
if body is not None:
- summary += '\n' + body
+ summary += '\n' + body.strip() + '\n'
descriptor, filename = tempfile.mkstemp()
revision = None
try:
temp_file = os.fdopen(descriptor, 'wb')
temp_file.write(summary)
temp_file.flush()
- revision = self._rcs_commit(filename)
+ self.precommit()
+ revision = self._rcs_commit(filename, allow_empty=allow_empty)
temp_file.close()
+ self.postcommit()
finally:
os.remove(filename)
return revision
- def precommit(self, directory):
+ def precommit(self):
+ """
+ Executed before all attempted commits.
+ """
pass
- def postcommit(self, directory):
+ def postcommit(self):
+ """
+ Only executed after successful commits.
+ """
pass
+ def revision_id(self, index=None):
+ """
+ Return the name of the <index>th revision. The choice of
+ which branch to follow when crossing branches/merges is not
+ defined.
+
+ Return None if index==None, revision IDs are not supported, or
+ if the specified revision does not exist.
+ """
+ if index == None:
+ return None
+ return self._rcs_revision_id(index)
+ def _u_any_in_string(self, list, string):
+ """
+ Return True if any of the strings in list are in string.
+ Otherwise return False.
+ """
+ for list_string in list:
+ if list_string in string:
+ return True
+ return False
def _u_invoke(self, args, stdin=None, expect=(0,), cwd=None):
+ """
+ expect should be a tuple of allowed exit codes. cwd should be
+ the directory from which the command will be executed.
+ """
if cwd == None:
cwd = self.rootdir
if self.verboseInvoke == True:
@@ -401,15 +463,13 @@ class RCS(object):
q = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
shell=True, cwd=cwd)
except OSError, e :
- strerror = "%s\nwhile executing %s" % (e.args[1], args)
- raise CommandError(strerror, e.args[0])
+ raise CommandError(args, e.args[0], e)
output, error = q.communicate(input=stdin)
status = q.wait()
if self.verboseInvoke == True:
print >> sys.stderr, "%d\n%s%s" % (status, output, error)
if status not in expect:
- strerror = "%s\nwhile executing %s\n%s" % (args[1], args, error)
- raise CommandError(strerror, status)
+ raise CommandError(args, status, error)
return status, output, error
def _u_invoke_client(self, *args, **kwargs):
directory = kwargs.get('directory',None)
@@ -784,6 +844,39 @@ class RCS_commit_TestCase(RCSTestCase):
self.failUnlessEqual(
self.test_contents['rev_1'], committed_contents)
+ def test_revision_id_as_committed(self):
+ """Check for compatibility between .commit() and .revision_id()"""
+ if not self.rcs.versioned:
+ self.failUnlessEqual(self.rcs.revision_id(5), None)
+ return
+ committed_revisions = []
+ for path in self.test_files:
+ full_path = self.full_path(path)
+ self.rcs.set_file_contents(
+ full_path, self.test_contents['rev_1'])
+ revision = self.rcs.commit("Initial %s contents." % path)
+ committed_revisions.append(revision)
+ self.rcs.set_file_contents(
+ full_path, self.test_contents['uncommitted'])
+ revision = self.rcs.commit("Altered %s contents." % path)
+ committed_revisions.append(revision)
+ for i,revision in enumerate(committed_revisions):
+ self.failUnlessEqual(self.rcs.revision_id(i), revision)
+ i += -len(committed_revisions) # check negative indices
+ self.failUnlessEqual(self.rcs.revision_id(i), revision)
+ i = len(committed_revisions)
+ self.failUnlessEqual(self.rcs.revision_id(i), None)
+ self.failUnlessEqual(self.rcs.revision_id(-i-1), None)
+
+ def test_revision_id_as_committed(self):
+ """Check revision id before first commit"""
+ if not self.rcs.versioned:
+ self.failUnlessEqual(self.rcs.revision_id(5), None)
+ return
+ committed_revisions = []
+ for path in self.test_files:
+ self.failUnlessEqual(self.rcs.revision_id(0), None)
+
class RCS_duplicate_repo_TestCase(RCSTestCase):
"""Test cases for RCS.duplicate_repo method."""
diff --git a/libbe/settings_object.py b/libbe/settings_object.py
index 7326d1b..ceea9d5 100644
--- a/libbe/settings_object.py
+++ b/libbe/settings_object.py
@@ -1,18 +1,19 @@
# Bugs Everywhere - a distributed bugtracker
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
This module provides a base class implementing settings-dict based
@@ -95,7 +96,7 @@ def versioned_property(name, doc,
require_save=False):
"""
Combine the common decorators in a single function.
-
+
Use zero or one (but not both) of default or generator, since a
working default will keep the generator from functioning. Use the
default if you know what you want the default value to be at
@@ -103,49 +104,57 @@ def versioned_property(name, doc,
determine a valid default at run time. If both default and
generator are None, then the property will be a defaulting
property which defaults to None.
-
+
allowed and check_fn have a similar relationship, although you can
use both of these if you want. allowed compares the proposed
value against a list determined at 'coding time' and check_fn
allows more flexible comparisons to take place at run time.
-
+
Set require_save to True if you want to save the default/generated
value for a property, to protect against future changes. E.g., we
currently expect all comments to be 'text/plain' but in the future
we may want to default to 'text/html'. If we don't want the old
comments to be interpreted as 'text/html', we would require that
the content type be saved.
-
+
change_hook, primer, settings_properties, and
required_saved_properties are only options to get their defaults
into our local scope. Don't mess with them.
+
+ Set mutable=True if:
+ * default is a mutable
+ * your generator function may return mutables
+ * you set change_hook and might have mutable property values
+ See the docstrings in libbe.properties for details on how each of
+ these cases are handled.
"""
settings_properties.append(name)
if require_save == True:
required_saved_properties.append(name)
def decorator(funcs):
fulldoc = doc
- if default != None:
+ if default != None or generator == None:
defaulting = defaulting_property(default=default, null=EMPTY,
- default_mutable=mutable)
- fulldoc += "\n\nThis property defaults to %s" % default
+ mutable_default=mutable)
+ fulldoc += "\n\nThis property defaults to %s." % default
if generator != None:
cached = cached_property(generator=generator, initVal=EMPTY,
mutable=mutable)
- fulldoc += "\n\nThis property is generated with %s" % generator
+ fulldoc += "\n\nThis property is generated with %s." % generator
if check_fn != None:
fn_checked = fn_checked_property(value_allowed_fn=check_fn)
- fulldoc += "\n\nThis property is checked with %s" % check_fn
+ fulldoc += "\n\nThis property is checked with %s." % check_fn
if allowed != None:
checked = checked_property(allowed=allowed)
fulldoc += "\n\nThe allowed values for this property are: %s." \
% (', '.join(allowed))
- hooked = change_hook_property(hook=change_hook, mutable=mutable)
+ hooked = change_hook_property(hook=change_hook, mutable=mutable,
+ default=EMPTY)
primed = primed_property(primer=primer, initVal=UNPRIMED)
settings = settings_property(name=name, null=UNPRIMED)
docp = doc_property(doc=fulldoc)
deco = hooked(primed(settings(docp(funcs))))
- if default != None:
+ if default != None or generator == None:
deco = defaulting(deco)
if generator != None:
deco = cached(deco)
@@ -179,7 +188,7 @@ class SavedSettingsObject(object):
# Override. Must call ._setup_saved_settings() after loading.
self.settings = {}
self._setup_saved_settings()
-
+
def _setup_saved_settings(self, flag_as_loaded=True):
"""
To be run after setting self.settings up from disk. Marks all
@@ -207,7 +216,7 @@ class SavedSettingsObject(object):
for k in self.required_saved_properties:
settings[k] = getattr(self, self._setting_name_to_attr_name(k))
return settings
-
+
def clear_cached_setting(self, setting=None):
"If setting=None, clear *all* cached settings"
if setting != None:
@@ -235,7 +244,7 @@ class SavedSettingsObjectTests(unittest.TestCase):
# access missing setting
self.failUnless(t._settings_loaded == False, t._settings_loaded)
self.failUnless(len(t.settings) == 0, len(t.settings))
- self.failUnless(t.content_type == EMPTY, t.content_type)
+ self.failUnless(t.content_type == None, t.content_type)
# accessing t.content_type triggers the priming, which runs
# t._setup_saved_settings, which fills out t.settings with
# EMPTY data. t._settings_loaded is still false though, since
@@ -250,16 +259,16 @@ class SavedSettingsObjectTests(unittest.TestCase):
self.failUnless(t._settings_loaded == True, t._settings_loaded)
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
- self.failUnless(t.content_type == EMPTY, t.content_type)
+ self.failUnless(t.content_type == None, t.content_type)
self.failUnless(len(t.settings) == 1, len(t.settings))
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
# now we set a value
- t.content_type = None
- self.failUnless(t.settings["Content-type"] == None,
+ t.content_type = 5
+ self.failUnless(t.settings["Content-type"] == 5,
t.settings["Content-type"])
- self.failUnless(t.content_type == None, t.content_type)
- self.failUnless(t.settings["Content-type"] == None,
+ self.failUnless(t.content_type == 5, t.content_type)
+ self.failUnless(t.settings["Content-type"] == 5,
t.settings["Content-type"])
# now we set another value
t.content_type = "text/plain"
@@ -273,7 +282,7 @@ class SavedSettingsObjectTests(unittest.TestCase):
self.failUnless(t._settings_loaded == True, t._settings_loaded)
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
- self.failUnless(t.content_type == EMPTY, t.content_type)
+ self.failUnless(t.content_type == None, t.content_type)
self.failUnless(len(t.settings) == 1, len(t.settings))
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
@@ -376,33 +385,25 @@ class SavedSettingsObjectTests(unittest.TestCase):
t.load_settings()
self.failUnless(SAVES == [], SAVES)
self.failUnless(t._settings_loaded == True, t._settings_loaded)
- self.failUnless(t.list_type == EMPTY, t.list_type)
- self.failUnless(SAVES == [
- "'None' -> '<class 'libbe.settings_object.EMPTY'>'"
- ], SAVES)
+ self.failUnless(t.list_type == None, t.list_type)
+ self.failUnless(SAVES == [], SAVES)
self.failUnless(t.settings["List-type"]==EMPTY,t.settings["List-type"])
t.list_type = []
self.failUnless(t.settings["List-type"] == [], t.settings["List-type"])
self.failUnless(SAVES == [
- "'None' -> '<class 'libbe.settings_object.EMPTY'>'",
"'<class 'libbe.settings_object.EMPTY'>' -> '[]'"
], SAVES)
t.list_type.append(5)
self.failUnless(SAVES == [
- "'None' -> '<class 'libbe.settings_object.EMPTY'>'",
"'<class 'libbe.settings_object.EMPTY'>' -> '[]'",
- "'<class 'libbe.settings_object.EMPTY'>' -> '[]'" # <- TODO. Where did this come from?
], SAVES)
self.failUnless(t.settings["List-type"] == [5],t.settings["List-type"])
self.failUnless(SAVES == [ # the append(5) has not yet been saved
- "'None' -> '<class 'libbe.settings_object.EMPTY'>'",
- "'<class 'libbe.settings_object.EMPTY'>' -> '[]'",
"'<class 'libbe.settings_object.EMPTY'>' -> '[]'",
], SAVES)
self.failUnless(t.list_type == [5], t.list_type) # <-get triggers saved
+
self.failUnless(SAVES == [ # now the append(5) has been saved.
- "'None' -> '<class 'libbe.settings_object.EMPTY'>'",
- "'<class 'libbe.settings_object.EMPTY'>' -> '[]'",
"'<class 'libbe.settings_object.EMPTY'>' -> '[]'",
"'[]' -> '[5]'"
], SAVES)
diff --git a/libbe/tree.py b/libbe/tree.py
index 54b927e..45ae085 100644
--- a/libbe/tree.py
+++ b/libbe/tree.py
@@ -1,20 +1,19 @@
# Bugs Everywhere, a distributed bugtracker
# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import doctest
@@ -36,7 +35,7 @@ class Tree(list):
>>> a = Tree(); a.n = "a"
>>> a.append(c)
>>> a.append(b)
-
+
>>> a.branch_len()
5
>>> a.sort(key=lambda node : -node.branch_len())
@@ -45,7 +44,7 @@ class Tree(list):
>>> a.sort(key=lambda node : node.branch_len())
>>> "".join([node.n for node in a.traverse()])
'abdgcefhi'
- >>> "".join([node.n for node in a.traverse(depthFirst=False)])
+ >>> "".join([node.n for node in a.traverse(depth_first=False)])
'abcdefghi'
>>> for depth,node in a.thread():
... print "%*s" % (2*depth+1, node.n)
@@ -69,7 +68,18 @@ class Tree(list):
f
h
i
+ >>> a.has_descendant(g)
+ True
+ >>> c.has_descendant(g)
+ False
+ >>> a.has_descendant(a)
+ False
+ >>> a.has_descendant(a, match_self=True)
+ True
"""
+ def __eq__(self, other):
+ return id(self) == id(other)
+
def branch_len(self):
"""
Exhaustive search every time == SLOW.
@@ -98,11 +108,11 @@ class Tree(list):
for child in self:
child.sort(*args, **kwargs)
- def traverse(self, depthFirst=True):
+ def traverse(self, depth_first=True):
"""
Note: you might want to sort() your tree first.
"""
- if depthFirst == True:
+ if depth_first == True:
yield self
for child in self:
for descendant in child.traverse():
@@ -120,7 +130,7 @@ class Tree(list):
When flatten==False, the depth of any node is one greater than
the depth of its parent. That way the inheritance is
explicit, but you can end up with highly indented threads.
-
+
When flatten==True, the depth of any node is only greater than
the depth of its parent when there is a branch, and the node
is not the last child. This can lead to ancestry ambiguity,
@@ -139,8 +149,8 @@ class Tree(list):
stack = [] # ancestry of the current node
if flatten == True:
depthDict = {}
-
- for node in self.traverse(depthFirst=True):
+
+ for node in self.traverse(depth_first=True):
while len(stack) > 0 \
and id(node) not in [id(c) for c in stack[-1]]:
stack.pop(-1)
@@ -158,4 +168,12 @@ class Tree(list):
yield (depth,node)
stack.append(node)
+ def has_descendant(self, descendant, depth_first=True, match_self=False):
+ if descendant == self:
+ return match_self
+ for d in self.traverse(depth_first):
+ if descendant == d:
+ return True
+ return False
+
suite = doctest.DocTestSuite()
diff --git a/libbe/utility.py b/libbe/utility.py
index 8a0f318..3df06b4 100644
--- a/libbe/utility.py
+++ b/libbe/utility.py
@@ -1,29 +1,28 @@
# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
# W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import calendar
import codecs
import os
import shutil
import tempfile
import time
+import types
import doctest
-
def search_parent_directories(path, filename):
"""
Find the file (or directory) named filename in path or in any
@@ -77,17 +76,54 @@ def time_to_str(time_val):
return time.strftime(RFC_2822_TIME_FMT, time.gmtime(time_val))
def str_to_time(str_time):
- """Convert an RFC 2822-fomatted string into a time falue.
+ """Convert an RFC 2822-fomatted string into a time value.
>>> str_to_time("Thu, 01 Jan 1970 00:00:00 +0000")
0
>>> q = time.time()
>>> str_to_time(time_to_str(q)) == int(q)
True
+ >>> str_to_time("Thu, 01 Jan 1970 00:00:00 -1000")
+ 36000
"""
- return calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT))
+ timezone_str = str_time[-5:]
+ if timezone_str != "+0000":
+ str_time = str_time.replace(timezone_str, "+0000")
+ time_val = calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT))
+ timesign = -int(timezone_str[0]+"1") # "+" -> time_val ahead of GMT
+ timezone_tuple = time.strptime(timezone_str[1:], "%H%M")
+ timezone = timezone_tuple.tm_hour*3600 + timezone_tuple.tm_min*60
+ return time_val + timesign*timezone
def handy_time(time_val):
return time.strftime("%a, %d %b %Y %H:%M", time.localtime(time_val))
+def time_to_gmtime(str_time):
+ """Convert an RFC 2822-fomatted string to a GMT string.
+ >>> time_to_gmtime("Thu, 01 Jan 1970 00:00:00 -1000")
+ 'Thu, 01 Jan 1970 10:00:00 +0000'
+ """
+ time_val = str_to_time(str_time)
+ return time_to_str(time_val)
+
+def iterable_full_of_strings(value, alternative=None):
+ """
+ Require an iterable full of strings.
+ >>> iterable_full_of_strings([])
+ True
+ >>> iterable_full_of_strings(["abc", "def", u"hij"])
+ True
+ >>> iterable_full_of_strings(["abc", None, u"hij"])
+ False
+ >>> iterable_full_of_strings(None, alternative=None)
+ True
+ """
+ if value == alternative:
+ return True
+ elif not hasattr(value, "__iter__"):
+ return False
+ for x in value:
+ if type(x) not in types.StringTypes:
+ return False
+ return True
suite = doctest.DocTestSuite()
diff --git a/completion/be.bash b/misc/completion/be.bash
index 834bf25..834bf25 100644
--- a/completion/be.bash
+++ b/misc/completion/be.bash
diff --git a/setup.py b/setup.py
index b909f3f..e770419 100755
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,16 @@
#!/usr/bin/env python
from distutils.core import setup
+from libbe import _version
+
+rev_id = _version.version_info["revision_id"]
+rev_date = rev_id.split("-")[1]
setup(
name='Bugs Everywhere',
- version='0.0.193',
+ version=rev_date,
description='Bugtracker built on distributed revision control',
- url='http://panoramicfeedback.com/opensource/',
+ url='http://bugseverywhere.org/',
packages=['becommands', 'libbe'],
scripts=['be'],
data_files=[
diff --git a/test_usage.sh b/test_usage.sh
index b2e2cab..13be2ff 100755
--- a/test_usage.sh
+++ b/test_usage.sh
@@ -18,6 +18,8 @@ set -v # verbose, echo commands to stdout
exec 6>&2 # save stderr to file descriptor 6
exec 2>&1 # fd 2 now writes to stdout
+ONLY_TEST_COMMIT="true"
+
if [ $# -gt 1 ]
then
echo "usage: test_usage.sh [RCS]"
@@ -75,8 +77,8 @@ then
darcs init
elif [ "$RCS" == "git" ]
then
- NAME=`git-config user.name`
- EMAIL=`git-config user.email`
+ NAME=`git config user.name`
+ EMAIL=`git config user.email`
ID="$NAME <$EMAIL>"
git init
elif [ "$RCS" == "hg" ]
@@ -124,7 +126,16 @@ BUGB=`echo "$OUT" | sed -n 's/Created bug with ID //p'`
be comment $BUGB "Blissfully unaware of a similar bug"
be merge $BUG $BUGB # join BUGB to BUG
be show $BUG # show bug details & comments
+# you can also export/import XML bugs/comments
+OUT=`be new 'yet more fun'`
+BUGC=`echo "$OUT" | sed -n 's/Created bug with ID //p'`
+be comment $BUGC "The ants go marching..."
+be show --xml $BUGC | be comment --xml ${BUG}:2 -
be remove $BUG # decide that you don't like that bug after all
+be commit "You can even commit using BE"
+be commit --allow-empty "And you can add empty commits if you like"
+be commit "But this will fail" || echo "Failed"
+
cd /
rm -rf $TESTDIR
diff --git a/update_copyright.sh b/update_copyright.sh
index 260cb3b..28eb0e0 100755
--- a/update_copyright.sh
+++ b/update_copyright.sh
@@ -1,10 +1,62 @@
#!/bin/bash
#
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
# Update copyright information in source code with information from
# the bzr repository. Run from the BE repository root.
+#
+# Replaces everything starting with '^# Copyright' and continuing with
+# '^#' with an auto-generated copyright blurb. If you want to add
+# #-commented material after a copyright blurb, please insert a blank
+# line between the blurb and your comment (as in this file), so the
+# next run of update_copyright.sh doesn't clobber your comment.
+#
+# usage: update_copyright.sh [files ...]
+#
+# If no files are given, a list of files to update is generated
+# automatically.
set -o pipefail
+if [ $# -gt 0 ]; then
+ FILES="$*"
+else
+ FILES=`grep -rc "# Copyright" . | grep -v ':0$' | cut -d: -f1`
+fi
+
+COPYRIGHT_TEXT="#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA."
+# escape newlines and special characters
+SED_RM_TRAIL_END='s/[\]n$//' # strip trailing newline escape
+SED_ESC_SPECIAL='s/\([()/]\)/\\\1/g' # escape special characters
+ESCAPED_TEXT=`echo "$COPYRIGHT_TEXT" | awk '{printf("%s\\\\n", $0)}' | sed "$SED_RM_TRAIL_END" | sed "$SED_ESC_SPECIAL"`
+
# adjust the AUTHORS file
AUTHORS=`bzr log | grep '^ *committer\|^ *author' | cut -d: -f2 | sed 's/ <.*//;s/^ *//' | sort | uniq`
AUTHORS=`echo "$AUTHORS" | grep -v 'j\^\|wking\|John Doe'` # remove non-names
@@ -12,7 +64,6 @@ echo "Bugs Everywhere was written by:" > AUTHORS
echo "$AUTHORS" >> AUTHORS
CURRENT_YEAR=`date +"%Y"`
-FILES=`grep -rc "# Copyright" . | grep -v ':0$' | cut -d: -f1`
TMP=`mktemp BE_update_copyright.XXXXXXX`
for file in $FILES
@@ -91,10 +142,10 @@ do
COPYRIGHT=`echo "$COPYRIGHT\\n# $DATE_SPACE $AUTHOR"`
done < <(echo "$OTHER_AUTHORS")
fi
- echo -e "$COPYRIGHT"
+ COPYRIGHT=`echo "$COPYRIGHT\\n$ESCAPED_TEXT"`
# Strip old copyright info and replace with tag
- awk 'BEGIN{incopy=0}{if(match($0, "^# Copyright")>0){incopy=1; print "-xyz-COPYRIGHT-zyx-"}else{if(incopy=0){print $0}else{if(match($0, "^# ")==0){incopy=0; print $0}}}}' "$file" > "$TMP"
+ awk 'BEGIN{incopy==0}{if(match($0, "^# Copyright")>0){incopy=1; print "-xyz-COPYRIGHT-zyx-"}else{if(incopy==0){print $0}else{if(match($0, "^#")==0){incopy=0; print $0}}}}' "$file" > "$TMP"
# Replace tag in with new string
sed -i "s/^-xyz-COPYRIGHT-zyx-$/$COPYRIGHT/" "$TMP"