aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values6
-rw-r--r--.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values6
-rw-r--r--.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values19
-rw-r--r--.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values19
-rw-r--r--.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values25
-rw-r--r--.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values19
-rw-r--r--.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values19
-rw-r--r--.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values19
-rw-r--r--.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values19
-rw-r--r--.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values19
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/0f60a148-7024-44bd-bbed-377cbece9d1b/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/13012b22-2d02-444c-87c0-8cf0f17137ae/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/1f9f60de-ba37-42bc-a1c0-dc062ef255e1/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/30a8b841-98ae-41b7-9ef2-6af7cffca8da/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/46937fd4-b0bc-4eed-8033-d699445441ea/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/4d192c6c-a4a8-4844-b083-2dd5926bd2d9/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/6dcc910a-ce15-4eeb-b49b-4747719748ed/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/8ffc90d7-0be7-4b00-88e6-9ae1b65f7957/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/bd98f525-95ec-446a-84e8-34c7d6fa5b40/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/c8283e08-967c-4a7b-b953-3ec62c83fb9f/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/d86e497d-667d-4c2b-9249-76026df56633/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/dc32aa62-cf56-4171-84a1-8f7d02b23b6d/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/e520239c-8d69-4ff6-b1bd-0c2f74366200/values6
-rw-r--r--.be/bugs/12c986be-d19a-4b8b-b1b5-68248ff4d331/comments/fd6162f3-7fc1-41d1-a073-a07465802b72/values6
-rw-r--r--.be/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values6
-rw-r--r--.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values6
-rw-r--r--.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values6
-rw-r--r--.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values6
-rw-r--r--.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values19
-rw-r--r--.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values19
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/body56
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/values11
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4952e1c7-e035-42f1-882b-6b5264481d0a/values6
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/6555a651-5a7f-4a8a-9793-47ad1315e9e8/values6
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/7750d77c-85d2-4810-9d41-cec62b0da885/values6
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/777182da-a216-45c7-bf4d-42c84e511c66/values6
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/9bbe9370-99c7-4d7c-80ee-9ade6b6feb9f/values6
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/b9865d8b-46ae-4169-bc83-d75a98164729/values6
-rw-r--r--.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values6
-rw-r--r--.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values19
-rw-r--r--.be/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values6
-rw-r--r--.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values17
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/074ef29a-3f1d-46dc-8561-7a56af7e6d67/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/1dba8196-654b-4ca0-9a95-fb334af81863/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/3bf57ee7-710f-4a01-a8af-8bb9eb9dc937/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/55263144-9775-4b18-ab83-29d66ed91a53/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/68927fef-6ce1-4a1f-a414-28695d913a50/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/83202b83-eea8-452f-8239-d468940bddba/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/8c1c4f38-a8d4-4cf9-a9f0-e9846ebbcad8/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/b900f7fd-bab6-48c4-922c-a051f933da58/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/c7ace551-2982-4683-bca3-b5e66056cce5/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/cb5689f4-7c36-4c44-b380-ca9e06e80bae/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/da97e18f-33d6-469e-9d93-6457b9a6bfca/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/e5248100-ea02-4205-a4c1-ac7a577c6362/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/comments/fd7ab206-5937-4ede-9e78-97aff098b677/values6
-rw-r--r--.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values2
-rw-r--r--.be/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values6
-rw-r--r--.be/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values19
-rw-r--r--.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values19
-rw-r--r--.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values19
-rw-r--r--.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values25
-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/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values19
-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/values21
-rw-r--r--.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values19
-rw-r--r--.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values6
-rw-r--r--.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values6
-rw-r--r--.be/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values19
-rw-r--r--.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values6
-rw-r--r--.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values6
-rw-r--r--.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/body30
-rw-r--r--.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/values11
-rw-r--r--.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/body16
-rw-r--r--.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/values8
-rw-r--r--.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/values22
-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/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/1f40efc1-6efc-4dd8-bdd2-97907e5aa624/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2bb7b4d0-6290-4771-9fff-4aa2e8086b1a/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/2c95ee07-462d-42cf-8dc3-8f5389a392cb/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/31beb504-c72b-4304-95ba-a66d2bcbc46a/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/6e315abe-a080-4369-8729-4aea2dee8494/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/744435b7-1521-4059-a55d-f0c403d7b4d8/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a536cee5-cc8d-4b18-b491-657e0c7998b4/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/a845096e-3cdf-41ed-a0e3-283439665b92/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/aad59898-8949-44fb-ad0b-2acea6eb2ef8/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/b19a8f6a-1d7b-4887-a9df-123d59b0cd9b/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/c35835c0-8f9f-4090-ba92-1f616867e486/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ea01c122-e629-4d5c-afa7-b180f4a8748b/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/f925e56f-26f9-4620-82fb-a0f160f27921/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/fdb615a4-168a-467b-8090-875c998455e5/values6
-rw-r--r--.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42/values6
-rw-r--r--.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values6
-rw-r--r--.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values6
-rw-r--r--.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values6
-rw-r--r--.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values19
-rw-r--r--.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values19
-rw-r--r--.be/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values6
-rw-r--r--.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values19
-rw-r--r--.be/bugs/764b812f-a0bb-4f4d-8e2f-c255c9474a0e/values17
-rw-r--r--.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values6
-rw-r--r--.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values6
-rw-r--r--.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values19
-rw-r--r--.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values19
-rw-r--r--.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values6
-rw-r--r--.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values6
-rw-r--r--.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values6
-rw-r--r--.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values6
-rw-r--r--.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values6
-rw-r--r--.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values6
-rw-r--r--.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values6
-rw-r--r--.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values17
-rw-r--r--.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values19
-rw-r--r--.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values19
-rw-r--r--.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values19
-rw-r--r--.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values19
-rw-r--r--.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values19
-rw-r--r--.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values6
-rw-r--r--.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values6
-rw-r--r--.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values6
-rw-r--r--.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values6
-rw-r--r--.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values17
-rw-r--r--.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values6
-rw-r--r--.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values6
-rw-r--r--.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values6
-rw-r--r--.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values19
-rw-r--r--.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values25
-rw-r--r--.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values25
-rw-r--r--.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values19
-rw-r--r--.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values19
-rw-r--r--.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values6
-rw-r--r--.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values6
-rw-r--r--.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values6
-rw-r--r--.be/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values19
-rw-r--r--.be/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values19
-rw-r--r--.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values17
-rw-r--r--.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values19
-rw-r--r--.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values19
-rw-r--r--.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values19
-rw-r--r--.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values19
-rw-r--r--.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values19
-rw-r--r--.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values6
-rw-r--r--.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values6
-rw-r--r--.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values6
-rw-r--r--.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values6
-rw-r--r--.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values19
-rw-r--r--.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values19
-rw-r--r--.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values19
-rw-r--r--.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values6
-rw-r--r--.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values6
-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/values17
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/00c6f4d8-f965-4d2f-a652-17e58c20ab8c/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/16357f68-19c0-4bf9-8220-b88b52b3456d/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/1f25cba2-03ee-43e1-a042-ef6724938ad8/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/21c90231-d7f2-49bb-97d9-99e16459d799/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/2496ccca-130b-4459-bfae-9d9ef0138177/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/42d57a41-219f-46db-9fda-21b42351da63/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/5e339bac-f4f3-407b-974a-b88795d3573b/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/7fa903a3-f9e6-4e4d-8128-0f26e1ce664b/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/e249e2aa-2029-4a96-bc84-962366e07fd6/values6
-rw-r--r--.be/bugs/d9959864-ea91-475a-a075-f39aa6760f98/comments/fa60ce1f-a809-4fb3-a2cd-1a2e0bdd0e0a/values6
-rw-r--r--.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values17
-rw-r--r--.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values19
-rw-r--r--.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values19
-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/values8
-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/values6
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/704b37ab-01bb-43d3-9e9f-f0d354f63c7d/values6
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/7b904395-86e9-4eb1-8534-69cec63801d4/values6
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/a0e846ed-1549-4ec3-b94d-391e54610f61/values6
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/cfd7cbc7-27ad-4618-8530-cb4d7323514a/values6
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/f1cde826-0506-4b4a-92ab-8499e953fa49/values6
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/fba8de97-9c61-4a08-b3e7-d8a95d6efe54/values6
-rw-r--r--.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values2
-rw-r--r--.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values25
-rw-r--r--.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values19
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values6
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values6
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values6
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values6
-rw-r--r--.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values6
-rw-r--r--.be/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values6
-rw-r--r--.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values17
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values6
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values6
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values6
-rw-r--r--.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values6
-rw-r--r--.be/settings9
-rw-r--r--.be/version2
-rw-r--r--Makefile4
-rwxr-xr-xbe8
-rw-r--r--becommands/assign.py15
-rw-r--r--becommands/close.py12
-rw-r--r--becommands/comment.py34
-rw-r--r--becommands/commit.py13
-rw-r--r--becommands/depend.py321
-rw-r--r--becommands/diff.py67
-rw-r--r--becommands/help.py6
-rw-r--r--becommands/html.py588
-rw-r--r--becommands/init.py28
-rw-r--r--becommands/list.py17
-rw-r--r--becommands/merge.py14
-rw-r--r--becommands/new.py12
-rw-r--r--becommands/open.py12
-rw-r--r--becommands/remove.py14
-rw-r--r--becommands/set.py24
-rw-r--r--becommands/severity.py18
-rw-r--r--becommands/show.py14
-rw-r--r--becommands/status.py21
-rw-r--r--becommands/subscribe.py390
-rw-r--r--becommands/tag.py26
-rw-r--r--becommands/target.py22
-rw-r--r--interfaces/README34
-rw-r--r--interfaces/email/interactive/README145
-rw-r--r--interfaces/email/interactive/_procmailrc22
-rwxr-xr-xinterfaces/email/interactive/be-handle-mail950
l---------interfaces/email/interactive/becommands1
-rw-r--r--interfaces/email/interactive/examples/blank0
-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
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py6
-rwxr-xr-xinterfaces/xml/be-mbox-to-xml39
-rwxr-xr-xinterfaces/xml/be-xml-to-mbox6
-rw-r--r--libbe/arch.py67
-rw-r--r--libbe/beuuid.py2
-rw-r--r--libbe/bug.py117
-rw-r--r--libbe/bugdir.py476
-rw-r--r--libbe/bzr.py52
-rw-r--r--libbe/cmdutil.py19
-rw-r--r--libbe/comment.py306
-rw-r--r--libbe/config.py6
-rw-r--r--libbe/darcs.py93
-rw-r--r--libbe/diff.py486
-rw-r--r--libbe/editor.py9
-rw-r--r--libbe/encoding.py10
-rw-r--r--libbe/git.py78
-rw-r--r--libbe/hg.py63
-rw-r--r--libbe/mapfile.py39
-rw-r--r--libbe/plugin.py6
-rw-r--r--libbe/properties.py10
-rw-r--r--libbe/settings_object.py11
-rw-r--r--libbe/tree.py4
-rw-r--r--libbe/upgrade.py187
-rw-r--r--libbe/utility.py5
-rw-r--r--libbe/vcs.py (renamed from libbe/rcs.py)485
-rw-r--r--libbe/version.py50
-rwxr-xr-xupdate_copyright.sh17
284 files changed, 6394 insertions, 2164 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 4b8a017..02ee559 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,8 +1,8 @@
-Content-type: text/plain
+Author: benf
-Date: Fri, 18 Apr 2008 11:21:03 +0000
+Content-type: text/plain
-From: benf
+Date: Fri, 18 Apr 2008 11:21:03 +0000
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
index 0b164a1..beec197 100644
--- 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
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Sat, 11 Jul 2009 14:08:45 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Sat, 11 Jul 2009 14:08:45 +0000
In-reply-to: 4be73baf-e46b-4acb-a58e-4719e57c550b
diff --git a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values
index 5ed19bf..34b6514 100644
--- a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values
+++ b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/144c238c-75d1-40f1-82c1-647668bcf2bc/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 04 Dec 2008 13:35:41 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Thu, 04 Dec 2008 13:35:41 +0000
diff --git a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values
index 58e8ffa..66a9f19 100644
--- a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values
+++ b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/rst
-
-
-
-
-
-
-Date=Thu, 06 Apr 2006 16:47:25 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/rst
+Date: Thu, 06 Apr 2006 16:47:25 +0000
diff --git a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values
index 2a1c84d..3f1fb15 100644
--- a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values
+++ b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values
@@ -1,28 +1,11 @@
+Author: abentley
+Content-type: text/restructured
-Content-type=text/restructured
+Date: Thu, 06 Apr 2006 16:54:57 +0000
-
-
-
-Date=Thu, 06 Apr 2006 16:54:57 +0000
-
-
-
-
-
-
-From=abentley
-
-
-
-
-
-
-In-reply-to=144c238c-75d1-40f1-82c1-647668bcf2bc
-
-
+In-reply-to: 144c238c-75d1-40f1-82c1-647668bcf2bc
diff --git a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values
index d55baa7..8dc0882 100644
--- a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values
+++ b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/16ba77d3-dfc9-4732-8d08-0e471f400d85/values
@@ -1,21 +1,8 @@
+Author: hubert
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 23 Jun 2008 05:02:22 +0000
-
-
-
-
-
-
-From=hubert
+Content-type: text/plain
+Date: Mon, 23 Jun 2008 05:02:22 +0000
diff --git a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values
index 1350ffb..176ae7f 100644
--- a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values
+++ b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/17a2217e-fc1d-4d7a-a569-4fd2a4a2261e/values
@@ -1,21 +1,8 @@
+Author: hubert
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Tue, 24 Jun 2008 02:45:18 +0000
-
-
-
-
-
-
-From=hubert
+Content-type: text/plain
+Date: Tue, 24 Jun 2008 02:45:18 +0000
diff --git a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values
index 67b182a..777a3f8 100644
--- a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values
+++ b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/202e0dc6-61bf-4b17-a8bd-f8a27482cb68/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Sun, 16 Nov 2008 20:36:20 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Sun, 16 Nov 2008 20:36:20 +0000
diff --git a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values
index 4a2e108..461a5ab 100644
--- a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values
+++ b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/6a0080c4-d684-4c2c-afaa-c15cc43d68ad/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 13 Nov 2008 19:31:04 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Thu, 13 Nov 2008 19:31:04 +0000
diff --git a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values
index cbf7142..e550f5c 100644
--- a/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values
+++ b/.be/bugs/0cad2ac6-76ef-4a88-abdf-b2e02de76f5c/comments/7e733393-8ba0-4345-a0e3-4140101d32f0/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 13 Nov 2008 20:18:02 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Thu, 13 Nov 2008 20:18:02 +0000
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
index c3d2045..4c93931 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <874otjmjhr.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Sat, 11 Jul 2009 23:34:08 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Sat, 11 Jul 2009 23:34:08 +1000
In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
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
index ed9c16f..26f7b94 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090711125030.GA18185@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sat, 11 Jul 2009 08:50:30 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sat, 11 Jul 2009 08:50:30 -0400
In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
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
index 6958136..55621fb 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <878wivmjm1.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Sat, 11 Jul 2009 23:31:34 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Sat, 11 Jul 2009 23:31:34 +1000
In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
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
index d95deb9..6e10b7e 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090713104715.GA13723@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Mon, 13 Jul 2009 06:47:15 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Mon, 13 Jul 2009 06:47:15 -0400
In-reply-to: 6dcc910a-ce15-4eeb-b49b-4747719748ed
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
index 1c7b2bf..968c96a 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090713115734.GA13788@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Mon, 13 Jul 2009 07:57:34 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Mon, 13 Jul 2009 07:57:34 -0400
In-reply-to: bd98f525-95ec-446a-84e8-34c7d6fa5b40
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
index 89f2724..d22c21f 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090712235502.GA10782@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sun, 12 Jul 2009 19:55:02 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sun, 12 Jul 2009 19:55:02 -0400
In-reply-to: 8ffc90d7-0be7-4b00-88e6-9ae1b65f7957
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
index 867700a..bbeacb6 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <1247468734.7189.1.camel@localhost>
-Content-type: text/plain
+Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
-Date: Mon, 13 Jul 2009 09:05:34 +0200
+Content-type: text/plain
-From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+Date: Mon, 13 Jul 2009 09:05:34 +0200
In-reply-to: 4d192c6c-a4a8-4844-b083-2dd5926bd2d9
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
index 38b8aa1..a9cd364 100644
--- 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
@@ -1,11 +1,11 @@
Alt-id: <1247313294.7701.60.camel@localhost>
-Content-type: text/plain
+Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
-Date: Sat, 11 Jul 2009 13:54:54 +0200
+Content-type: text/plain
-From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+Date: Sat, 11 Jul 2009 13:54:54 +0200
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
index de585ee..5a64ee0 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <1247433610.14803.3.camel@localhost>
-Content-type: text/plain
+Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
-Date: Sun, 12 Jul 2009 23:20:10 +0200
+Content-type: text/plain
-From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+Date: Sun, 12 Jul 2009 23:20:10 +0200
In-reply-to: bd98f525-95ec-446a-84e8-34c7d6fa5b40
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
index 2792f2b..9b9a279 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090711152507.GA18461@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sat, 11 Jul 2009 11:25:07 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sat, 11 Jul 2009 11:25:07 -0400
In-reply-to: e520239c-8d69-4ff6-b1bd-0c2f74366200
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
index 5e3db52..28fe7dc 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090713085859.GA21800@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Mon, 13 Jul 2009 10:58:59 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Mon, 13 Jul 2009 10:58:59 +0200
In-reply-to: e520239c-8d69-4ff6-b1bd-0c2f74366200
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
index 789fd7f..a79837f 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <1247320857.7701.67.camel@localhost>
-Content-type: text/plain
+Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
-Date: Sat, 11 Jul 2009 16:00:57 +0200
+Content-type: text/plain
-From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+Date: Sat, 11 Jul 2009 16:00:57 +0200
In-reply-to: 0f60a148-7024-44bd-bbed-377cbece9d1b
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
index 43173a4..00fe043 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <1247317985.7701.63.camel@localhost>
-Content-type: text/plain
+Author: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
-Date: Sat, 11 Jul 2009 15:13:05 +0200
+Content-type: text/plain
-From: Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+Date: Sat, 11 Jul 2009 15:13:05 +0200
In-reply-to: 13012b22-2d02-444c-87c0-8cf0f17137ae
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
index 351fdb9..2adef07 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87zlbbl128.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Sun, 12 Jul 2009 00:57:35 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Sun, 12 Jul 2009 00:57:35 +1000
In-reply-to: 88d1f2c2-e1af-4f0d-9390-e3c89ae4f7d7
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
index a7c438b..fc2560e 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090713090341.GB21800@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Mon, 13 Jul 2009 11:03:41 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Mon, 13 Jul 2009 11:03:41 +0200
In-reply-to: 1f9f60de-ba37-42bc-a1c0-dc062ef255e1
diff --git a/.be/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values b/.be/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values
index dae549f..a346a7c 100644
--- a/.be/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values
+++ b/.be/bugs/16fc9496-cdc2-4c6e-9b9f-b8f483b6dedb/comments/489397bd-b987-4a08-9589-c5b71661ebb7/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:16:11 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:16:11 +0000
diff --git a/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values b/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values
index b778afb..b82fbcb 100644
--- a/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values
+++ b/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/6010e186-0260-44e5-8442-8df2269910ce/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: abentley
-Date: Mon, 17 Apr 2006 20:59:15 +0000
+Content-type: text/plain
-From: abentley
+Date: Mon, 17 Apr 2006 20:59:15 +0000
diff --git a/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values b/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values
index f6daae2..42836a5 100644
--- a/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values
+++ b/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 21:29:13 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 21:29:13 +0000
diff --git a/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values b/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values
index 2425f4f..ac20cae 100644
--- a/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values
+++ b/.be/bugs/17921fbc-e7f0-4f31-8cdd-598e5ba7237b/comments/c531727a-9d0f-486f-aa0e-d4d2f2236640/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 21:29:33 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 21:29:33 +0000
diff --git a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values
index 74ffa83..01296d8 100644
--- a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values
+++ b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Sat, 01 Apr 2006 18:32:47 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/plain
+Date: Sat, 01 Apr 2006 18:32:47 +0000
diff --git a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values
index 6c7fb63..31bcacb 100644
--- a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values
+++ b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/e5db7c9b-de48-4302-905b-9570bb6e7ade/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Fri, 14 Nov 2008 05:00:43 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Fri, 14 Nov 2008 05:00:43 +0000
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/body
new file mode 100644
index 0000000..552b2ea
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/body
@@ -0,0 +1,56 @@
+On Mon, Jul 20, 2009 at 05:03:18PM -0400, Chris Ball wrote:
+> Hi Gianluca,
+>
+> > In any case, having the possibility to set a due date does not
+> > means that it is obligatory to do it and should be a good idea to
+> > offer as many possibilities as we can to the users of BE
+>
+> Okay, sounds reasonable. Would you like to write a patch for
+> associating due dates and open/closed with a target?
+
+I've been mulling this over, and I think that targets are a lot like
+bugs. Here's a list of issue/implementation pairs:
+
+ * Targeting normal bugs
+
+ With "be depend". I think we should remove the "target" field from
+ bugs, and move target dependencies over into the "be depend"
+ framework. Of course, we could add "blocks" (in addition to the
+ current "blocked-by") tags to make target lookup more efficient.
+
+ * "due_by"
+
+ We could add "due-by" to Bug.extra_strings as well, so that anyone
+ could set due dates for any issue they wanted.
+
+ * Bugdir-wide target
+
+ Just a pointer to the current target bug.
+
+ * Target dependency tree / time-series.
+
+ Use BLOCKS/BLOCKED-BY tags between targets, so you'd know which ones
+ came first.
+
+ * be target list
+
+ Would become "be list --severity target". A target "severity" would
+ keep target bugs distinct from other bug/issue types.
+
+ * Commenting on targets
+
+ They'd be Bug()s, so commenting already build in, e.g. to add
+ release notes, layout roadmaps, etc.
+
+If you want, we could maintain the current "be target" interface,
+and just use all this stuff behind the scenes.
+
+Thoughts?
+Trevor
+
+--
+This email may be signed or encrypted with GPG (http://www.gnupg.org).
+The GPG signature (if present) will be attached as 'signature.asc'.
+For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy
+
+My public key is at http://www.physics.drexel.edu/~wking/pubkey.txt
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/values
new file mode 100644
index 0000000..fee86e6
--- /dev/null
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/4012c6cc-1300-4f6b-af0e-9176eedf8de7/values
@@ -0,0 +1,11 @@
+Alt-id: <20090801102742.GA29000@mjolnir.home.net>
+
+
+Author: '"W. Trevor King" <wking@drexel.edu>'
+
+
+Content-type: text/plain
+
+
+Date: Sat, 1 Aug 2009 06:27:42 -0400
+
diff --git a/.be/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
index db30358..5b9011f 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <200907202259.11774.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Mon, 20 Jul 2009 22:59:11 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Mon, 20 Jul 2009 22:59:11 +0200
In-reply-to: 6555a651-5a7f-4a8a-9793-47ad1315e9e8
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
index cdf7754..5de3e0c 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3skgt648h.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Sat, 18 Jul 2009 18:00:46 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Sat, 18 Jul 2009 18:00:46 -0400
In-reply-to: b9865d8b-46ae-4169-bc83-d75a98164729
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
index d830558..0d96f8e 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <200907202340.39963.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Mon, 20 Jul 2009 23:40:39 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Mon, 20 Jul 2009 23:40:39 +0200
In-reply-to: 777182da-a216-45c7-bf4d-42c84e511c66
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
index a14e287..bf069fc 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3hbx72hk9.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Mon, 20 Jul 2009 17:03:18 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Mon, 20 Jul 2009 17:03:18 -0400
In-reply-to: 4952e1c7-e035-42f1-882b-6b5264481d0a
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
index b6c0979..e2dd8b8 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090718222701.GA304@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sat, 18 Jul 2009 18:27:01 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sat, 18 Jul 2009 18:27:01 -0400
In-reply-to: 6555a651-5a7f-4a8a-9793-47ad1315e9e8
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
index 4972040..6f56640 100644
--- 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
@@ -1,11 +1,11 @@
Alt-id: <200907182351.03217.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Sat, 18 Jul 2009 23:51:03 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Sat, 18 Jul 2009 23:51:03 +0200
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values
index 1485877..7440b56 100644
--- a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values
+++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values
@@ -1,9 +1,13 @@
-assigned: Gianluca Montecchi <gian@grys.it>
+assigned: W. Trevor King <wking@drexel.edu>
creator: W. Trevor King <wking@drexel.edu>
+extra_strings:
+- BLOCKED-BY:51930348-9ccc-4165-af41-6c7450de050e
+
+
reporter: Gianluca Montecchi <gian@grys.it>
diff --git a/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values b/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values
index fe5568e..1402892 100644
--- a/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values
+++ b/.be/bugs/2929814b-2163-45d0-87ba-f7d1ef0a32a9/comments/6d7072de-89b6-4c53-a435-6879c644a0e8/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Wed, 04 Jan 2006 21:03:54 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/plain
+Date: Wed, 04 Jan 2006 21:03:54 +0000
diff --git a/.be/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values b/.be/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values
index ad389a7..4c495f7 100644
--- a/.be/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values
+++ b/.be/bugs/2aa60b34-2c8d-4f41-bb97-a57309523262/comments/f21bec0d-cad0-44d2-a301-bfb11adce313/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:21:08 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:21:08 +0000
diff --git a/.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values b/.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values
new file mode 100644
index 0000000..c2861d0
--- /dev/null
+++ b/.be/bugs/2b81b428-fc43-4970-9469-b442385b9c0d/values
@@ -0,0 +1,17 @@
+creator: gianluca <gian@galactica>
+
+
+reporter: gianluca <gian@galactica>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: Use the get_parser in becommands/html.py
+
+
+time: Wed, 08 Jul 2009 21:27:37 +0000
+
diff --git a/.be/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
index b83f4a6..eda49f5 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87hbxqrckv.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Mon, 06 Jul 2009 08:26:24 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Mon, 06 Jul 2009 08:26:24 +1000
In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
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
index 4ef9544..642697d 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87y6r5qoyw.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Sat, 04 Jul 2009 10:19:35 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Sat, 04 Jul 2009 10:19:35 +1000
In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
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
index 7dee5d6..d8ccad9 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87skh9p8ax.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Tue, 07 Jul 2009 11:53:58 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Tue, 07 Jul 2009 11:53:58 +1000
In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
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
index 6f9ecf7..4c9ee4e 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090706104839.GA19537@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Mon, 6 Jul 2009 06:48:39 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Mon, 6 Jul 2009 06:48:39 -0400
In-reply-to: 074ef29a-3f1d-46dc-8561-7a56af7e6d67
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
index 3452022..69c1846 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090705143108.GB10709@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sun, 5 Jul 2009 10:31:08 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sun, 5 Jul 2009 10:31:08 -0400
In-reply-to: 1dba8196-654b-4ca0-9a95-fb334af81863
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
index ac3b5ab..b918b25 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090707013454.GA3721@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Mon, 6 Jul 2009 21:34:54 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Mon, 6 Jul 2009 21:34:54 -0400
In-reply-to: da97e18f-33d6-469e-9d93-6457b9a6bfca
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
index 6259717..17513d6 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <200907062218.33895.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Mon, 06 Jul 2009 22:18:33 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Mon, 06 Jul 2009 22:18:33 +0200
In-reply-to: 1dba8196-654b-4ca0-9a95-fb334af81863
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
index 7d09f96..ee8e589 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3iqi9thk1.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Fri, 03 Jul 2009 20:31:26 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Fri, 03 Jul 2009 20:31:26 -0400
In-reply-to: cb5689f4-7c36-4c44-b380-ca9e06e80bae
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
index e846ff5..fc79745 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <6f719a1c43fdcba8bdbfee1130072595.squirrel@webmail.grys.it>
-Content-type: text/plain
+Author: gian@grys.it
-Date: Tue, 07 Jul 2009 14:15:08 +0200
+Content-type: text/plain
-From: gian@grys.it
+Date: Tue, 07 Jul 2009 14:15:08 +0200
In-reply-to: 83202b83-eea8-452f-8239-d468940bddba
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
index e95ab61..9ce9085 100644
--- 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
@@ -1,11 +1,11 @@
Alt-id: <200907032250.17327.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Fri, 03 Jul 2009 22:50:17 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Fri, 03 Jul 2009 22:50:17 +0200
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
index 1bf2dc4..f989b78 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <200907062246.54804.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Mon, 06 Jul 2009 22:46:54 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Mon, 06 Jul 2009 22:46:54 +0200
In-reply-to: b900f7fd-bab6-48c4-922c-a051f933da58
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
index 2285e1d..931a187 100644
--- 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
@@ -1,11 +1,11 @@
Alt-id: <200906252203.08535.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Thu, 25 Jun 2009 22:03:08 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Thu, 25 Jun 2009 22:03:08 +0200
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
index b61fc2b..d4458fd 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <200907062238.56930.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Mon, 06 Jul 2009 22:38:56 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Mon, 06 Jul 2009 22:38:56 +0200
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
index 093611e..5f2d264 100644
--- a/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values
+++ b/.be/bugs/2f048ac5-5564-4b34-b7f9-605357267ed2/values
@@ -10,7 +10,7 @@ reporter: Gianluca Montecchi <gian@grys.it>
severity: wishlist
-status: assigned
+status: fixed
summary: Static html report generation
diff --git a/.be/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values b/.be/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values
index 6e9546e..0eaf9c9 100644
--- a/.be/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values
+++ b/.be/bugs/301724b1-3853-4aff-8f23-44373df7cf1c/comments/0d8af004-8352-4254-b747-d96a40a5d457/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:40:08 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:40:08 +0000
diff --git a/.be/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values b/.be/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values
index c499bfe..ab313b9 100644
--- a/.be/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values
+++ b/.be/bugs/31cd490d-a1c2-4ab3-8284-d80395e34dd2/comments/b2a333f7-eda6-42b9-8940-177f61ca7f48/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 13 Nov 2008 17:27:17 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Thu, 13 Nov 2008 17:27:17 +0000
diff --git a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values
index 1f7615e..ab2a567 100644
--- a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values
+++ b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9aa88bbd-71d0-44fa-804d-3562171f9539/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 04 Dec 2008 13:44:33 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Thu, 04 Dec 2008 13:44:33 +0000
diff --git a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values
index f88e71f..f692e19 100644
--- a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values
+++ b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/9e33512e-e3cb-42ec-bc99-8e77587d0d3f/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Tue, 17 May 2005 13:42:52 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/plain
+Date: Tue, 17 May 2005 13:42:52 +0000
diff --git a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values
index e939438..a0c9a34 100644
--- a/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values
+++ b/.be/bugs/381555eb-f2e3-4ef0-8303-d759c00b390a/comments/b76434a3-5cf9-4d2c-820b-64444289c09f/values
@@ -1,28 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
-Content-type=text/plain
+Date: Thu, 04 Dec 2008 13:46:32 +0000
-
-
-
-Date=Thu, 04 Dec 2008 13:46:32 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
-
-
-
-
-
-
-In-reply-to=9e33512e-e3cb-42ec-bc99-8e77587d0d3f
-
-
+In-reply-to: 9e33512e-e3cb-42ec-bc99-8e77587d0d3f
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..797a274
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/287d3cc1-1cd0-449a-b280-87c529e33951/values
@@ -0,0 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Wed, 22 Jul 2009 19:07:28 +0000
+
diff --git a/.be/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..e19bf0b
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/303986f2-0b17-4589-bf76-ed1461699c3e/values
@@ -0,0 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:52:25 +0000
+
+
+In-reply-to: 950ac308-f3e1-4956-885a-e79ce3025fd5
+
diff --git a/.be/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..74d7d97
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/478443b3-dd69-4719-b79a-b1279f75b8e4/values
@@ -0,0 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:53:02 +0000
+
+
+In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf
+
diff --git a/.be/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..ae4672b
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/85a2d1ac-200a-4ae7-841f-9f4e87795dbf/values
@@ -0,0 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:34:20 +0000
+
diff --git a/.be/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..d9fcf73
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/950ac308-f3e1-4956-885a-e79ce3025fd5/values
@@ -0,0 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Tue, 21 Jul 2009 19:34:32 +0000
+
+
+In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf
+
diff --git a/.be/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..f42f8ad
--- /dev/null
+++ b/.be/bugs/3e331b72-51fd-4408-bc0d-b6c5ac3b9f3e/comments/f72f8640-2e50-471e-aebe-0ddb8cdd5a2a/values
@@ -0,0 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Wed, 22 Jul 2009 18:54:06 +0000
+
+
+In-reply-to: 85a2d1ac-200a-4ae7-841f-9f4e87795dbf
+
diff --git a/.be/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/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values b/.be/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values
index b5100d0..e434e1e 100644
--- a/.be/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values
+++ b/.be/bugs/40dac9af-951e-4b98-8779-9ba02c37f8a1/comments/e1ff6c81-37d8-43ee-9dcf-17a89e07556a/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 13 Nov 2008 15:58:18 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Thu, 13 Nov 2008 15:58:18 +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..d88c668
--- /dev/null
+++ b/.be/bugs/427e0ca7-17f5-4a5a-8c68-98cc111a2495/values
@@ -0,0 +1,21 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+extra_strings:
+- BLOCKED-BY:51930348-9ccc-4165-af41-6c7450de050e
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: 'Terminology: Version control system vs. RCS'
+
+
+time: Mon, 03 Aug 2009 23:10:02 +0000
+
diff --git a/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values
index b19c065..e9bbdac 100644
--- a/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values
+++ b/.be/bugs/496edad5-1484-413a-bc68-4b01274a65eb/comments/8d927822-eff9-42c4-9541-8b784b3f7db2/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Sat, 22 Nov 2008 18:53:20 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Sat, 22 Nov 2008 18:53:20 +0000
diff --git a/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values b/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values
index 667dc94..63842d1 100644
--- a/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values
+++ b/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/0ac3c4cb-90e3-4b67-b6cb-1186d5d66240/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:05:50 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:05:50 +0000
diff --git a/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values b/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values
index 225f59e..6a4005c 100644
--- a/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values
+++ b/.be/bugs/4a4609c8-1882-47de-9d30-fee410b8a802/comments/942cd941-583d-4020-99e4-80de7e836129/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 15:42:07 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 15:42:07 +0000
diff --git a/.be/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values b/.be/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values
index 1f7615e..ab2a567 100644
--- a/.be/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values
+++ b/.be/bugs/4f7a4c3b-31e3-4023-8c9d-e67f627a34f0/comments/a8f35fca-8a15-4833-b568-326f0cc89bfa/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 04 Dec 2008 13:44:33 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Thu, 04 Dec 2008 13:44:33 +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 62b29d5..3ea62d2 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,10 +1,10 @@
-Content-type: text/plain
+Author: abentley
-Date: Mon, 16 Jul 2007 15:23:47 +0000
+Content-type: text/plain
-From: abentley
+Date: Mon, 16 Jul 2007 15:23:47 +0000
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 e31b44b..3292da3 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,8 +1,8 @@
-Content-type: text/plain
+Author: jelmer
-Date: Sun, 15 Jul 2007 13:34:52 +0000
+Content-type: text/plain
-From: jelmer
+Date: Sun, 15 Jul 2007 13:34:52 +0000
diff --git a/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/body b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/body
new file mode 100644
index 0000000..34d37e5
--- /dev/null
+++ b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/body
@@ -0,0 +1,30 @@
+Added libbe/upgrade.py to handle upgrading on-disk bugdirs.
+
+When upgrade.BUGDIR_DISK_VERSION changes, a series of Updater
+classes handle the upgrade. For example, if
+ BUGDIR_DISK_VERSIONS = ["v1", "v2", "v3"]
+and the on-disk version is "v1", you should have defined classes
+ class Upgrade_1_to_2 (Upgrader):
+ initial_version = "v1"
+ final_version = "v2"
+ def _upgrade():
+ ....
+ class Upgrade_2_to_3 (Upgrader):
+ initial_version = "v2"
+ final_version = "v3"
+ def _upgrade():
+ ....
+and added them to upgraders:
+ upgraders = [Upgrade_1_to_2, Upgrade_2_to_3]
+If the on-disk version is v2, then only Upgrade_2_to_3.upgrade() is
+run. If the on-disk version is v1, then Upgrade_1_to_2.upgrade() is
+run, followed by Upgrade_2_to_3.upgrade().
+
+You can optionally define shortcut upgrades (e.g. Upgrade_1_to_3) for
+efficiency or to avoid data loss.
+
+This upgrade occurs during BugDir.load(), which is called by
+BugDir.__init__(from_disk=True), before any processing of the on-disk
+data except for the access of .be/version to determine if an upgrade
+was necessary.
+
diff --git a/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/values b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/values
new file mode 100644
index 0000000..b296bff
--- /dev/null
+++ b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/d304f93b-faf2-477e-9ff8-c77e301fd9f9/values
@@ -0,0 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 31 Aug 2009 16:29:50 +0000
+
+
+In-reply-to: f1479ecf-4154-4cd4-bbd6-0ed6275b9f98
+
diff --git a/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/body b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/body
new file mode 100644
index 0000000..372a655
--- /dev/null
+++ b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/body
@@ -0,0 +1,16 @@
+There is no obvious means of using
+".be/version"/"libbe.bugdir.TREE_VERSION_STRING". In the past I've
+worked around this by keeping all the disk-reading backwards
+compatible (e.g. homemade mapfile -> YAML, the "From" hack in
+libbe.comment.Comment.load_settings, possibly others). However, this
+is not the road to easily maintainable code.
+
+Most projects only need to maintain backwards compatibility with the
+last few versions of their disk cache, to allow users an easy upgrade
+path. The difficulties come with "be diff", which must be able to
+read _every_ disk-image of the bugdir ever committed into something
+comparible with the current cutting edge. This makes sweeping changes
+very difficult. VCSs themselves avoid this by never showing their
+disk-cache to another program, but we've shown ours to the VCS, and
+it's difficult (or impossible, depending on the VCS) to change history
+to match the current format.
diff --git a/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/values b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/values
new file mode 100644
index 0000000..3d4d9df
--- /dev/null
+++ b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/comments/f1479ecf-4154-4cd4-bbd6-0ed6275b9f98/values
@@ -0,0 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Sun, 16 Aug 2009 19:07:06 +0000
+
diff --git a/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/values b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/values
new file mode 100644
index 0000000..75a191c
--- /dev/null
+++ b/.be/bugs/51930348-9ccc-4165-af41-6c7450de050e/values
@@ -0,0 +1,22 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+extra_strings:
+- BLOCKS:22b6f620-d2f7-42a5-a02e-145733a4e366
+- BLOCKS:427e0ca7-17f5-4a5a-8c68-98cc111a2495
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: Upgrade path for on-disk representation
+
+
+time: Sun, 16 Aug 2009 19:05:59 +0000
+
diff --git a/.be/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..eb90c47
--- /dev/null
+++ b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/4c50ca0b-a08f-4723-b00d-4bf342cf86b6/values
@@ -0,0 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 24 Jul 2009 12:33:58 +0000
+
+
+In-reply-to: b17a561a-6100-490e-84eb-d1ae4b617940
+
diff --git a/.be/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..d9d45f7
--- /dev/null
+++ b/.be/bugs/52034fd0-ec50-424d-b25d-2beaf2d2c317/comments/b17a561a-6100-490e-84eb-d1ae4b617940/values
@@ -0,0 +1,8 @@
+Author: Martin F Krafft <madduck@debian.org>
+
+
+Content-type: text/plain
+
+
+Date: Fri, 24 Jul 2009 12:09:02 +0000
+
diff --git a/.be/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/values b/.be/bugs/529c290e-b1cf-4800-be7e-68f1ecb9565c/comments/0c40c13a-3515-4b45-a8c3-142cceab9254/values
index c064938..e7077e7 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090714142942.GA5717@ukfsn.org>
-Content-type: text/plain
+Author: James Rowe <jnrowe@gmail.com>
-Date: Tue, 14 Jul 2009 15:29:42 +0100
+Content-type: text/plain
-From: James Rowe <jnrowe@gmail.com>
+Date: Tue, 14 Jul 2009 15:29:42 +0100
In-reply-to: ea01c122-e629-4d5c-afa7-b180f4a8748b
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
index 4538a9f..ce34e73 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090714171725.GB10445@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Tue, 14 Jul 2009 13:17:25 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Tue, 14 Jul 2009 13:17:25 -0400
In-reply-to: 0c40c13a-3515-4b45-a8c3-142cceab9254
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
index 4e3ade1..320c484 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87hbxdhtkp.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Thu, 16 Jul 2009 19:21:10 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Thu, 16 Jul 2009 19:21:10 +1000
In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
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
index 5134cf2..8cfe1b0 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <4A5CCE76.9040106@aaronbentley.com>
-Content-type: text/plain
+Author: Aaron Bentley <aaron@aaronbentley.com>
-Date: Tue, 14 Jul 2009 14:29:10 -0400
+Content-type: text/plain
-From: Aaron Bentley <aaron@aaronbentley.com>
+Date: Tue, 14 Jul 2009 14:29:10 -0400
In-reply-to: ae4f8f1e-6f86-4f81-ba9f-4042deb2ee68
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
index 0c1e529..df4c701 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090714191145.GB10606@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Tue, 14 Jul 2009 15:11:45 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Tue, 14 Jul 2009 15:11:45 -0400
In-reply-to: 6e315abe-a080-4369-8729-4aea2dee8494
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
index a4534a2..4f1d60d 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090714183404.GB26032@ukfsn.org>
-Content-type: text/plain
+Author: jnrowe@gmail.com
-Date: Tue, 14 Jul 2009 19:34:04 +0100
+Content-type: text/plain
-From: jnrowe@gmail.com
+Date: Tue, 14 Jul 2009 19:34:04 +0100
In-reply-to: 1f40efc1-6efc-4dd8-bdd2-97907e5aa624
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
index 1b70837..c5d9cbb 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87ocrnjvat.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Tue, 14 Jul 2009 22:36:26 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Tue, 14 Jul 2009 22:36:26 +1000
In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
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
index ea6e6aa..239feb5 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3ljmrfgot.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Tue, 14 Jul 2009 11:05:38 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Tue, 14 Jul 2009 11:05:38 -0400
In-reply-to: ea01c122-e629-4d5c-afa7-b180f4a8748b
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
index 1acfd91..ee9cc4b 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090718105008.GA31639@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sat, 18 Jul 2009 06:50:08 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sat, 18 Jul 2009 06:50:08 -0400
In-reply-to: c35835c0-8f9f-4090-ba92-1f616867e486
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
index 761c219..466be33 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3k52bfgf0.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Tue, 14 Jul 2009 11:11:31 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Tue, 14 Jul 2009 11:11:31 -0400
In-reply-to: ffbf5ac9-e2f5-47ab-9c3c-33989c81ad42
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
index 4439bad..fca4962 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090714182034.GA10606@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Tue, 14 Jul 2009 14:20:34 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Tue, 14 Jul 2009 14:20:34 -0400
In-reply-to: 1f40efc1-6efc-4dd8-bdd2-97907e5aa624
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
index 5d49c42..57b408f 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <5c5e5c350907140827u218553e8rc5773325d43c2bf2@mail.gmail.com>
-Content-type: text/plain
+Author: Elena of Valhalla <elena.valhalla@gmail.com>
-Date: Tue, 14 Jul 2009 17:27:52 +0200
+Content-type: text/plain
-From: Elena of Valhalla <elena.valhalla@gmail.com>
+Date: Tue, 14 Jul 2009 17:27:52 +0200
In-reply-to: aad59898-8949-44fb-ad0b-2acea6eb2ef8
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
index a828a3a..c7c0273 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <200907172337.49779.gian@grys.it>
-Content-type: text/plain
+Author: Gianluca Montecchi <gian@grys.it>
-Date: Fri, 17 Jul 2009 23:37:49 +0200
+Content-type: text/plain
-From: Gianluca Montecchi <gian@grys.it>
+Date: Fri, 17 Jul 2009 23:37:49 +0200
In-reply-to: f925e56f-26f9-4620-82fb-a0f160f27921
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
index 94bb94d..2df38ed 100644
--- 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
@@ -1,11 +1,11 @@
Alt-id: <20090714110543.GB4855@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Tue, 14 Jul 2009 07:05:43 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Tue, 14 Jul 2009 07:05:43 -0400
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
index c863757..42e7df8 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090714133732.GB6160@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Tue, 14 Jul 2009 09:37:32 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Tue, 14 Jul 2009 09:37:32 -0400
In-reply-to: 744435b7-1521-4059-a55d-f0c403d7b4d8
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
index 36f4007..4e46802 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090716103855.GA8579@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Thu, 16 Jul 2009 06:38:55 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Thu, 16 Jul 2009 06:38:55 -0400
In-reply-to: fdb615a4-168a-467b-8090-875c998455e5
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
index d373a73..3a42917 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87d481ht1s.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Thu, 16 Jul 2009 19:32:31 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Thu, 16 Jul 2009 19:32:31 +1000
In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
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
index aa9b55f..56bef0b 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87k52bjoxe.fsf_-_@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Wed, 15 Jul 2009 00:54:05 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Wed, 15 Jul 2009 00:54:05 +1000
In-reply-to: cdf15bdd-d3fe-4251-9d0b-f1b687e9a26c
diff --git a/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values b/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values
index 3a2ebfb..f460840 100644
--- a/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values
+++ b/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/72dab0c4-f04d-4ff0-9319-f55aafaea627/values
@@ -1,10 +1,10 @@
-Content-type: text/html
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:05:00 +0000
+Content-type: text/html
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:05:00 +0000
In-reply-to: c454aa67-ca30-43e8-9be4-58cbddd01b63
diff --git a/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values b/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values
index 1456cca..0c7cd6c 100644
--- a/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values
+++ b/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/c454aa67-ca30-43e8-9be4-58cbddd01b63/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:03:27 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:03:27 +0000
In-reply-to: d83a5436-85e3-42c7-9a89-a6d50df9d279
diff --git a/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values b/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values
index 4aea60a..7c8dc1c 100644
--- a/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values
+++ b/.be/bugs/576e804a-8b76-4876-8e9d-d7a72b0aef10/comments/d83a5436-85e3-42c7-9a89-a6d50df9d279/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Fri, 19 Jun 2009 20:22:19 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Fri, 19 Jun 2009 20:22:19 +0000
diff --git a/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values b/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values
index d7e4f49..929dce6 100644
--- a/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values
+++ b/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/8015d736-f3ea-4085-940c-552c01a287ef/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 04 Dec 2008 13:35:42 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Thu, 04 Dec 2008 13:35:42 +0000
diff --git a/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values b/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values
index 9121d12..54570b2 100644
--- a/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values
+++ b/.be/bugs/597a7386-643f-4559-8dc4-6871924229b6/comments/eff20807-07f0-444d-8992-f69ab3f526c5/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/restructured
-
-
-
-
-
-
-Date=Thu, 06 Apr 2006 16:54:57 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/restructured
+Date: Thu, 06 Apr 2006 16:54:57 +0000
diff --git a/.be/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values b/.be/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values
index 84da235..9f4fd8c 100644
--- a/.be/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values
+++ b/.be/bugs/68ba7f0c-ca5f-4f49-a508-e39150c07e13/comments/be64734c-d9a8-4f6d-83eb-e9b6c9adc0bf/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:29:30 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:29:30 +0000
diff --git a/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values b/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values
index ba9e33e..a1e25ea 100644
--- a/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values
+++ b/.be/bugs/6eb8141f-b0b1-4d5b-b4e6-d0860d844ada/comments/f2011471-56cb-46e2-813b-1ac336ee7bbc/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Fri, 27 Jan 2006 14:30:26 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/plain
+Date: Fri, 27 Jan 2006 14:30:26 +0000
diff --git a/.be/bugs/764b812f-a0bb-4f4d-8e2f-c255c9474a0e/values b/.be/bugs/764b812f-a0bb-4f4d-8e2f-c255c9474a0e/values
new file mode 100644
index 0000000..4406356
--- /dev/null
+++ b/.be/bugs/764b812f-a0bb-4f4d-8e2f-c255c9474a0e/values
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: Add docstrings explaining role of the libbe submodules.
+
+
+time: Mon, 31 Aug 2009 13:57:54 +0000
+
diff --git a/.be/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 59a3828..ead68e1 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,8 +1,8 @@
-Content-type: text/plain
+Author: abentley
-Date: Thu, 24 Mar 2005 17:04:47 +0000
+Content-type: text/plain
-From: abentley
+Date: Thu, 24 Mar 2005 17:04:47 +0000
diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values
index ddbdc15..90beac0 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,8 +1,8 @@
-Content-type: text/plain
+Author: abentley
-Date: Thu, 24 Mar 2005 13:05:13 +0000
+Content-type: text/plain
-From: abentley
+Date: Thu, 24 Mar 2005 13:05:13 +0000
diff --git a/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values b/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values
index ada2348..2d5059c 100644
--- a/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values
+++ b/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/2f6b71c5-45b3-473f-bd14-a1fe41bafcee/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 24 Nov 2008 13:08:07 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Mon, 24 Nov 2008 13:08:07 +0000
diff --git a/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values b/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values
index 2bde2a3..dcdd529 100644
--- a/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values
+++ b/.be/bugs/7bfc591e-584a-476e-8e11-b548f1afcaa6/comments/5a6b44f5-9d1d-4e2e-a42c-f5423c43a1dc/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Wed, 21 Dec 2005 21:53:47 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/plain
+Date: Wed, 21 Dec 2005 21:53:47 +0000
diff --git a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values
index 4d945d0..c054670 100644
--- a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values
+++ b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/401950a0-a5ff-46f3-afac-a9cfb300f94b/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:39:39 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:39:39 +0000
diff --git a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values
index 98b7985..32e49e7 100644
--- a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values
+++ b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/6010e186-0260-44e5-8442-8df2269910ce/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: abentley
-Date: Mon, 17 Apr 2006 20:59:15 +0000
+Content-type: text/plain
-From: abentley
+Date: Mon, 17 Apr 2006 20:59:15 +0000
In-reply-to: f87fd684-6af1-498d-98d5-f915bcee76a9
diff --git a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values
index cd3e2bf..d2f0f5c 100644
--- a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values
+++ b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/80780fa9-69f8-438c-8fbf-5a702b3badc1/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 25 Jun 2009 12:39:26 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 25 Jun 2009 12:39:26 +0000
diff --git a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values
index 3754f28..8874446 100644
--- a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values
+++ b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/bb988ed4-d3d5-4e49-b67e-c7ccb8ae44d3/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:42:12 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:42:12 +0000
In-reply-to: ec133a4e-c9ff-4499-b469-cb0a2ca9a685
diff --git a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values
index 11fb7b0..fe86bd4 100644
--- a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values
+++ b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/c2b78df3-641a-4d4d-ba94-33b26eda6364/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 21:29:13 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 21:29:13 +0000
In-reply-to: f87fd684-6af1-498d-98d5-f915bcee76a9
diff --git a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values
index 9078dd6..c85b16f 100644
--- a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values
+++ b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/ec133a4e-c9ff-4499-b469-cb0a2ca9a685/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:40:54 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:40:54 +0000
In-reply-to: 401950a0-a5ff-46f3-afac-a9cfb300f94b
diff --git a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values
index 0465a85..2b6307e 100644
--- a/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values
+++ b/.be/bugs/7ec2c071-9630-42b0-b08a-9854616f9144/comments/f87fd684-6af1-498d-98d5-f915bcee76a9/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 21:29:32 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 21:29:32 +0000
diff --git a/.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values b/.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values
new file mode 100644
index 0000000..5d80e70
--- /dev/null
+++ b/.be/bugs/8385a1fb-63df-4ca6-81cd-28ede83bb0c2/values
@@ -0,0 +1,17 @@
+creator: gianluca <gian@galactica>
+
+
+reporter: gianluca <gian@galactica>
+
+
+severity: minor
+
+
+status: wontfix
+
+
+summary: Add the html files for the status detail to "be html" output
+
+
+time: Fri, 03 Jul 2009 22:56:09 +0000
+
diff --git a/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values b/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values
index 39df7ff..924ca86 100644
--- a/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values
+++ b/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/4d642e39-a8f3-41d8-93da-bea7e05ef9a6/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 27 Nov 2008 14:26:18 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Thu, 27 Nov 2008 14:26:18 +0000
diff --git a/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values b/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values
index ea73789..fb952c2 100644
--- a/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values
+++ b/.be/bugs/8e1bbda4-35b6-4579-849d-117b1596ee99/comments/bf0c3752-6338-4919-93ba-4c9252945fb1/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 27 Nov 2008 13:43:47 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Thu, 27 Nov 2008 13:43:47 +0000
diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values
index f109f3e..3453fd9 100644
--- a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values
+++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/13e88b64-117b-4f8b-8cba-8f4a9bc394f5/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Fri, 21 Nov 2008 18:41:47 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Fri, 21 Nov 2008 18:41:47 +0000
diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values
index e0e3783..2a76a4e 100644
--- a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values
+++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/2ae039de-5b0d-4a4f-aa80-6c81d1345367/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Fri, 21 Nov 2008 19:12:42 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Fri, 21 Nov 2008 19:12:42 +0000
diff --git a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values
index e5498c9..d63f4e1 100644
--- a/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values
+++ b/.be/bugs/8e83da06-26f1-4763-a972-dae7e7062233/comments/a492508e-0be7-4403-bbd0-9cdc0a46b06b/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Fri, 21 Nov 2008 19:01:19 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Fri, 21 Nov 2008 19:01:19 +0000
diff --git a/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values b/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values
index cebf3cf..dea0808 100644
--- a/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values
+++ b/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/3e7144eb-c934-4b62-94b7-7dbfa90ed6ee/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 19:46:45 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 19:46:45 +0000
diff --git a/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values b/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values
index 560e158..ca6c353 100644
--- a/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values
+++ b/.be/bugs/8e948522-c6a1-4c97-af93-2cf4090f44b5/comments/7d7e703f-22f2-4c47-86a3-fcc3c8ead576/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 24 Nov 2008 13:10:38 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 24 Nov 2008 13:10:38 +0000
diff --git a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values
index 8270e8e..901c32f 100644
--- a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values
+++ b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/209e2a60-ddd0-4a71-90ef-e57547ed6d76/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 18:05:38 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 18:05:38 +0000
diff --git a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values
index eb5d3c0..4031ab2 100644
--- a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values
+++ b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: abentley
-Date: Fri, 31 Mar 2006 22:15:09 +0000
+Content-type: text/plain
-From: abentley
+Date: Fri, 31 Mar 2006 22:15:09 +0000
diff --git a/.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values b/.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values
new file mode 100644
index 0000000..4b6bb4f
--- /dev/null
+++ b/.be/bugs/9b1a0e71-4f7d-40b1-ab32-18496bf19a3f/values
@@ -0,0 +1,17 @@
+creator: gianluca <gian@galactica>
+
+
+reporter: gianluca <gian@galactica>
+
+
+severity: minor
+
+
+status: wontfix
+
+
+summary: Add the html files for the severity detail to "be html" output
+
+
+time: Fri, 03 Jul 2009 22:56:19 +0000
+
diff --git a/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values b/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values
index 7ba64d0..98f869b 100644
--- a/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values
+++ b/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/095ade7c-9378-41bd-8137-f2731c6afcac/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 18:40:43 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 18:40:43 +0000
diff --git a/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values b/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values
index 47ac983..cebbded 100644
--- a/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values
+++ b/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/4be35966-373b-438c-a35a-824f5c7a940a/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 21:12:00 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 21:12:00 +0000
In-reply-to: d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00
diff --git a/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values b/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values
index 2355aa5..c2f0da8 100644
--- a/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values
+++ b/.be/bugs/9ce2f015-8ea0-43a5-a03d-fc36f6d202fe/comments/d81d0df9-e6d9-4fe8-8dbe-989ef2c81f00/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 19:43:21 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 19:43:21 +0000
diff --git a/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values b/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values
index b0ecc8f..1bd47bf 100644
--- a/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values
+++ b/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Tue, 25 Nov 2008 02:24:04 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Tue, 25 Nov 2008 02:24:04 +0000
diff --git a/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values b/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values
index a93e649..7a04fc3 100644
--- a/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values
+++ b/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values
@@ -1,28 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
-Content-type=text/plain
+Date: Sat, 22 Nov 2008 21:43:29 +0000
-
-
-
-Date=Sat, 22 Nov 2008 21:43:29 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
-
-
-
-
-
-
-In-reply-to=0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1
-
-
+In-reply-to: 0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1
diff --git a/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values b/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values
index 35b6806..0c87273 100644
--- a/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values
+++ b/.be/bugs/a403de79-8f39-41f2-b9ec-15053b175ee2/comments/25c67b0b-1afd-4613-a787-e0f018614966/values
@@ -1,28 +1,11 @@
+Author: W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
-Content-type=text/plain
+Date: Sun, 23 Nov 2008 12:37:57 +0000
-
-
-
-Date=Sun, 23 Nov 2008 12:37:57 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
-
-
-
-
-
-
-In-reply-to=0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1
-
-
+In-reply-to: 0fd8ba95-d9ea-49b3-9f5a-b0eb723cdbe1
diff --git a/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values b/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values
index 6df7a97..8086b48 100644
--- a/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values
+++ b/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/3415fbd7-5a7e-4a7f-af30-82f8ce6ca85b/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 24 Nov 2008 13:05:07 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Mon, 24 Nov 2008 13:05:07 +0000
diff --git a/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values b/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values
index 8e0fad6..e94fece 100644
--- a/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values
+++ b/.be/bugs/a4d38ba7-ec28-4096-a4f3-eb8c9790ffb2/comments/b0e7165b-7099-45ca-9513-412225f7bd52/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 10 Apr 2006 23:23:25 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/plain
+Date: Mon, 10 Apr 2006 23:23:25 +0000
diff --git a/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values b/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values
index afd88e5..8fcd9d4 100644
--- a/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values
+++ b/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/2628eeca-96c6-4933-8484-d55bb1dbf985/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:05:49 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:05:49 +0000
diff --git a/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values b/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values
index 366395d..3033bc1 100644
--- a/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values
+++ b/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/942cd941-583d-4020-99e4-80de7e836129/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 15:42:07 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 15:42:07 +0000
In-reply-to: 2628eeca-96c6-4933-8484-d55bb1dbf985
diff --git a/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values b/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values
index 80e328b..4a24d7e 100644
--- a/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values
+++ b/.be/bugs/ae998b27-a11b-4243-abf6-11841e5b8242/comments/ae0f9aea-960c-42b4-82df-943bbbe17d58/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:07:25 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:07:25 +0000
diff --git a/.be/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values b/.be/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values
index b7b289e..251453e 100644
--- a/.be/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values
+++ b/.be/bugs/b187fbce-fb10-4819-ace2-c8b0b4a45c57/comments/e757d2ae-085a-4539-99be-096386de5352/values
@@ -1,21 +1,8 @@
+Author: benf
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 21 Apr 2008 03:24:11 +0000
-
-
-
-
-
-
-From=benf
+Content-type: text/plain
+Date: Mon, 21 Apr 2008 03:24:11 +0000
diff --git a/.be/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values b/.be/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values
index 1ff89fa..549a346 100644
--- a/.be/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values
+++ b/.be/bugs/b8d95763-1825-4e09-bf52-cbd884b916af/comments/ae56365e-7a9c-4cc3-ba67-7addbeeeff49/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 04 Dec 2008 13:48:47 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Thu, 04 Dec 2008 13:48:47 +0000
diff --git a/.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values b/.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values
new file mode 100644
index 0000000..b859364
--- /dev/null
+++ b/.be/bugs/c271a802-d324-48a6-b01d-63e4a72aa43e/values
@@ -0,0 +1,17 @@
+creator: gianluca <gian@galactica>
+
+
+reporter: gianluca <gian@galactica>
+
+
+severity: wishlist
+
+
+status: open
+
+
+summary: Add a verbose option to "be html"?
+
+
+time: Fri, 03 Jul 2009 21:17:41 +0000
+
diff --git a/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values b/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values
index 3a3c77b..cdba2a5 100644
--- a/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values
+++ b/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/04d71e10-9e44-4006-ab37-b4cc71647671/values
@@ -1,21 +1,8 @@
+Author: j
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 14 Apr 2008 16:43:07 +0000
-
-
-
-
-
-
-From=j
+Content-type: text/plain
+Date: Mon, 14 Apr 2008 16:43:07 +0000
diff --git a/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values b/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values
index f94558c..6a0464f 100644
--- a/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values
+++ b/.be/bugs/c45e5ece-63e3-4fd2-b33f-0bfd06820cf4/comments/1cb7063f-07ce-4a76-98f9-d184e1ee7282/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 24 Nov 2008 13:23:43 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Mon, 24 Nov 2008 13:23:43 +0000
diff --git a/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values b/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values
index 9f2b558..ae76653 100644
--- a/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values
+++ b/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/2ca25dd6-e9d1-4581-bd29-50f2eaa32fe4/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 13 Nov 2008 16:35:24 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Thu, 13 Nov 2008 16:35:24 +0000
diff --git a/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values b/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values
index 9b72e2c..cf27de6 100644
--- a/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values
+++ b/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/acbecd72-988c-4899-a340-fea370ce15a8/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Wed, 19 Nov 2008 17:11:51 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Wed, 19 Nov 2008 17:11:51 +0000
diff --git a/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values b/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values
index c404aa9..8103512 100644
--- a/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values
+++ b/.be/bugs/c4ea43d5-4964-49ea-a1eb-2bab2bde8e2e/comments/b3fabbe0-f05d-42a1-9037-e59e628a83e2/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 13 Nov 2008 16:38:36 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Thu, 13 Nov 2008 16:38:36 +0000
diff --git a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values
index bfc4ff7..6bc9258 100644
--- a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values
+++ b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/22348320-40d3-422c-bdf0-0f6a6bde3fab/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:12:35 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:12:35 +0000
In-reply-to: 354dcfc6-5997-4ffe-b7a0-baa852213539
diff --git a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values
index bc3434d..d000e42 100644
--- a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values
+++ b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/354dcfc6-5997-4ffe-b7a0-baa852213539/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:11:02 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:11:02 +0000
diff --git a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values
index 172a87c..34d6548 100644
--- a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values
+++ b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/c129067c-2341-4e7a-92a6-2dcd30d3bbf5/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:20:39 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:20:39 +0000
In-reply-to: f847c981-873e-41ae-b5ce-83dfe60b9afe
diff --git a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values
index 0ecd143..62a3fb7 100644
--- a/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values
+++ b/.be/bugs/c76d7899-d495-4103-9355-012c0a6fece3/comments/f847c981-873e-41ae-b5ce-83dfe60b9afe/values
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 20:14:26 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 20:14:26 +0000
In-reply-to: 22348320-40d3-422c-bdf0-0f6a6bde3fab
diff --git a/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values b/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values
index 368afb3..645e8c9 100644
--- a/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values
+++ b/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/208595bd-35b8-44c2-bf97-fc5ef9e7a58d/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Sat, 22 Nov 2008 21:43:29 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Sat, 22 Nov 2008 21:43:29 +0000
diff --git a/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values b/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values
index 5953360..32491b7 100644
--- a/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values
+++ b/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/25c67b0b-1afd-4613-a787-e0f018614966/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Sun, 23 Nov 2008 12:37:57 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Sun, 23 Nov 2008 12:37:57 +0000
diff --git a/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values b/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values
index 8283996..a0c3bd7 100644
--- a/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values
+++ b/.be/bugs/c894f10f-197d-4b22-9c5b-19f394df40d4/comments/7dfdf230-231b-43e0-9b46-58d4d18eded1/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@drexel.edu>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Tue, 25 Nov 2008 02:24:05 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@drexel.edu>
+Content-type: text/plain
+Date: Tue, 25 Nov 2008 02:24:05 +0000
diff --git a/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values b/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values
index e8c9da6..2dea024 100644
--- a/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values
+++ b/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/0e5fab2a-66eb-4f7d-979f-b50181f604d4/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Mon, 22 Jun 2009 19:48:44 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Mon, 22 Jun 2009 19:48:44 +0000
diff --git a/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values b/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values
index e964891..405abc1 100644
--- a/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values
+++ b/.be/bugs/cf56e648-3b09-4131-8847-02dff12b4db2/comments/f05359f6-1bfc-4aa6-9a6d-673516bc0f94/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: wking
-Date: Sat, 15 Nov 2008 23:56:51 +0000
+Content-type: text/plain
-From: wking
+Date: Sat, 15 Nov 2008 23:56:51 +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
new file mode 100644
index 0000000..ce4bc92
--- /dev/null
+++ b/.be/bugs/d8dba78d-f82a-4674-9003-a0ec569b4a96/values
@@ -0,0 +1,17 @@
+creator: gianluca <gian@galactica>
+
+
+reporter: gianluca <gian@galactica>
+
+
+severity: wishlist
+
+
+status: fixed
+
+
+summary: Add the possibility to specify the repository directory to "be html"?
+
+
+time: Fri, 03 Jul 2009 21:18:13 +0000
+
diff --git a/.be/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
index f303bf3..4a25a25 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3eisxtgfx.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Fri, 03 Jul 2009 20:55:30 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Fri, 03 Jul 2009 20:55:30 -0400
In-reply-to: 2496ccca-130b-4459-bfae-9d9ef0138177
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
index 2029281..bb88284 100644
--- 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
@@ -1,11 +1,11 @@
Alt-id: <272FECFE-D16B-47B7-B39A-E2C8A718CCC5@stevelosh.com>
-Content-type: text/plain
+Author: Steve Losh <steve@stevelosh.com>
-Date: Sat, 07 Feb 2009 16:30:33 -0500
+Content-type: text/plain
-From: Steve Losh <steve@stevelosh.com>
+Date: Sat, 07 Feb 2009 16:30:33 -0500
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
index 96612e6..ca3efd0 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <f6f643a20902071531y6aa3d7a6k7c5a4bd4aa5a04f6@mail.gmail.com>
-Content-type: text/plain
+Author: Matthew Wilson <matt@tplus1.com>
-Date: Sat, 07 Feb 2009 18:31:04 -0500
+Content-type: text/plain
-From: Matthew Wilson <matt@tplus1.com>
+Date: Sat, 07 Feb 2009 18:31:04 -0500
In-reply-to: 21c90231-d7f2-49bb-97d9-99e16459d799
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
index 7bdded2..fbe6c0e 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <D765386C-4D43-4AE0-83E3-986A1CB4008C@stevelosh.com>
-Content-type: text/plain
+Author: Steve Losh <steve@stevelosh.com>
-Date: Sat, 07 Feb 2009 17:48:06 -0500
+Content-type: text/plain
-From: Steve Losh <steve@stevelosh.com>
+Date: Sat, 07 Feb 2009 17:48:06 -0500
In-reply-to: 42d57a41-219f-46db-9fda-21b42351da63
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
index 0c68560..e602a56 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <4701D71B-A14D-4C63-ABCC-E7E5FFE4E4BA@stevelosh.com>
-Content-type: text/plain
+Author: Steve Losh <steve@stevelosh.com>
-Date: Fri, 03 Jul 2009 20:34:51 -0400
+Content-type: text/plain
-From: Steve Losh <steve@stevelosh.com>
+Date: Fri, 03 Jul 2009 20:34:51 -0400
In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
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
index 7c7e41a..e05f99d 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3zlgxyjo5.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Sat, 07 Feb 2009 17:19:22 -0500
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Sat, 07 Feb 2009 17:19:22 -0500
In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
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
index 9b607ef..a4063be 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m31vp82yyj.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Thu, 25 Jun 2009 10:02:44 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Thu, 25 Jun 2009 10:02:44 -0400
In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
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
index 72597bc..eea816a 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <26FBD153-39C5-4641-AF5E-749731964086@stevelosh.com>
-Content-type: text/plain
+Author: Steve Losh <steve@stevelosh.com>
-Date: Thu, 25 Jun 2009 10:23:04 -0400
+Content-type: text/plain
-From: Steve Losh <steve@stevelosh.com>
+Date: Thu, 25 Jun 2009 10:23:04 -0400
In-reply-to: 5e339bac-f4f3-407b-974a-b88795d3573b
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
index 37e1899..d361c19 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090721135907.GB4469@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Tue, 21 Jul 2009 09:59:07 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Tue, 21 Jul 2009 09:59:07 -0400
In-reply-to: 21c90231-d7f2-49bb-97d9-99e16459d799
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
index 2fa8470..49c9314 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090625154734.GA19441@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Thu, 25 Jun 2009 11:47:34 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Thu, 25 Jun 2009 11:47:34 -0400
In-reply-to: 16357f68-19c0-4bf9-8220-b88b52b3456d
diff --git a/.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values b/.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values
new file mode 100644
index 0000000..2832bb3
--- /dev/null
+++ b/.be/bugs/da2b09ff-af24-40f3-9b8d-6ffaa5f41164/values
@@ -0,0 +1,17 @@
+creator: Gianluca Montecchi <gian@grys.it>
+
+
+reporter: Gianluca Montecchi <gian@grys.it>
+
+
+severity: wishlist
+
+
+status: open
+
+
+summary: Add an icon near the status string in "be html" output
+
+
+time: Tue, 04 Aug 2009 21:15:52 +0000
+
diff --git a/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values b/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values
index 92e7e86..5972d7a 100644
--- a/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values
+++ b/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/1182d8e6-5e87-4d0a-b271-c298c36bbc21/values
@@ -1,21 +1,8 @@
+Author: W. Trevor King <wking@example.com>
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Wed, 19 Nov 2008 01:12:37 +0000
-
-
-
-
-
-
-From=W. Trevor King <wking@example.com>
+Content-type: text/plain
+Date: Wed, 19 Nov 2008 01:12:37 +0000
diff --git a/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values b/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values
index 13df021..8496f0a 100644
--- a/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values
+++ b/.be/bugs/dac91856-cb6a-4f69-8c03-38ff0b29aab2/comments/8097468f-87a9-4d84-ac20-1772393bb54d/values
@@ -1,21 +1,8 @@
+Author: wking
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Mon, 17 Nov 2008 15:03:58 +0000
-
-
-
-
-
-
-From=wking
+Content-type: text/plain
+Date: Mon, 17 Nov 2008 15:03:58 +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/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values
new file mode 100644
index 0000000..324b732
--- /dev/null
+++ b/.be/bugs/dcca51b3-bf8f-4482-8f67-662cfbcb9c6c/comments/d4a87066-c5f4-49f1-9bd9-a872c8e4ffe6/values
@@ -0,0 +1,8 @@
+Author: Gianluca Montecchi <gian@grys.it>
+
+
+Content-type: text/plain
+
+
+Date: Mon, 03 Aug 2009 20:33:30 +0000
+
diff --git a/.be/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/values b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/comments/09f950d4-9366-4e7b-98b3-9057999f8f38/values
index d8edf61..79dd755 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090718131220.GA31832@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sat, 18 Jul 2009 09:12:20 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sat, 18 Jul 2009 09:12:20 -0400
In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49
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
index c00299a..a2751e8 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090719130649.GA4164@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sun, 19 Jul 2009 09:06:49 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sun, 19 Jul 2009 09:06:49 -0400
In-reply-to: 7b904395-86e9-4eb1-8534-69cec63801d4
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
index ab160fb..67fc80f 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090718220551.GB32230@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sat, 18 Jul 2009 18:05:51 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sat, 18 Jul 2009 18:05:51 -0400
In-reply-to: 09f950d4-9366-4e7b-98b3-9057999f8f38
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
index 30de513..d8ffc73 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <20090719130153.GA4036@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Sun, 19 Jul 2009 09:01:53 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Sun, 19 Jul 2009 09:01:53 -0400
In-reply-to: cfd7cbc7-27ad-4618-8530-cb4d7323514a
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
index b98fbf7..057b7fa 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <87fxctbnce.fsf@benfinney.id.au>
-Content-type: text/plain
+Author: Ben Finney <bignose+hates-spam@benfinney.id.au>
-Date: Sun, 19 Jul 2009 09:09:05 +1000
+Content-type: text/plain
-From: Ben Finney <bignose+hates-spam@benfinney.id.au>
+Date: Sun, 19 Jul 2009 09:09:05 +1000
In-reply-to: f1cde826-0506-4b4a-92ab-8499e953fa49
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
index 3f81305..5a6047a 100644
--- 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
@@ -1,11 +1,11 @@
Alt-id: <20090716133930.GC12213@mjolnir.home.net>
-Content-type: text/plain
+Author: '"W. Trevor King" <wking@drexel.edu>'
-Date: Thu, 16 Jul 2009 09:39:30 -0400
+Content-type: text/plain
-From: '"W. Trevor King" <wking@drexel.edu>'
+Date: Thu, 16 Jul 2009 09:39:30 -0400
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
index f5da0c9..3cac90e 100644
--- 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
@@ -1,13 +1,13 @@
Alt-id: <m3fxct5vl6.fsf@pullcord.laptop.org>
-Content-type: text/plain
+Author: Chris Ball <cjb@laptop.org>
-Date: Sat, 18 Jul 2009 21:07:33 -0400
+Content-type: text/plain
-From: Chris Ball <cjb@laptop.org>
+Date: Sat, 18 Jul 2009 21:07:33 -0400
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
index 5be4cca..da43639 100644
--- a/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values
+++ b/.be/bugs/e0858b12-0be3-49bb-ad7a-030e488bb2f1/values
@@ -10,7 +10,7 @@ reporter: W. Trevor King <wking@drexel.edu>
severity: wishlist
-status: assigned
+status: fixed
summary: Interactive email interface
diff --git a/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values b/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values
index b2bedbb..ae0ca8f 100644
--- a/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values
+++ b/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/bcd6e5d4-8d03-43ad-a10d-17619735d077/values
@@ -1,28 +1,11 @@
+Author: abentley
+Content-type: text/plain
-Content-type=text/plain
+Date: Thu, 14 Sep 2006 18:05:48 +0000
-
-
-
-Date=Thu, 14 Sep 2006 18:05:48 +0000
-
-
-
-
-
-
-From=abentley
-
-
-
-
-
-
-In-reply-to=e5decfc6-050b-4283-8776-977bf85b2c99
-
-
+In-reply-to: e5decfc6-050b-4283-8776-977bf85b2c99
diff --git a/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values b/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values
index 9e82a6e..72d84b7 100644
--- a/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values
+++ b/.be/bugs/e2f6514c-5f9f-4734-a537-daf3fbe7e9a0/comments/e5decfc6-050b-4283-8776-977bf85b2c99/values
@@ -1,21 +1,8 @@
+Author: abentley
-
-Content-type=text/plain
-
-
-
-
-
-
-Date=Thu, 14 Sep 2006 18:03:41 +0000
-
-
-
-
-
-
-From=abentley
+Content-type: text/plain
+Date: Thu, 14 Sep 2006 18:03:41 +0000
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
index cd8d8b9..8a5060e 100644
--- 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
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Sun, 12 Jul 2009 11:34:22 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Sun, 12 Jul 2009 11:34:22 +0000
In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132
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
index 8bdaf52..55642ec 100644
--- 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
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Sun, 12 Jul 2009 11:42:16 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Sun, 12 Jul 2009 11:42:16 +0000
In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132
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
index 1784e0e..705ce8d 100644
--- 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
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Sun, 12 Jul 2009 11:46:57 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Sun, 12 Jul 2009 11:46:57 +0000
In-reply-to: 520a9829-8d90-43ce-be64-868b8321e5b0
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
index cca07c3..3a0c6ff 100644
--- 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
@@ -1,10 +1,10 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Sun, 12 Jul 2009 11:37:55 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Sun, 12 Jul 2009 11:37:55 +0000
In-reply-to: 07fc448f-c42e-4846-929a-8924de485766
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
index e430ea0..8591aa5 100644
--- 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
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Sun, 12 Jul 2009 11:31:13 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Sun, 12 Jul 2009 11:31:13 +0000
diff --git a/.be/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values b/.be/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values
index 7bf391a..9825ae8 100644
--- a/.be/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values
+++ b/.be/bugs/f70dd5df-805b-49f3-a9ce-12e0fae63365/comments/24903c62-f441-496e-9dcf-17e7a581df33/values
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Thu, 04 Dec 2008 17:20:20 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Thu, 04 Dec 2008 17:20:20 +0000
diff --git a/.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values b/.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values
new file mode 100644
index 0000000..72c2839
--- /dev/null
+++ b/.be/bugs/f77fc673-c852-4c81-bfa2-1d59de2661c8/values
@@ -0,0 +1,17 @@
+creator: Gianluca Montecchi <gian@grys.it>
+
+
+reporter: Gianluca Montecchi <gian@grys.it>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: Comment should be threaded in the "be html" output
+
+
+time: Tue, 21 Jul 2009 21:39:52 +0000
+
diff --git a/.be/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 d39c4a1..1e12a53 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,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Tue, 25 Nov 2008 19:41:02 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Tue, 25 Nov 2008 19:41:02 +0000
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 639fd4a..95751fd 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,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Tue, 25 Nov 2008 02:36:16 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Tue, 25 Nov 2008 02:36:16 +0000
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 2821b2f..1e4f9c5 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,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Tue, 25 Nov 2008 03:02:59 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Tue, 25 Nov 2008 03:02:59 +0000
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
index a67680d..86cfb90 100644
--- 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
@@ -1,8 +1,8 @@
-Content-type: text/plain
+Author: W. Trevor King <wking@drexel.edu>
-Date: Sat, 11 Jul 2009 18:28:57 +0000
+Content-type: text/plain
-From: W. Trevor King <wking@drexel.edu>
+Date: Sat, 11 Jul 2009 18:28:57 +0000
diff --git a/.be/settings b/.be/settings
index a9bd6dd..b3c2b81 100644
--- a/.be/settings
+++ b/.be/settings
@@ -1,3 +1,10 @@
+encoding: utf-8
+
+
+extra_strings:
+- "SUBSCRIBE:W. Trevor King <wking@drexel.edu>\tall\t*"
+
+
inactive_status:
- - closed
- The bug is no longer relevant.
@@ -9,5 +16,5 @@ inactive_status:
- Unknown meaning. For backwards compatibility with old BE bugs.
-rcs_name: bzr
+vcs_name: bzr
diff --git a/.be/version b/.be/version
index 990837e..7bd05c2 100644
--- a/.be/version
+++ b/.be/version
@@ -1 +1 @@
-Bugs Everywhere Tree 1 0
+Bugs Everywhere Directory v1.2
diff --git a/Makefile b/Makefile
index b1207c2..fe482c3 100644
--- a/Makefile
+++ b/Makefile
@@ -57,8 +57,8 @@ build: libbe/_version.py
.PHONY: install
install: doc build
python setup.py install ${INSTALL_OPTIONS}
- cp -v interfaces/xml/* ${PREFIX}/bin
- cp -v interfaces/email/catmutt ${PREFIX}/bin
+#cp -v interfaces/xml/* ${PREFIX}/bin
+#cp -v interfaces/email/catmutt ${PREFIX}/bin
.PHONY: clean
diff --git a/be b/be
index 36deaba..feacfb4 100755
--- a/be
+++ b/be
@@ -21,7 +21,7 @@
import os
import sys
-from libbe import cmdutil, _version
+from libbe import cmdutil, version
__doc__ = cmdutil.help()
@@ -31,6 +31,8 @@ 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("--verbose-version", action="store_true", dest="verbose_version",
+ help="Print verbose version information and exit.")
parser.add_option("-d", "--dir", dest="dir", metavar="DIR",
help="Run this command from DIR instead of the current directory.")
@@ -50,8 +52,8 @@ except cmdutil.GetCompletions, e:
print '\n'.join(e.completions)
sys.exit(0)
-if options.version == True:
- print _version.version_info["revision_id"]
+if options.version == True or options.verbose_version == True:
+ print version.version(verbose=options.verbose_version)
sys.exit(0)
if options.dir != None:
os.chdir(options.dir)
diff --git a/becommands/assign.py b/becommands/assign.py
index 536bca6..794f028 100644
--- a/becommands/assign.py
+++ b/becommands/assign.py
@@ -20,28 +20,29 @@
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 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 None
True
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -53,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 0ba8f50..0532ed2 100644
--- a/becommands/close.py
+++ b/becommands/close.py
@@ -20,18 +20,19 @@
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)
@@ -41,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 55b5913..9a614b2 100644
--- a/becommands/comment.py
+++ b/becommands/comment.py
@@ -25,20 +25,20 @@ 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
@@ -47,19 +47,20 @@ def execute(args, test=False):
>>> 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)
@@ -68,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]
@@ -79,17 +80,17 @@ 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)
+ 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:
if parent == bug.comment_root:
@@ -103,7 +104,6 @@ def execute(args, test=False):
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/"))
@@ -117,11 +117,11 @@ def execute(args, test=False):
body = args[1]
if not body.endswith('\n'):
body+='\n'
-
+
if options.XML == False:
new = parent.new_reply(body=body)
if options.author != None:
- new.From = options.author
+ new.author = options.author
if options.alt_id != None:
new.alt_id = options.alt_id
if options.content_type != None:
diff --git a/becommands/commit.py b/becommands/commit.py
index 4f3bdbd..dc70e7e 100644
--- a/becommands/commit.py
+++ b/becommands/commit.py
@@ -14,7 +14,7 @@
# 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
+from libbe import cmdutil, bugdir, editor, vcs
import sys
__desc__ = __doc__
@@ -22,13 +22,14 @@ 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)
>>> full_path = "testfile"
>>> test_contents = "A test file"
- >>> bd.rcs.set_file_contents(full_path, test_contents)
+ >>> bd.vcs.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)
@@ -48,11 +49,11 @@ def execute(args, manipulate_encodings=True):
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)
+ body = bd.vcs.get_file_contents(options.body, allow_no_vcs=True)
try:
- revision = bd.rcs.commit(summary, body=body,
+ revision = bd.vcs.commit(summary, body=body,
allow_empty=options.allow_empty)
- except rcs.EmptyCommit, e:
+ except vcs.EmptyCommit, e:
print e
return 1
else:
diff --git a/becommands/depend.py b/becommands/depend.py
index 4a23b0f..f72b8ba 100644
--- a/becommands/depend.py
+++ b/becommands/depend.py
@@ -14,26 +14,56 @@
# 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,45 +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
-
- 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="""
@@ -88,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 13402c0..b6ac5b0 100644
--- a/becommands/diff.py
+++ b/becommands/diff.py
@@ -20,23 +20,35 @@ 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")
+ >>> original = bd.vcs.commit("Original status")
>>> bug = bd.bug_from_uuid("a")
>>> bug.status = "closed"
- >>> changed = bd.rcs.commit("Closed bug a")
+ >>> changed = bd.vcs.commit("Closed bug a")
>>> os.chdir(bd.root)
- >>> if bd.rcs.versioned == True:
- ... execute([original], test=True)
+ >>> if bd.vcs.versioned == 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
+ ... 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.vcs.versioned == True:
+ ... execute(["--modified", original], manipulate_encodings=False)
+ ... else:
+ ... print "a"
+ a
+ >>> if bd.vcs.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)
@@ -47,28 +59,31 @@ 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)
- if bd.rcs.versioned == False:
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if bd.vcs.versioned == False:
print "This directory is not revision-controlled."
else:
+ if revision == None: # get the most recent revision
+ revision = bd.vcs.revision_id(-1)
old_bd = bd.duplicate_bugdir(revision)
- r,m,a = diff.bug_diffs(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 :
- rep = diff.diff_report((r,m,a), old_bd, bd).encode(bd.encoding)
- if len(rep) > 0:
+ rep = tree.report_string()
+ if rep != None:
print rep
bd.remove_duplicate_bugdir()
@@ -85,14 +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
+Uses the VCS 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.
+VCS.
For Arch your specifier must be a fully-qualified revision name.
diff --git a/becommands/help.py b/becommands/help.py
index a8ae338..a8f346a 100644
--- a/becommands/help.py
+++ b/becommands/help.py
@@ -19,9 +19,11 @@
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
new file mode 100644
index 0000000..908c714
--- /dev/null
+++ b/becommands/html.py
@@ -0,0 +1,588 @@
+# Copyright (C) 2009 Gianluca Montecchi <gian@grys.it>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Generate a static HTML dump of the current repository status"""
+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, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> 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)
+ complete(options, args, parser)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==False})
+
+ if len(args) == 0:
+ out_dir = options.outdir
+ print "Creating the html output in %s"%out_dir
+ else:
+ out_dir = args[0]
+ if len(args) > 0:
+ raise cmdutil.UsageError, "Too many arguments."
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bd.load_all_bugs()
+ status_list = bug.status_values
+ severity_list = bug.severity_values
+ st = {}
+ se = {}
+ stime = {}
+ bugs_active = []
+ bugs_inactive = []
+ for s in status_list:
+ st[s] = 0
+ for b in sorted(bd, reverse=True):
+ stime[b.uuid] = b.time
+ if b.active == True:
+ bugs_active.append(b)
+ else:
+ bugs_inactive.append(b)
+ st[b.status] += 1
+ ordered_bug_list = sorted([(value,key) for (key,value) in stime.items()])
+ ordered_bug_list_in = sorted([(value,key) for (key,value) in stime.items()])
+ #open_bug_list = sorted([(value,key) for (key,value) in bugs.items()])
+
+ html_gen = BEHTMLGen(bd)
+ html_gen.create_index_file(out_dir, st, bugs_active, ordered_bug_list, "active", bd.encoding)
+ html_gen.create_index_file(out_dir, st, bugs_inactive, ordered_bug_list, "inactive", bd.encoding)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be open OUTPUT_DIR")
+ parser.add_option("-o", "--output", metavar="export_dir", dest="outdir",
+ help="Set the output path, default is ./html_export", default="html_export")
+ return parser
+
+longhelp="""
+Generate a set of html pages representing the current state of the bug
+directory.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option, value in cmdutil.option_value_pairs(options, parser):
+ if "--complete" in args:
+ raise cmdutil.GetCompletions() # no positional arguments for list
+
+
+def escape(string):
+ if string == None:
+ return ""
+ chars = []
+ for char in xml.sax.saxutils.escape(string):
+ codepoint = ord(char)
+ if codepoint in htmlentitydefs.codepoint2name:
+ char = "&%s;" % htmlentitydefs.codepoint2name[codepoint]
+ chars.append(char)
+ return "".join(chars)
+
+class BEHTMLGen():
+ def __init__(self, bd):
+ self.index_value = ""
+ self.bd = bd
+
+ self.css_file = """
+ body {
+ font-family: "lucida grande", "sans serif";
+ color: #333;
+ width: auto;
+ margin: auto;
+ }
+
+
+ div.main {
+ padding: 20px;
+ margin: auto;
+ padding-top: 0;
+ margin-top: 1em;
+ background-color: #fcfcfc;
+ }
+
+ .comment {
+ padding: 20px;
+ margin: auto;
+ padding-top: 20px;
+ margin-top: 0;
+ }
+
+ .commentF {
+ padding: 0px;
+ margin: auto;
+ padding-top: 0px;
+ paddin-bottom: 20px;
+ margin-top: 0;
+ }
+
+ tb {
+ border = 1;
+ }
+
+ .wishlist-row {
+ background-color: #B4FF9B;
+ width: auto;
+ }
+
+ .minor-row {
+ background-color: #FCFF98;
+ width: auto;
+ }
+
+
+ .serious-row {
+ background-color: #FFB648;
+ width: auto;
+ }
+
+ .critical-row {
+ background-color: #FF752A;
+ width: auto;
+ }
+
+ .fatal-row {
+ background-color: #FF3300;
+ width: auto;
+ }
+
+ .person {
+ font-family: courier;
+ }
+
+ a, a:visited {
+ background: inherit;
+ text-decoration: none;
+ }
+
+ a {
+ color: #003d41;
+ }
+
+ a:visited {
+ color: #553d41;
+ }
+
+ ul {
+ list-style-type: none;
+ padding: 0;
+ }
+
+ p {
+ width: auto;
+ }
+
+ .inline-status-image {
+ position: relative;
+ top: 0.2em;
+ }
+
+ .dimmed {
+ color: #bbb;
+ }
+
+ table {
+ border-style: 10px solid #313131;
+ border-spacing: 0;
+ width: auto;
+ }
+
+ table.log {
+ }
+
+ td {
+ border-width: 0;
+ border-style: none;
+ padding-right: 0.5em;
+ padding-left: 0.5em;
+ width: auto;
+ }
+
+ .td_sel {
+ background-color: #afafaf;
+ border: 1px solid #afafaf;
+ font-weight:bold;
+ padding-right: 1em;
+ padding-left: 1em;
+
+ }
+
+ .td_nsel {
+ border: 0px;
+ padding-right: 1em;
+ padding-left: 1em;
+ }
+
+ tr {
+ vertical-align: top;
+ width: auto;
+ }
+
+ h1 {
+ padding: 0.5em;
+ background-color: #305275;
+ margin-top: 0;
+ margin-bottom: 0;
+ color: #fff;
+ margin-left: -20px;
+ margin-right: -20px;
+ }
+
+ wid {
+ text-transform: uppercase;
+ font-size: smaller;
+ margin-top: 1em;
+ margin-left: -0.5em;
+ /*background: #fffbce;*/
+ /*background: #628a0d;*/
+ padding: 5px;
+ color: #305275;
+ }
+
+ .attrname {
+ text-align: right;
+ font-size: smaller;
+ }
+
+ .attrval {
+ color: #222;
+ }
+
+ .issue-closed-fixed {
+ background-image: "green-check.png";
+ }
+
+ .issue-closed-wontfix {
+ background-image: "red-check.png";
+ }
+
+ .issue-closed-reorg {
+ background-image: "blue-check.png";
+ }
+
+ .inline-issue-link {
+ text-decoration: underline;
+ }
+
+ img {
+ border: 0;
+ }
+
+
+ div.footer {
+ font-size: small;
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin: auto;
+ background: #305275;
+ color: #fffee7;
+ }
+
+ .footer a {
+ color: #508d91;
+ }
+
+
+ .header {
+ font-family: "lucida grande", "sans serif";
+ font-size: smaller;
+ background-color: #a9a9a9;
+ text-align: left;
+
+ padding-right: 0.5em;
+ padding-left: 0.5em;
+
+ }
+
+
+ .selected-cell {
+ background-color: #e9e9e2;
+ }
+
+ .plain-cell {
+ background-color: #f9f9f9;
+ }
+
+
+ .logcomment {
+ padding-left: 4em;
+ font-size: smaller;
+ }
+
+ .id {
+ font-family: courier;
+ }
+
+ .table_bug {
+ background-color: #afafaf;
+ border: 2px solid #afafaf;
+ }
+
+ .message {
+ }
+
+ .progress-meter-done {
+ background-color: #03af00;
+ }
+
+ .progress-meter-undone {
+ background-color: #ddd;
+ }
+
+ .progress-meter {
+ }
+
+ """
+
+ self.index_first = """
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>BugsEverywhere Issue Tracker</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%s" />
+ <link rel="stylesheet" href="style.css" type="text/css" />
+ </head>
+ <body>
+
+
+ <div class="main">
+ <h1>BugsEverywhere Bug List</h1>
+ <p></p>
+ <table>
+
+ <tr>
+ <td class="%%s"><a href="index.html">Active Bugs</a></td>
+ <td class="%%s"><a href="index_inactive.html">Inactive Bugs</a></td>
+ </tr>
+
+ </table>
+ <table class="table_bug">
+ <tbody>
+ """ % self.bd.encoding
+
+ self.bug_line ="""
+ <tr class="%s-row">
+ <td ><a href="bugs/%s.html">%s</a></td>
+ <td ><a href="bugs/%s.html">%s</a></td>
+ <td><a href="bugs/%s.html">%s</a></td>
+ <td><a href="bugs/%s.html">%s</a></td>
+ <td><a href="bugs/%s.html">%s</a></td>
+ </tr>
+ """
+
+ self.detail_first = """
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>BugsEverywhere Issue Tracker</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%s" />
+ <link rel="stylesheet" href="../style.css" type="text/css" />
+ </head>
+ <body>
+
+
+ <div class="main">
+ <h1>BugsEverywhere Bug List</h1>
+ <h5><a href="%%s">Back to Index</a></h5>
+ <h2>Bug: _bug_id_</h2>
+ <table >
+ <tbody>
+ """ % self.bd.encoding
+
+
+
+ self.detail_line ="""
+ <tr>
+ <td align="right">%s</td><td>%s</td>
+ </tr>
+ """
+
+ self.index_last = """
+ </tbody>
+ </table>
+
+ </div>
+
+ <div class="footer">Generated by <a href="http://www.bugseverywhere.org/">BugsEverywhere</a> on %s</div>
+
+ </body>
+ </html>
+ """
+
+ self.comment_section = """
+ """
+
+ self.begin_comment_section ="""
+ <tr>
+ <td align="right">Comments:
+ </td>
+ <td>
+ """
+
+
+ self.end_comment_section ="""
+ </td>
+ </tr>
+ """
+
+ self.detail_last = """
+ </tbody>
+ </table>
+ </div>
+ <h5><a href="%s">Back to Index</a></h5>
+ <div class="footer">Generated by <a href="http://www.bugseverywhere.org/">BugsEverywhere</a>.</div>
+ </body>
+ </html>
+ """
+
+
+ def create_index_file(self, out_dir_path, summary, bugs, ordered_bug, fileid, encoding):
+ try:
+ os.stat(out_dir_path)
+ except:
+ try:
+ os.mkdir(out_dir_path)
+ except:
+ raise cmdutil.UsageError, "Cannot create output directory."
+ try:
+ FO = codecs.open(out_dir_path+"/style.css", "w", encoding)
+ FO.write(self.css_file)
+ FO.close()
+ except:
+ raise cmdutil.UsageError, "Cannot create the style.css file."
+
+ try:
+ os.mkdir(out_dir_path+"/bugs")
+ except:
+ pass
+
+ try:
+ if fileid == "active":
+ FO = codecs.open(out_dir_path+"/index.html", "w", encoding)
+ FO.write(self.index_first%('td_sel','td_nsel'))
+ if fileid == "inactive":
+ FO = codecs.open(out_dir_path+"/index_inactive.html", "w", encoding)
+ FO.write(self.index_first%('td_nsel','td_sel'))
+ except:
+ raise cmdutil.UsageError, "Cannot create the index.html file."
+
+ c = 0
+ t = len(bugs) - 1
+ for l in range(t, -1, -1):
+ line = self.bug_line%(escape(bugs[l].severity),
+ escape(bugs[l].uuid), escape(bugs[l].uuid[0:3]),
+ escape(bugs[l].uuid), escape(bugs[l].status),
+ escape(bugs[l].uuid), escape(bugs[l].severity),
+ escape(bugs[l].uuid), escape(bugs[l].summary),
+ escape(bugs[l].uuid), escape(bugs[l].time_string)
+ )
+ FO.write(line)
+ c += 1
+ self.create_detail_file(bugs[l], out_dir_path, fileid, encoding)
+ when = time.ctime()
+ FO.write(self.index_last%when)
+
+
+ def create_detail_file(self, bug, out_dir_path, fileid, encoding):
+ f = "%s.html"%bug.uuid
+ p = out_dir_path+"/bugs/"+f
+ try:
+ FD = codecs.open(p, "w", encoding)
+ except:
+ raise cmdutil.UsageError, "Cannot create the detail html file."
+
+ detail_first_ = re.sub('_bug_id_', bug.uuid[0:3], self.detail_first)
+ if fileid == "active":
+ FD.write(detail_first_%"../index.html")
+ if fileid == "inactive":
+ FD.write(detail_first_%"../index_inactive.html")
+
+
+
+ bug_ = self.bd.bug_from_shortname(bug.uuid)
+ bug_.load_comments(load_full=True)
+
+ FD.write(self.detail_line%("ID : ", bug.uuid))
+ FD.write(self.detail_line%("Short name : ", escape(bug.uuid[0:3])))
+ FD.write(self.detail_line%("Severity : ", escape(bug.severity)))
+ FD.write(self.detail_line%("Status : ", escape(bug.status)))
+ FD.write(self.detail_line%("Assigned : ", escape(bug.assigned)))
+ FD.write(self.detail_line%("Target : ", escape(bug.target)))
+ FD.write(self.detail_line%("Reporter : ", escape(bug.reporter)))
+ FD.write(self.detail_line%("Creator : ", escape(bug.creator)))
+ FD.write(self.detail_line%("Created : ", escape(bug.time_string)))
+ FD.write(self.detail_line%("Summary : ", escape(bug.summary)))
+ FD.write("<tr><td colspan=\"2\"><hr /></td></tr>")
+ FD.write(self.begin_comment_section)
+ tr = []
+ b = ''
+ level = 0
+ stack = []
+ for depth,comment in bug_.comment_root.thread(flatten=False):
+ while len(stack) > depth:
+ stack.pop(-1) # pop non-parents off the stack
+ FD.write("</div>\n") # close non-parent <div class="comment...
+ assert len(stack) == depth
+ stack.append(comment)
+ lines = ["--------- Comment ---------",
+ "Name: %s" % comment.uuid,
+ "From: %s" % escape(comment.author),
+ "Date: %s" % escape(comment.date),
+ ""]
+ lines.extend(escape(comment.body).splitlines())
+ if depth == 0:
+ FD.write('<div class="commentF">')
+ else:
+ FD.write('<div class="comment">')
+ FD.write("<br />\n".join(lines)+"<br />\n")
+ while len(stack) > 0:
+ stack.pop(-1)
+ FD.write("</div>\n") # close every remaining <div class="comment...
+ FD.write(self.end_comment_section)
+ if fileid == "active":
+ FD.write(self.detail_last%"../index.html")
+ if fileid == "inactive":
+ FD.write(self.detail_last%"../index_inactive.html")
+ FD.close()
+
+
diff --git a/becommands/init.py b/becommands/init.py
index 5b2a416..1125d93 100644
--- a/becommands/init.py
+++ b/becommands/init.py
@@ -19,9 +19,9 @@ 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
+ >>> from libbe import utility, vcs
>>> import os
>>> dir = utility.Dir()
>>> try:
@@ -29,28 +29,28 @@ 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)
>>> dir = utility.Dir()
>>> os.chdir(dir.path)
- >>> rcs = rcs.installed_rcs()
- >>> rcs.init('.')
- >>> print rcs.name
+ >>> vcs = vcs.installed_vcs()
+ >>> vcs.init('.')
+ >>> print vcs.name
Arch
- >>> execute([], test=True)
+ >>> execute([], manipulate_encodings=False)
Using Arch for revision control.
Directory initialized.
- >>> rcs.cleanup()
+ >>> vcs.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('/')
@@ -64,14 +64,14 @@ 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:
raise cmdutil.UserError("Directory already initialized: %s" % options.root_dir)
bd.save()
- if bd.rcs.name is not "None":
- print "Using %s for revision control." % bd.rcs.name
+ if bd.vcs.name is not "None":
+ print "Using %s for revision control." % bd.vcs.name
else:
print "No revision control detected."
print "Directory initialized."
@@ -86,7 +86,7 @@ def get_parser():
longhelp="""
This command initializes Bugs Everywhere support for the specified directory
and all its subdirectories. It will auto-detect any supported revision control
-system. You can use "be set rcs_name" to change the rcs being used.
+system. You can use "be set vcs_name" to change the vcs being used.
The directory defaults to your current working directory.
diff --git a/becommands/list.py b/becommands/list.py
index 5ba1821..12e1e29 100644
--- a/becommands/list.py
+++ b/becommands/list.py
@@ -26,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)
@@ -46,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:
diff --git a/becommands/merge.py b/becommands/merge.py
index 4aaefa8..f212b01 100644
--- a/becommands/merge.py
+++ b/becommands/merge.py
@@ -18,10 +18,10 @@ 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
@@ -37,7 +37,7 @@ def execute(args, test=False):
>>> dummy = dummy.new_reply("1 2 3 4")
>>> dummy.time = 2
>>> 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")
@@ -120,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)
@@ -133,10 +134,11 @@ 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)
diff --git a/becommands/new.py b/becommands/new.py
index af599d7..a8ee2ec 100644
--- a/becommands/new.py
+++ b/becommands/new.py
@@ -19,16 +19,16 @@ 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
@@ -38,13 +38,15 @@ def execute(args, test=False):
minor
>>> 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)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if args[0] == '-': # read summary from stdin
summary = sys.stdin.readline()
else:
diff --git a/becommands/open.py b/becommands/open.py
index 2ef5f43..0c6bf05 100644
--- a/becommands/open.py
+++ b/becommands/open.py
@@ -20,17 +20,18 @@
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)
@@ -40,8 +41,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 = "open"
def get_parser():
diff --git a/becommands/remove.py b/becommands/remove.py
index d79a7be..8d85033 100644
--- a/becommands/remove.py
+++ b/becommands/remove.py
@@ -17,22 +17,23 @@
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)
@@ -40,8 +41,9 @@ 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)
print "Removed bug %s" % bug.uuid
diff --git a/becommands/set.py b/becommands/set.py
index 0c0862f..f7e68d3 100644
--- a/becommands/set.py
+++ b/becommands/set.py
@@ -19,7 +19,7 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Change tree settings"""
import textwrap
-from libbe import cmdutil, bugdir, rcs, settings_object
+from libbe import cmdutil, bugdir, vcs, settings_object
__desc__ = __doc__
def _value_string(bd, setting):
@@ -32,26 +32,28 @@ def _value_string(bd, setting):
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()
@@ -85,12 +87,12 @@ def get_bugdir_settings():
set = getattr(bugdir.BugDir, s)
dstr = set.__doc__.strip()
# per-setting comment adjustments
- if s == "rcs_name":
+ if s == "vcs_name":
lines = dstr.split('\n')
while lines[0].startswith("This property defaults to") == False:
lines.pop(0)
assert len(lines) != None, \
- "Unexpected rcs_name docstring:\n '%s'" % dstr
+ "Unexpected vcs_name docstring:\n '%s'" % dstr
lines.insert(
0, "The name of the revision control system to use.\n")
dstr = '\n'.join(lines)
diff --git a/becommands/severity.py b/becommands/severity.py
index 65467e3..660586e 100644
--- a/becommands/severity.py
+++ b/becommands/severity.py
@@ -20,27 +20,29 @@
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:
diff --git a/becommands/show.py b/becommands/show.py
index e43cfb9..50bd6eb 100644
--- a/becommands/show.py
+++ b/becommands/show.py
@@ -22,12 +22,12 @@ 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,7 @@ 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>
@@ -50,6 +50,7 @@ def execute(args, test=False):
<created>...</created>
<summary>Bug A</summary>
</bug>
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -57,7 +58,8 @@ def execute(args, test=False):
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)
+ 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:
@@ -72,7 +74,7 @@ def execute(args, test=False):
is_comment = False
if is_comment == True and options.comments == False:
continue
- bug = bd.bug_from_shortname(bugname)
+ bug = cmdutil.bug_from_shortname(bd, bugname)
if is_comment == False:
if options.XML:
print bug.xml(show_comments=options.comments)
diff --git a/becommands/status.py b/becommands/status.py
index edc948d..f315003 100644
--- a/becommands/status.py
+++ b/becommands/status.py
@@ -17,27 +17,29 @@
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:
@@ -55,7 +57,8 @@ def get_parser():
def help():
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])
diff --git a/becommands/subscribe.py b/becommands/subscribe.py
new file mode 100644
index 0000000..0a23057
--- /dev/null
+++ b/becommands/subscribe.py
@@ -0,0 +1,390 @@
+# 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
+ if options.list_all == True:
+ entity_name = "anything in the bug directory"
+
+ types = [type_from_name(name, type_root, default=INVALID_TYPE,
+ default_ok=options.unsubscribe)
+ for name in types]
+ estrs = entity.extra_strings
+ if options.list == True or options.list_all == True:
+ pass
+ else: # alter subscriptions
+ 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
+
+ if options.list_all == True:
+ bd.load_all_bugs()
+ subscriptions = get_bugdir_subscribers(bd, servers[0])
+ else:
+ 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("-a", "--list-all", action="store_true",
+ dest="list_all", default=False,
+ help="List all subscribers (no ID argument, read only action).")
+ parser.add_option("-l", "--list", action="store_true",
+ dest="list", default=False,
+ help="List subscribers (read only action).")
+ 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:
+ if not string.startswith(TAG):
+ continue
+ subscriber,types,servers = _parse_string(string, type_root)
+ type_match = False
+ if type in types:
+ type_match = True
+ if type_match == False and match_ancestor_types == True:
+ for t in types:
+ if t.has_descendant(type):
+ type_match = True
+ break
+ if type_match == False and match_descendant_types == True:
+ for t in types:
+ if type.has_descendant(t):
+ type_match = True
+ break
+ server_match = False
+ if server in servers or servers == ["*"] or server == "*":
+ server_match = True
+ if type_match == True and server_match == True:
+ yield subscriber
+
+def get_bugdir_subscribers(bugdir, server):
+ """
+ I have a bugdir. Who cares about it, and what do they care about?
+ Returns a dict of dicts:
+ subscribers[user][id] = types
+ where id is either a bug.uuid (in the case of a bug subscription)
+ or "DIR" (in the case of a bugdir subscription).
+
+ Only checks bugs that are currently in memory, so you might want
+ to call bugdir.load_all_bugs() first.
+
+ >>> bd = bugdir.SimpleBugDir(sync_with_disk=False)
+ >>> a = bd.bug_from_shortname("a")
+ >>> bd.extra_strings = subscribe(bd.extra_strings, "John Doe <j@doe.com>", [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 216ffbc..ecd853f 100644
--- a/becommands/tag.py
+++ b/becommands/tag.py
@@ -18,34 +18,34 @@ 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
@@ -57,15 +57,16 @@ def execute(args, test=False):
>>> a.extra_strings = []
>>> print a.extra_strings
[]
- >>> 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
diff --git a/becommands/target.py b/becommands/target.py
index 527b16a..7e41451 100644
--- a/becommands/target.py
+++ b/becommands/target.py
@@ -22,21 +22,22 @@
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)
@@ -46,14 +47,15 @@ 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:
print "No target assigned."
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/interfaces/email/interactive/README b/interfaces/email/interactive/README
new file mode 100644
index 0000000..79ef9a9
--- /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>] commit message
+ control [be-bug] commit message
+These are analogous to submit@bugs.debian.org, nnn@bugs.debian.org,
+and control@bugs.debian.org respectively.
+
+Creating bugs
+=============
+
+This interface creates a bug whose summary is given by the email's
+post-tag subject. The body of the email must begin with a
+pseudo-header containing at least the "Version" field. Anything after
+the pseudo-header and before a line starting with '--' is, if present,
+attached as the bug's first comment.
+
+ From jdoe@example.com Fri Apr 18 12:00:00 2008
+ From: John Doe <jdoe@example.com>
+ Date: Fri, 18 Apr 2008 12:00:00 +0000
+ Content-Type: text/plain; charset=UTF-8
+ Content-Transfer-Encoding: 8bit
+ Subject: [be-bug:submit] Need tests for the email interface.
+
+ Version: XYZ
+ Severity: minor
+
+ Someone should write up a series of test emails to send into
+ be-handle mail so we can test changes quickly without having to
+ use procmail.
+
+ --
+ Goofy tagline not included.
+
+Available pseudo-headers are Version, Reporter, Assign, Depend,
+Severity, Status, Tag, and Target.
+
+Commenting on bugs
+==================
+
+This interface appends a comment to the bug specified in the subject
+tag. The the first non-multipart body is attached with the
+appropriate content-type. In the case of "text/plain" contents,
+anything following a line starting with '--' is stripped.
+
+ From jdoe@example.com Fri Apr 18 12:00:00 2008
+ From: John Doe <jdoe@example.com>
+ Date: Fri, 18 Apr 2008 12:00:00 +0000
+ Content-Type: text/plain; charset=UTF-8
+ Content-Transfer-Encoding: 8bit
+ Subject: [be-bug:XYZ] Isolated problem in baz()
+
+ Finally tracked it down to the bar() call. Some sort of
+ string<->unicode conversion problem. Solution ideas?
+
+ --
+ Goofy tagline not included.
+
+Controlling bugs
+================
+
+This interface consists of a list of allowed be commands, with one
+command per line. Blank lines and lines beginning with '#' are
+ignored, as well anything following a line starting with '--'. All
+the listed commands are executed in order and their output returned.
+The commands are split into arguments with the POSIX-compliant
+shlex.split().
+
+ From jdoe@example.com Fri Apr 18 12:00:00 2008
+ From: John Doe <jdoe@example.com>
+ Date: Fri, 18 Apr 2008 12:00:00 +0000
+ Content-Type: text/plain; charset=UTF-8
+ Content-Transfer-Encoding: 8bit
+ Subject: [be-bug] I'll handle XYZ by release 1.2.3
+
+ assign XYZ "John Doe <jdoe@example.com>"
+ status XYZ assigned
+ severity XYZ critical
+ target XYZ 1.2.3
+
+ --
+ Goofy tagline ignored.
+
+Example emails
+==============
+
+Take a look at 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..fa80698
--- /dev/null
+++ b/interfaces/email/interactive/be-handle-mail
@@ -0,0 +1,950 @@
+#!/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 add comments to a bug. The first non-multipart portion
+of the email is used as the comment body. If that portion has a
+"text/plain" type, any text after and including a possible line
+starting with BREAK is stripped to avoid lots of taglines cluttering
+up the repository.
+
+Control emails preform any allowed BE commands. The first
+non-multipart portion of the email is used as the comment body. If
+that portion has a "text/plain" type, any text after and including a
+possible line starting with BREAK is stripped. Each pre-BREAK line of
+the portion should be a valid BE command, with the initial "be"
+omitted, e.g. "be status XYZ fixed" --> "status XYZ fixed".
+
+Any changes made to the repository are commited after the email is
+executed, with the email's post-tag subject as the commit message.
+"""
+
+import codecs
+import StringIO as StringIO
+import email
+from email.mime.multipart import MIMEMultipart
+import email.utils
+import os
+import os.path
+import re
+import shlex
+import sys
+import time
+import traceback
+import 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",
+ u"Confirm", u"Subscribe"]
+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:
+ orig_stdin = sys.stdin
+ sys.stdin = StringIO.StringIO(self.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 = 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):
+ if self.ret == None: self.ret = -1
+ 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)
+ self.confirm = True # enable/disable confirmation email
+ def _yes_no(self, boolean):
+ if boolean == True:
+ return "yes"
+ return "no"
+ def author_tuple(self):
+ """
+ Extract and normalize the sender's email address. Returns a
+ (name, email) tuple.
+ """
+ if not hasattr(self, "author_tuple_cache"):
+ self._author_tuple_cache = \
+ send_pgp_mime.source_email(self.msg, return_realname=True)
+ return self._author_tuple_cache
+ def author_addr(self):
+ return email.utils.formataddr(self.author_tuple())
+ def author_name(self):
+ return self.author_tuple()[0]
+ def author_email(self):
+ return self.author_tuple()[1]
+ def default_msg_attribute_access(self, attr_name, default=None):
+ if attr_name in self.msg:
+ return self.msg[attr_name]
+ return default
+ def message_id(self, default=None):
+ return self.default_msg_attribute_access("message-id", default=default)
+ def subject(self):
+ if "subject" not in self.msg:
+ raise InvalidSubject(self, u"Email must contain a subject")
+ return self.msg["subject"]
+ def _split_subject(self):
+ """
+ Returns (tag, subject), with missing values replaced by None.
+ """
+ if hasattr(self, "_split_subject_cache"):
+ return self._split_subject_cache
+ args = self.subject().split(u"]",1)
+ if len(args) < 1:
+ self._split_subject_cache = (None, None)
+ elif len(args) < 2:
+ self._split_subject_cache = (args[0]+u"]", None)
+ else:
+ self._split_subject_cache = (args[0]+u"]", args[1].strip())
+ return self._split_subject_cache
+ def _subject_tag_type(self):
+ """
+ Parse subject tag, return (type, value), where type is one of
+ None, "new", "comment", or "control"; and value is None except
+ in the case of "comment", in which case it's the bug
+ ID/shortname.
+ """
+ tag,subject = self._split_subject()
+ type = None
+ value = None
+ if tag == SUBJECT_TAG_NEW:
+ type = u"new"
+ elif tag == SUBJECT_TAG_CONTROL:
+ type = u"control"
+ else:
+ match = SUBJECT_TAG_COMMENT.match(tag)
+ if len(match.groups()) == 1:
+ type = u"comment"
+ value = match.group(1)
+ return (type, value)
+ def validate_subject(self):
+ """
+ Validate the subject line.
+ """
+ tag,subject = self._split_subject()
+ if not tag.startswith(SUBJECT_TAG_START):
+ raise InvalidSubject(
+ self, u"Subject must start with '%s'" % SUBJECT_TAG_START)
+ tag_type,value = self._subject_tag_type()
+ if tag_type == None:
+ raise InvalidSubject(self, u"Invalid tag '%s'" % tag)
+ elif tag_type == u"new" and len(subject) == 0:
+ raise InvalidSubject(self, u"Cannot create a bug with blank title")
+ elif tag_type == u"comment" and len(value) == 0:
+ raise InvalidSubject(self, u"Must specify a bug ID to comment")
+ def _get_bodies_and_mime_types(self):
+ """
+ Traverse the email message returning (body, mime_type) for
+ each non-mulitpart portion of the message.
+ """
+ msg_charset = self.msg.get_content_charset(ENCODING).lower()
+ for part in self.msg.walk():
+ if part.is_multipart():
+ continue
+ body,mime_type=(part.get_payload(decode=True),part.get_content_type())
+ charset = part.get_content_charset(msg_charset).lower()
+ if mime_type.startswith("text/"):
+ body = unicode(body, charset) # convert text types to unicode
+ yield (body, mime_type)
+ def _parse_body_pseudoheaders(self, body, required, optional,
+ dictionary=None):
+ """
+ Grab any pseudo-headers from the beginning of body. Raise
+ InvalidPseudoHeader on errors. Returns the body text after
+ the pseudo-header and a dictionary of set options. If you
+ like, you can initialize the dictionary with some defaults
+ and pass your initialized dict in as dictionary.
+ """
+ if dictionary == None:
+ dictionary = {}
+ body_lines = body.splitlines()
+ all = required+optional
+ for i,line in enumerate(body_lines):
+ line = line.strip()
+ if len(line) == 0:
+ break
+ if ":" not in line:
+ raise InvalidPseudoheader(self, line)
+ key,value = line.split(":", 1)
+ value = value.strip()
+ if key not in all:
+ raise InvalidPseudoHeader(self, key)
+ if len(value) == 0:
+ raise InvalidEmail(
+ self, u"Blank value for: %s" % key)
+ dictionary[key] = value
+ missing = []
+ for key in required:
+ if key not in dictionary:
+ missing.append(key)
+ if len(missing) > 0:
+ raise InvalidPseudoHeader(self,
+ u"Missing required pseudo-headers:\n%s"
+ % u", ".join(missing))
+ remaining_body = u"\n".join(body_lines[i:]).strip()
+ return (remaining_body, dictionary)
+ def _strip_footer(self, body):
+ body_lines = body.splitlines()
+ for i,line in enumerate(body_lines):
+ if line.startswith(BREAK):
+ break
+ i += 1 # increment past the current valid line.
+ return u"\n".join(body_lines[:i]).strip()
+ def parse(self):
+ """
+ Parse the commands given in the email. Raises assorted
+ subclasses of InvalidEmail in the case of invalid messages,
+ otherwise returns a list of suggested commands to run.
+ """
+ self.validate_subject()
+ tag_type,value = self._subject_tag_type()
+ if tag_type == u"new":
+ commands = self.parse_new()
+ elif tag_type == u"comment":
+ commands = self.parse_comment(value)
+ elif tag_type == u"control":
+ commands = self.parse_control()
+ else:
+ raise Exception, u"Unrecognized tag type '%s'" % tag_type
+ return commands
+ def parse_new(self):
+ command = u"new"
+ tag,subject = self._split_subject()
+ summary = subject
+ options = {u"Reporter": self.author_addr(),
+ u"Confirm": self._yes_no(self.confirm),
+ u"Subscribe": "no",
+ }
+ body,mime_type = list(self._get_bodies_and_mime_types())[0]
+ comment_body,options = \
+ self._parse_body_pseudoheaders(body,
+ NEW_REQUIRED_PSEUDOHEADERS,
+ NEW_OPTIONAL_PSEUDOHEADERS,
+ options)
+ if options[u"Confirm"].lower() == "no":
+ self.confirm = False
+ if options[u"Subscribe"].lower() == "yes" and self.confirm == True:
+ # respond with the subscription format rather than the
+ # normal command-output format, because the subscription
+ # format is more user-friendly.
+ self.confirm = False
+ args = [u"--reporter", options[u"Reporter"]]
+ args.append(summary)
+ commands = [Command(self, command, args)]
+ id = ID(commands[0])
+ comment_body = self._strip_footer(comment_body)
+ if len(comment_body) > 0:
+ command = u"comment"
+ comment = u"Version: %s\n\n"%options[u"Version"] + comment_body
+ args = [u"--author", self.author_addr(),
+ u"--alt-id", self.message_id(),
+ u"--content-type", mime_type]
+ args.append(id)
+ args.append(u"-")
+ commands.append(Command(self, u"comment", args, stdin=comment))
+ for key,value in options.items():
+ if key in [u"Version", u"Reporter", u"Confirm"]:
+ continue # we've already handled these options
+ command = key.lower()
+ args = [id, value]
+ if key == u"Subscribe":
+ if value.lower() != "yes":
+ continue
+ args = ["--subscriber", self.author_addr(), id]
+ commands.append(Command(self, command, args))
+ return commands
+ def parse_comment(self, bug_uuid):
+ command = u"comment"
+ bug_id = bug_uuid
+ author = self.author_addr()
+ alt_id = self.message_id()
+ body,mime_type = list(self._get_bodies_and_mime_types())[0]
+ if mime_type == "text/plain":
+ body = self._strip_footer(body)
+ content_type = mime_type
+ args = [u"--author", author, u"--alt-id", alt_id,
+ u"--content-type", content_type, bug_id, u"-"]
+ commands = [Command(self, command, args, stdin=body)]
+ return commands
+ def parse_control(self):
+ body,mime_type = list(self._get_bodies_and_mime_types())[0]
+ commands = []
+ for line in body.splitlines():
+ line = line.strip()
+ if line.startswith(CONTROL_COMMENT) or len(line) == 0:
+ continue
+ if line.startswith(BREAK):
+ break
+ 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.")
+ 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.vcs.versioned == False: # no way to tell what's changed
+ raise NotificationFailed("Not versioned")
+
+ bd.load_all_bugs()
+ 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 = []
+ if "DIR" in subscriptions: # make sure we check the DIR level first
+ ordered_subscriptions = [("DIR", subscriptions.pop("DIR"))]
+ else:
+ ordered_subscriptions = []
+ ordered_subscriptions.extend(subscriptions.items())
+ for id,types in ordered_subscriptions:
+ if id == "DIR":
+ if subscribe.BUGDIR_TYPE_ALL in types:
+ parts.append(diff_tree.report_or_none())
+ break # we've attached everything, so stop checking.
+ 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
+ # if we get this far, id refers to a bug.
+ assert types == [subscribe.BUG_TYPE_ALL], types
+ if id not in bug_index:
+ continue # no changes here, move on to next id
+ type,bug_root = bug_index[id]
+ if type == "added" \
+ and "DIR" in subscriptions \
+ and subscriptions["DIR"] == subscribe.BUGDIR_TYPE_NEW:
+ # this info already attached at the DIR level
+ continue # move on to next 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.vcs.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)
+ elif m.confirm == True:
+ if LOGFILE != None:
+ LOGFILE.write(u"Sending response to %s\n" % m.author_addr())
+ LOGFILE.write(u"\n%s\n\n" % send_pgp_mime.flatten(response,
+ to_unicode=True))
+ send_pgp_mime.mail(response, send_pgp_mime.sendmail)
+ else:
+ if LOGFILE != None:
+ LOGFILE.write(u"Response declined by %s\n" % m.author_addr())
+ if options.subscribers == True:
+ if LOGFILE != None:
+ LOGFILE.write(u"Checking for subscribers\n")
+ try:
+ emails = m.subscriber_emails()
+ except NotificationFailed, e:
+ if LOGFILE != None:
+ LOGFILE.write(unicode(e) + u"\n")
+ else:
+ for msg in emails:
+ if options.output == True:
+ print send_pgp_mime.flatten(msg, to_unicode=True)
+ else:
+ send_pgp_mime.mail(msg, send_pgp_mime.sendmail)
+
+ close_logfile()
+
+
+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/interfaces/email/interactive/examples/blank b/interfaces/email/interactive/examples/blank
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ 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..c19483e
--- /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 = PGPMimeMessageFactory('check 1 2\\ncheck 1 2\\n')
+ >>> print flatten(m.clearBodyPart())
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: inline
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ >>> print flatten(m.plain())
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ >>> signed = m.sign(header)
+ >>> signed.set_boundary('boundsep')
+ >>> print flatten(signed).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ Content-Type: multipart/signed; protocol="application/pgp-signature";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: inline
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Description: signature
+ Content-Type: application/pgp-signature; name="signature.asc";
+ charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP SIGNATURE-----
+ ...
+ -----END PGP SIGNATURE-----
+ <BLANKLINE>
+ --boundsep--
+ >>> encrypted = m.encrypt(header)
+ >>> encrypted.set_boundary('boundsep')
+ >>> print flatten(encrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ Content-Type: multipart/encrypted;
+ protocol="application/pgp-encrypted";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: application/pgp-encrypted
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ Version: 1
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Type: application/octet-stream; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP MESSAGE-----
+ ...
+ -----END PGP MESSAGE-----
+ <BLANKLINE>
+ --boundsep--
+ >>> signedAndEncrypted = m.signAndEncrypt(header)
+ >>> signedAndEncrypted.set_boundary('boundsep')
+ >>> print flatten(signedAndEncrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ Content-Type: multipart/encrypted;
+ protocol="application/pgp-encrypted";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: application/pgp-encrypted
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ Version: 1
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Type: application/octet-stream; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP MESSAGE-----
+ ...
+ -----END PGP MESSAGE-----
+ <BLANKLINE>
+ --boundsep--
+ """
+ def __init__(self, body):
+ self.body = body
+ def clearBodyPart(self):
+ body = encodedMIMEText(self.body)
+ body.add_header('Content-Disposition', 'inline')
+ return body
+ def passphrase_arg(self, passphrase=None):
+ if passphrase == None and PASSPHRASE != None:
+ passphrase = PASSPHRASE
+ if passphrase == None:
+ return (None,'')
+ return (passphrase, pgp_stdin_passphrase_arg)
+ def plain(self):
+ """
+ text/plain
+ """
+ return encodedMIMEText(self.body)
+ def sign(self, header, passphrase=None):
+ """
+ multipart/signed
+ +-> text/plain (body)
+ +-> application/pgp-signature (signature)
+ """
+ passphrase,pass_arg = self.passphrase_arg(passphrase)
+ body = self.clearBodyPart()
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ args = replace(pgp_sign_command, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % source_email(header)
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ args = replace(args, 'p', pass_arg)
+ status,output,error = execute(args, stdin=passphrase)
+ signature = output
+
+ sig = MIMEApplication(_data=signature,
+ _subtype='pgp-signature; name="signature.asc"',
+ _encoder=encode_7or8bit)
+ sig['Content-Description'] = 'signature'
+ sig.set_charset('us-ascii')
+
+ msg = MIMEMultipart('signed', micalg='pgp-sha1',
+ protocol='application/pgp-signature')
+ msg.attach(body)
+ msg.attach(sig)
+
+ msg['Content-Disposition'] = 'inline'
+ return msg
+ def encrypt(self, header, passphrase=None):
+ """
+ multipart/encrypted
+ +-> application/pgp-encrypted (control information)
+ +-> application/octet-stream (body)
+ """
+ body = self.clearBodyPart()
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ recipients = [replace(pgp_recipient_arg, 'r', recipient)
+ for recipient in target_emails(header)]
+ recipient_string = ' '.join(recipients)
+ args = replace(pgp_encrypt_only_command, 'R', recipient_string)
+ args = replace(args, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % source_email(header)
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ status,output,error = execute(args)
+ encrypted = output
+
+ enc = MIMEApplication(_data=encrypted, _subtype='octet-stream',
+ _encoder=encode_7or8bit)
+ enc.set_charset('us-ascii')
+
+ control = MIMEApplication(_data='Version: 1\n', _subtype='pgp-encrypted',
+ _encoder=encode_7or8bit)
+
+ msg = MIMEMultipart('encrypted', micalg='pgp-sha1',
+ protocol='application/pgp-encrypted')
+ msg.attach(control)
+ msg.attach(enc)
+
+ msg['Content-Disposition'] = 'inline'
+ return msg
+ def signAndEncrypt(self, header, passphrase=None):
+ """
+ multipart/encrypted
+ +-> application/pgp-encrypted (control information)
+ +-> application/octet-stream (body)
+ """
+ passphrase,pass_arg = self.passphrase_arg(passphrase)
+ body = self.sign(header, passphrase)
+ body.__delitem__('Bcc')
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ recipients = [replace(pgp_recipient_arg, 'r', recipient)
+ for recipient in target_emails(header)]
+ recipient_string = ' '.join(recipients)
+ args = replace(pgp_encrypt_only_command, 'R', recipient_string)
+ args = replace(args, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % source_email(header)
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ args = replace(args, 'p', pass_arg)
+ status,output,error = execute(args, stdin=passphrase)
+ encrypted = output
+
+ enc = MIMEApplication(_data=encrypted, _subtype='octet-stream',
+ _encoder=encode_7or8bit)
+ enc.set_charset('us-ascii')
+
+ control = MIMEApplication(_data='Version: 1\n',
+ _subtype='pgp-encrypted',
+ _encoder=encode_7or8bit)
+
+ msg = MIMEMultipart('encrypted', micalg='pgp-sha1',
+ protocol='application/pgp-encrypted')
+ msg.attach(control)
+ msg.attach(enc)
+
+ msg['Content-Disposition'] = 'inline'
+ return msg
+
+def test():
+ import doctest
+ doctest.testmod()
+
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser(usage=usage)
+ parser.add_option('-t', '--test', dest='test', action='store_true',
+ help='Run doctests and exit')
+
+ parser.add_option('-H', '--header-file', dest='header_filename',
+ help='file containing email header', metavar='FILE')
+ parser.add_option('-B', '--body-file', dest='body_filename',
+ help='file containing email body', metavar='FILE')
+
+ parser.add_option('-P', '--passphrase-file', dest='passphrase_file',
+ help='file containing gpg passphrase', metavar='FILE')
+ parser.add_option('-p', '--passphrase-fd', dest='passphrase_fd',
+ help='file descriptor from which to read gpg passphrase (0 for stdin)',
+ type="int", metavar='DESCRIPTOR')
+
+ parser.add_option('--mode', dest='mode', default='sign',
+ help="One of 'sign', 'encrypt', 'sign-encrypt', or 'plain'. Defaults to %default.",
+ metavar='MODE')
+
+ parser.add_option('-a', '--sign-as', dest='sign_as',
+ help="The gpg key to sign with (gpg's -u/--local-user)",
+ metavar='KEY')
+
+ parser.add_option('--output', dest='output', action='store_true',
+ help="Don't mail the generated message, print it to stdout instead.")
+
+ (options, args) = parser.parse_args()
+
+ stdin_used = False
+
+ if options.passphrase_file != None:
+ PASSPHRASE = file(options.passphrase_file, 'r').read()
+ elif options.passphrase_fd != None:
+ if options.passphrase_fd == 0:
+ stdin_used = True
+ PASSPHRASE = sys.stdin.read()
+ else:
+ PASSPHRASE = os.read(options.passphrase_fd)
+
+ if options.sign_as:
+ PGP_SIGN_AS = options.sign_as
+
+ if options.test == True:
+ test()
+ sys.exit(0)
+
+ header = None
+ if options.header_filename != None:
+ if options.header_filename == '-':
+ assert stdin_used == False
+ stdin_used = True
+ header = sys.stdin.read()
+ else:
+ header = file(options.header_filename, 'r').read()
+ if header == None:
+ raise Exception, "missing header"
+ headermsg = header_from_text(header)
+ body = None
+ if options.body_filename != None:
+ if options.body_filename == '-':
+ assert stdin_used == False
+ stdin_used = True
+ body = sys.stdin.read()
+ else:
+ body = file(options.body_filename, 'r').read()
+ if body == None:
+ raise Exception, "missing body"
+
+ m = PGPMimeMessageFactory(body)
+ if options.mode == "sign":
+ bodymsg = m.sign(header)
+ elif options.mode == "encrypt":
+ bodymsg = m.encrypt(header)
+ elif options.mode == "sign-encrypt":
+ bodymsg = m.signAndEncrypt(header)
+ elif options.mode == "plain":
+ bodymsg = m.plain()
+ else:
+ print "Unrecognized mode '%s'" % options.mode
+
+ message = attach_root(headermsg, bodymsg)
+ if options.output == True:
+ message = flatten(message)
+ print message
+ else:
+ mail(message, sendmail)
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py b/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py
index a0d0ff9..50cc754 100644
--- a/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py
@@ -119,9 +119,9 @@ class Bug(PrestHandler):
assigned = None
bug.assigned = assigned
bug.save()
-# bug.rcs.precommit(bug.path)
-# bug.rcs.commit(bug.path, "Auto-commit")
-# bug.rcs.postcommit(bug.path)
+# bug.vcs.precommit(bug.path)
+# bug.vcs.commit(bug.path, "Auto-commit")
+# bug.vcs.postcommit(bug.path)
raise cherrypy.HTTPRedirect(bug_list_url(bug_data["project"]))
def instantiate(self, project, bug):
diff --git a/interfaces/xml/be-mbox-to-xml b/interfaces/xml/be-mbox-to-xml
index 335f92f..a740117 100755
--- a/interfaces/xml/be-mbox-to-xml
+++ b/interfaces/xml/be-mbox-to-xml
@@ -25,11 +25,10 @@ followed by a blank line.
import base64
import email.utils
from libbe.encoding import get_encoding, set_IO_stream_encodings
+from libbe.utility import time_to_str
from mailbox import mbox, Message # the mailbox people really want an on-disk copy
-from time import asctime, gmtime
+from time import asctime, gmtime, mktime
import types
-from xml.sax import make_parser
-from xml.sax.handler import ContentHandler
from xml.sax.saxutils import escape
DEFAULT_ENCODING = get_encoding()
@@ -37,14 +36,34 @@ set_IO_stream_encodings(DEFAULT_ENCODING)
KNOWN_IDS = []
+def normalize_email_address(address):
+ """
+ Standardize whitespace, etc.
+ """
+ return email.utils.formataddr(email.utils.parseaddr(address))
+
+def normalize_RFC_2822_date(date):
+ """
+ Some email clients write non-RFC 2822-compliant date tags like:
+ Fri, 18 Sep 2009 08:49:02 -0400 (EDT)
+ with the non-standard (EDT) timezone name. This funtion attempts
+ to deal with such inconsistencies.
+ """
+ time_tuple = email.utils.parsedate(date)
+ assert time_tuple != None, \
+ 'unparsable date: "%s"' % date
+ return time_to_str(mktime(time_tuple))
+
def 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'from'] = message[u'from']
+ new_fields[u'author'] = normalize_email_address(message[u'from'])
new_fields[u'date'] = message[u'date']
+ if new_fields[u'date'] != None:
+ new_fields[u'date'] = normalize_RFC_2822_date(new_fields[u'date'])
new_fields[u'content-type'] = message.get_content_type()
for k,v in new_fields.items():
if v != None and type(v) != types.UnicodeType:
@@ -67,25 +86,27 @@ def comment_message_to_xml(message, fields=None):
fields[u'in-reply-to'] = refs[0] # default to the first
else: # check for mutliple in-reply-to references.
refs = fields[u'in-reply-to'].split()
+ found_ref = False
for ref in refs: # search for a known reference id.
if ref in KNOWN_IDS:
fields[u'in-reply-to'] = ref
+ found_ref = True
break
- if fields[u'in-reply-to'] == None and len(refs) > 0:
+ if found_ref == False 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 fields[u'alt-id'] != None:
+ KNOWN_IDS.append(fields[u'alt-id'])
if message.is_multipart():
ret = []
alt_id = fields[u'alt-id']
- from_str = fields[u'from']
+ from_str = fields[u'author']
date = fields[u'date']
for m in message.walk():
if m == message:
continue
- fields[u'from'] = from_str
+ 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
diff --git a/interfaces/xml/be-xml-to-mbox b/interfaces/xml/be-xml-to-mbox
index ea77c34..c630447 100755
--- a/interfaces/xml/be-xml-to-mbox
+++ b/interfaces/xml/be-xml-to-mbox
@@ -129,7 +129,7 @@ class Comment (LimitedAttrDict):
u"alt-id",
u"short-name",
u"in-reply-to",
- u"from",
+ u"author",
u"date",
u"content-type",
u"body"]
@@ -137,7 +137,7 @@ class Comment (LimitedAttrDict):
if bug == None:
bug = Bug()
bug[u"uuid"] = u"no-uuid"
- name,addr = email.utils.parseaddr(self["from"])
+ name,addr = email.utils.parseaddr(self["author"])
print "From %s %s" % (addr, rfc2822_to_asctime(self["date"]))
if "uuid" in self: id = self["uuid"]
elif "alt-id" in self: id = self["alt-id"]
@@ -145,7 +145,7 @@ class Comment (LimitedAttrDict):
if id != None:
print "Message-ID: <%s@%s>" % (id, DEFAULT_DOMAIN)
print "Date: %s" % self["date"]
- print "From: %s" % self["from"]
+ print "From: %s" % self["author"]
subject = ""
if "short-name" in self:
subject += self["short-name"]+u": "
diff --git a/libbe/arch.py b/libbe/arch.py
index 2f45aa9..ab55172 100644
--- a/libbe/arch.py
+++ b/libbe/arch.py
@@ -17,6 +17,10 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+GNU Arch (tla) backend.
+"""
+
import codecs
import os
import re
@@ -26,10 +30,11 @@ import time
import unittest
import doctest
-import config
from beuuid import uuid_gen
-import rcs
-from rcs import RCS
+import config
+import vcs
+
+
DEFAULT_CLIENT = "tla"
@@ -38,7 +43,7 @@ client = config.get_val("arch_client", default=DEFAULT_CLIENT)
def new():
return Arch()
-class Arch(RCS):
+class Arch(vcs.VCS):
name = "Arch"
client = client
versioned = True
@@ -48,21 +53,25 @@ class Arch(RCS):
_project_name = None
_tmp_project = False
_arch_paramdir = os.path.expanduser("~/.arch-params")
- def _rcs_help(self):
+ def _vcs_help(self):
status,output,error = self._u_invoke_client("--help")
return output
- def _rcs_detect(self, path):
+ def _vcs_detect(self, path):
"""Detect whether a directory is revision-controlled using Arch"""
if self._u_search_parent_directories(path, "{arch}") != None :
config.set_val("arch_client", client)
return True
return False
- def _rcs_init(self, path):
+ def _vcs_init(self, path):
self._create_archive(path)
self._create_project(path)
self._add_project_code(path)
def _create_archive(self, path):
- # Create a new archive
+ """
+ Create a temporary Arch archive in the directory PATH. This
+ archive will be removed by
+ __del__->cleanup->_vcs_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()
@@ -103,7 +112,7 @@ class Arch(RCS):
"""
Create a temporary Arch project in the directory PATH. This
project will be removed by
- __del__->cleanup->_rcs_cleanup->_remove_project
+ __del__->cleanup->_vcs_cleanup->_remove_project
"""
# http://mwolson.org/projects/GettingStartedWithArch.html
# http://regexps.srparish.net/tutorial-tla/new-project.html#Starting_a_New_Project
@@ -159,13 +168,13 @@ class Arch(RCS):
self._adjust_naming_conventions(path)
self._invoke_client("import", "--summary", "Began versioning",
directory=path)
- def _rcs_cleanup(self):
+ def _vcs_cleanup(self):
if self._tmp_project == True:
self._remove_project()
if self._tmp_archive == True:
self._remove_archive()
- def _rcs_root(self, path):
+ def _vcs_root(self, path):
if not os.path.isdir(path):
dirname = os.path.dirname(path)
else:
@@ -176,7 +185,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')
@@ -188,7 +196,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)
@@ -197,7 +204,7 @@ class Arch(RCS):
archive_name,project_name = output.rstrip('\n').split('/')
self._archive_name = archive_name
self._project_name = project_name
- def _rcs_get_user_id(self):
+ def _vcs_get_user_id(self):
try:
status,output,error = self._u_invoke_client('my-id')
return output.rstrip('\n')
@@ -206,9 +213,9 @@ class Arch(RCS):
return None
else:
raise
- def _rcs_set_user_id(self, value):
+ def _vcs_set_user_id(self, value):
self._u_invoke_client('my-id', value)
- def _rcs_add(self, path):
+ def _vcs_add(self, path):
self._u_invoke_client("add-id", path)
realpath = os.path.realpath(self._u_abspath(path))
pathAdded = realpath in self._list_added(self.rootdir)
@@ -237,14 +244,14 @@ class Arch(RCS):
self._add_dir_rule(rule, os.path.dirname(path), self.rootdir)
if os.path.realpath(path) not in self._list_added(self.rootdir):
raise CantAddFile(path)
- def _rcs_remove(self, path):
+ def _vcs_remove(self, path):
if not '.arch-ids' in path:
self._u_invoke_client("delete-id", path)
- def _rcs_update(self, path):
+ def _vcs_update(self, path):
pass
- def _rcs_get_file_contents(self, path, revision=None, binary=False):
+ def _vcs_get_file_contents(self, path, revision=None, binary=False):
if revision == None:
- return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
+ return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary)
else:
status,output,error = \
self._invoke_client("file-find", path, revision)
@@ -254,18 +261,18 @@ class Arch(RCS):
contents = f.read()
f.close()
return contents
- def _rcs_duplicate_repo(self, directory, revision=None):
+ def _vcs_duplicate_repo(self, directory, revision=None):
if revision == None:
- RCS._rcs_duplicate_repo(self, directory, revision)
+ vcs.VCS._vcs_duplicate_repo(self, directory, revision)
else:
status,output,error = \
self._u_invoke_client("get", revision,directory)
- def _rcs_commit(self, commitfile, allow_empty=False):
+ def _vcs_commit(self, commitfile, allow_empty=False):
if allow_empty == False:
# arch applies empty commits without complaining, so check first
status,output,error = self._u_invoke_client("changes",expect=(0,1))
if status == 0:
- raise rcs.EmptyCommit()
+ raise vcs.EmptyCommit()
summary,body = self._u_parse_commitfile(commitfile)
args = ["commit", "--summary", summary]
if body != None:
@@ -281,6 +288,16 @@ class Arch(RCS):
assert revpath.startswith(self._archive_project_name()+'--')
revision = revpath[len(self._archive_project_name()+'--'):]
return revpath
+ def _vcs_revision_id(self, index):
+ status,output,error = self._u_invoke_client("logs")
+ logs = output.splitlines()
+ first_log = logs.pop(0)
+ assert first_log == "base-0", first_log
+ try:
+ log = logs[index]
+ except IndexError:
+ return None
+ return "%s--%s" % (self._archive_project_name(), log)
class CantAddFile(Exception):
def __init__(self, file):
@@ -289,7 +306,7 @@ class CantAddFile(Exception):
-rcs.make_rcs_testcase_subclasses(Arch, sys.modules[__name__])
+vcs.make_vcs_testcase_subclasses(Arch, sys.modules[__name__])
unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
diff --git a/libbe/beuuid.py b/libbe/beuuid.py
index bc47208..490ed62 100644
--- a/libbe/beuuid.py
+++ b/libbe/beuuid.py
@@ -13,6 +13,7 @@
# 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
@@ -20,6 +21,7 @@ on 2.4 ;), the uuid call should be merged into bugdir.py
import unittest
+
try:
from uuid import uuid4 # Python >= 2.5
def uuid_gen():
diff --git a/libbe/bug.py b/libbe/bug.py
index c1e5481..fd30ff7 100644
--- a/libbe/bug.py
+++ b/libbe/bug.py
@@ -15,6 +15,11 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Define the Bug class for representing bugs.
+"""
+
import os
import os.path
import errno
@@ -33,6 +38,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/
@@ -216,15 +226,15 @@ class Bug(settings_object.SavedSettingsObject):
@doc_property(doc="The trunk of the comment tree")
def comment_root(): return {}
- def _get_rcs(self):
- if hasattr(self.bugdir, "rcs"):
- return self.bugdir.rcs
+ def _get_vcs(self):
+ if hasattr(self.bugdir, "vcs"):
+ return self.bugdir.vcs
@Property
- @cached_property(generator=_get_rcs)
- @local_property("rcs")
+ @cached_property(generator=_get_vcs)
+ @local_property("vcs")
@doc_property(doc="A revision control system instance.")
- def rcs(): return {}
+ def vcs(): return {}
def __init__(self, bugdir=None, uuid=None, from_disk=False,
load_comments=False, summary=None):
@@ -238,17 +248,20 @@ class Bug(settings_object.SavedSettingsObject):
if uuid == None:
self.uuid = uuid_gen()
self.time = int(time.time()) # only save to second precision
- if self.rcs != None:
- self.creator = self.rcs.get_user_id()
+ if self.vcs != None:
+ self.creator = self.vcs.get_user_id()
self.summary = summary
def __repr__(self):
return "Bug(uuid=%r)" % self.uuid
- def set_sync_with_disk(self, value):
- self.sync_with_disk = value
- for comment in self.comments():
- comment.set_sync_with_disk(value)
+ 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)
@@ -331,43 +344,34 @@ class Bug(settings_object.SavedSettingsObject):
output = bugout
return output
- def __str__(self):
- return self.string(shortlist=True)
+ # methods for saving/loading/acessing settings and properties.
- def __cmp__(self, other):
- return cmp_full(self, other)
+ def get_path(self, *args):
+ dir = os.path.join(self.bugdir.get_path("bugs"), self.uuid)
+ if len(args) == 0:
+ return dir
+ assert args[0] in ["values", "comments"], str(args)
+ return os.path.join(dir, *args)
- def get_path(self, name=None):
- my_dir = os.path.join(self.bugdir.get_path("bugs"), self.uuid)
- if name is None:
- return my_dir
- assert name in ["values", "comments"]
- return os.path.join(my_dir, name)
+ def 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):
- self.settings = mapfile.map_load(self.rcs, self.get_path("values"))
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("load settings")
+ self.settings = mapfile.map_load(self.vcs, self.get_path("values"))
self._setup_saved_settings()
- def load_comments(self, load_full=True):
- if load_full == True:
- # Force a complete load of the whole comment tree
- self.comment_root = self._get_comment_root(load_full=True)
- else:
- # Setup for fresh lazy-loading. Clear _comment_root, so
- # _get_comment_root returns a fresh version. Turn of
- # syncing temporarily so we don't write our blank comment
- # tree to disk.
- self.sync_with_disk = False
- self.comment_root = None
- self.sync_with_disk = True
-
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())
+ self.vcs.mkdir(self.get_path())
path = self.get_path("values")
- mapfile.map_save(self.rcs, path, self._get_saved_settings())
-
+ mapfile.map_save(self.vcs, path, self._get_saved_settings())
+
def save(self):
"""
Save any loaded contents to disk. Because of lazy loading of
@@ -378,15 +382,39 @@ class Bug(settings_object.SavedSettingsObject):
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)
+ else:
+ # Setup for fresh lazy-loading. Clear _comment_root, so
+ # _get_comment_root returns a fresh version. Turn of
+ # syncing temporarily so we don't write our blank comment
+ # tree to disk.
+ self.sync_with_disk = False
+ self.comment_root = None
+ self.sync_with_disk = True
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)
+ self.vcs.recursive_remove(path)
+ # methods for managing comments
+
def comments(self):
for comment in self.comment_root.traverse():
yield comment
@@ -489,8 +517,12 @@ 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)
@@ -512,7 +544,8 @@ def cmp_comments(bug_1, bug_2):
return 0
DEFAULT_CMP_FULL_CMP_LIST = \
- (cmp_status,cmp_severity,cmp_assigned,cmp_time,cmp_creator,cmp_comments)
+ (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 6e020ee..c4f0f91 100644
--- a/libbe/bugdir.py
+++ b/libbe/bugdir.py
@@ -17,23 +17,30 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Define the BugDir class for representing bug comments.
+"""
+
+import copy
+import errno
import os
import os.path
-import errno
+import sys
import time
-import copy
import unittest
import doctest
+import bug
+import encoding
from properties import Property, doc_property, local_property, \
defaulting_property, checked_property, fn_checked_property, \
cached_property, primed_property, change_hook_property, \
settings_property
-import settings_object
import mapfile
-import bug
-import rcs
-import encoding
+import vcs
+import settings_object
+import upgrade
import utility
@@ -62,8 +69,16 @@ 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
-TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n"
+class DiskAccessRequired (Exception):
+ def __init__(self, goal):
+ msg = "Cannot %s without accessing the disk" % goal
+ Exception.__init__(self, msg)
class BugDir (list, settings_object.SavedSettingsObject):
@@ -99,7 +114,8 @@ class BugDir (list, settings_object.SavedSettingsObject):
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 memoryy, a call to save() is a safe move.
+ 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.
@@ -107,15 +123,14 @@ class BugDir (list, settings_object.SavedSettingsObject):
changes, this .save() call will be a waste of time.
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)).
+ loads new settings/bugs/comments that it doesn't already have in
+ memory and .sync_with_disk == True.
- Allow RCS initialization
+ Allow VCS 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
+ BugDir to search for an installed VCS backend and initialize it in
the root directory. This is a convenience option for supporting
tests of versioning functionality (e.g. .duplicate_bugdir).
@@ -172,9 +187,9 @@ class BugDir (list, settings_object.SavedSettingsObject):
def encoding(): return {}
def _setup_user_id(self, user_id):
- self.rcs.user_id = user_id
+ self.vcs.user_id = user_id
def _guess_user_id(self):
- return self.rcs.get_user_id()
+ return self.vcs.get_user_id()
def _set_user_id(self, old_user_id, new_user_id):
self._setup_user_id(new_user_id)
self._prop_save_settings(old_user_id, new_user_id)
@@ -182,7 +197,7 @@ class BugDir (list, settings_object.SavedSettingsObject):
@_versioned_property(name="user_id",
doc=
"""The user's prefered name, e.g. 'John Doe <jdoe@example.com>'. Note
-that the Arch RCS backend *enforces* ids with this format.""",
+that the Arch VCS backend *enforces* ids with this format.""",
change_hook=_set_user_id,
generator=_guess_user_id)
def user_id(): return {}
@@ -192,32 +207,32 @@ that the Arch RCS backend *enforces* ids with this format.""",
"""The default assignee for new bugs e.g. 'John Doe <jdoe@example.com>'.""")
def default_assignee(): return {}
- @_versioned_property(name="rcs_name",
- doc="""The name of the current RCS. Kept seperate to make saving/loading
-settings easy. Don't set this attribute. Set .rcs instead, and
-.rcs_name will be automatically adjusted.""",
+ @_versioned_property(name="vcs_name",
+ doc="""The name of the current VCS. Kept seperate to make saving/loading
+settings easy. Don't set this attribute. Set .vcs instead, and
+.vcs_name will be automatically adjusted.""",
default="None",
allowed=["None", "Arch", "bzr", "darcs", "git", "hg"])
- def rcs_name(): return {}
+ def vcs_name(): return {}
- def _get_rcs(self, rcs_name=None):
+ def _get_vcs(self, vcs_name=None):
"""Get and root a new revision control system"""
- if rcs_name == None:
- rcs_name = self.rcs_name
- new_rcs = rcs.rcs_by_name(rcs_name)
- self._change_rcs(None, new_rcs)
- return new_rcs
- def _change_rcs(self, old_rcs, new_rcs):
- new_rcs.encoding = self.encoding
- new_rcs.root(self.root)
- self.rcs_name = new_rcs.name
+ if vcs_name == None:
+ vcs_name = self.vcs_name
+ new_vcs = vcs.vcs_by_name(vcs_name)
+ self._change_vcs(None, new_vcs)
+ return new_vcs
+ def _change_vcs(self, old_vcs, new_vcs):
+ new_vcs.encoding = self.encoding
+ new_vcs.root(self.root)
+ self.vcs_name = new_vcs.name
@Property
- @change_hook_property(hook=_change_rcs)
- @cached_property(generator=_get_rcs)
- @local_property("rcs")
+ @change_hook_property(hook=_change_vcs)
+ @cached_property(generator=_get_vcs)
+ @local_property("vcs")
@doc_property(doc="A revision control system instance.")
- def rcs(): return {}
+ def vcs(): return {}
def _bug_map_gen(self):
map = {}
@@ -279,9 +294,8 @@ 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):
+ assert_new_BugDir=False, allow_vcs_init=False,
+ manipulate_encodings=True, from_disk=False, vcs=None):
list.__init__(self)
settings_object.SavedSettingsObject.__init__(self)
self._manipulate_encodings = manipulate_encodings
@@ -293,9 +307,9 @@ settings easy. Don't set this attribute. Set .rcs instead, and
if not os.path.exists(root):
raise NoRootEntry(root)
self.root = root
- # get a temporary rcs until we've loaded settings
+ # get a temporary vcs until we've loaded settings
self.sync_with_disk = False
- self.rcs = self._guess_rcs()
+ self.vcs = self._guess_vcs()
if from_disk == True:
self.sync_with_disk = True
@@ -305,20 +319,24 @@ settings easy. Don't set this attribute. Set .rcs instead, and
if assert_new_BugDir == True:
if os.path.exists(self.get_path()):
raise AlreadyInitialized, self.get_path()
- if rcs == None:
- rcs = self._guess_rcs(allow_rcs_init)
- self.rcs = rcs
+ if vcs == None:
+ vcs = self._guess_vcs(allow_vcs_init)
+ self.vcs = vcs
self._setup_user_id(self.user_id)
- def set_sync_with_disk(self, value):
- self.sync_with_disk = value
- for bug in self:
- bug.set_sync_with_disk(value)
+ def __del__(self):
+ self.cleanup()
+
+ def cleanup(self):
+ self.vcs.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)
@@ -334,136 +352,212 @@ settings easy. Don't set this attribute. Set .rcs instead, and
raise NoBugDir(path)
return beroot
- def get_version(self, path=None, use_none_rcs=False):
- if use_none_rcs == True:
- RCS = rcs.rcs_by_name("None")
- RCS.root(self.root)
- RCS.encoding = encoding.get_encoding()
+ def _guess_vcs(self, allow_vcs_init=False):
+ """
+ Only called by __init__.
+ """
+ deepdir = self.get_path()
+ if not os.path.exists(deepdir):
+ deepdir = os.path.dirname(deepdir)
+ new_vcs = vcs.detect_vcs(deepdir)
+ install = False
+ if new_vcs.name == "None":
+ if allow_vcs_init == True:
+ new_vcs = vcs.installed_vcs()
+ new_vcs.init(self.root)
+ return new_vcs
+
+ # methods for saving/loading/accessing settings and properties.
+
+ def get_path(self, *args):
+ """
+ Return a path relative to .root.
+ """
+ dir = os.path.join(self.root, ".be")
+ if len(args) == 0:
+ return dir
+ assert args[0] in ["version", "settings", "bugs"], str(args)
+ return os.path.join(dir, *args)
+
+ def _get_settings(self, settings_path, for_duplicate_bugdir=False):
+ allow_no_vcs = not self.vcs.path_in_root(settings_path)
+ if allow_no_vcs == 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.vcs, settings_path, allow_no_vcs)
+ except vcs.NoSuchFile:
+ settings = {"vcs_name": "None"}
+ return settings
+
+ def _save_settings(self, settings_path, settings,
+ for_duplicate_bugdir=False):
+ allow_no_vcs = not self.vcs.path_in_root(settings_path)
+ if allow_no_vcs == 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.vcs.mkdir(self.get_path(), allow_no_vcs)
+ mapfile.map_save(self.vcs, settings_path, settings, allow_no_vcs)
+
+ 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.vcs = vcs.vcs_by_name(self.vcs_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_vcs=False,
+ for_duplicate_bugdir=False):
+ """
+ Requires disk access.
+ """
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("get version")
+ if use_none_vcs == True:
+ VCS = vcs.vcs_by_name("None")
+ VCS.root(self.root)
+ VCS.encoding = encoding.get_encoding()
else:
- RCS = self.rcs
+ VCS = self.vcs
if path == None:
path = self.get_path("version")
- tree_version = RCS.get_file_contents(path)
- return tree_version
+ allow_no_vcs = not VCS.path_in_root(path)
+ if allow_no_vcs == True:
+ assert for_duplicate_bugdir == True
+ version = VCS.get_file_contents(
+ path, allow_no_vcs=allow_no_vcs).rstrip("\n")
+ return version
def set_version(self):
- self.rcs.mkdir(self.get_path())
- self.rcs.set_file_contents(self.get_path("version"),
- TREE_VERSION_STRING)
+ """
+ Requires disk access.
+ """
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("set version")
+ self.vcs.mkdir(self.get_path())
+ self.vcs.set_file_contents(self.get_path("version"),
+ upgrade.BUGDIR_DISK_VERSION+"\n")
- 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):
- version = self.get_version(use_none_rcs=True)
- if version != TREE_VERSION_STRING:
- raise NotImplementedError, \
- "BugDir cannot handle version '%s' yet." % version
+ """
+ Reqires disk access
+ """
+ version = self.get_version(use_none_vcs=True)
+ if version != upgrade.BUGDIR_DISK_VERSION:
+ upgrade.upgrade(self.root, version)
else:
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):
"""
+ 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 self.sync_with_disk = True, then any changes are
+ 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()
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)
+ # methods for managing duplicate BugDirs
- 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
+ def duplicate_bugdir(self, revision):
+ duplicate_path = self.vcs.duplicate_repo(revision)
+ duplicate_version_path = os.path.join(duplicate_path, ".be", "version")
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
- self.rcs.mkdir(self.get_path(), allow_no_rcs)
- mapfile.map_save(self.rcs, settings_path, settings, allow_no_rcs)
-
- def duplicate_bugdir(self, revision):
- duplicate_path = self.rcs.duplicate_repo(revision)
+ version = self.get_version(duplicate_version_path,
+ for_duplicate_bugdir=True)
+ except DiskAccessRequired:
+ self.sync_with_disk = True # temporarily allow access
+ version = self.get_version(duplicate_version_path,
+ for_duplicate_bugdir=True)
+ self.sync_with_disk = False
+ if version != upgrade.BUGDIR_DISK_VERSION:
+ upgrade.upgrade(duplicate_path, version)
- # setup revision RCS as None, since the duplicate may not be
+ # setup revision VCS as None, since the duplicate may not be
# initialized for versioning
duplicate_settings_path = os.path.join(duplicate_path,
".be", "settings")
- duplicate_settings = self._get_settings(duplicate_settings_path)
- if "rcs_name" in duplicate_settings:
- duplicate_settings["rcs_name"] = "None"
+ duplicate_settings = self._get_settings(duplicate_settings_path,
+ for_duplicate_bugdir=True)
+ if "vcs_name" in duplicate_settings:
+ duplicate_settings["vcs_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()
+ self.vcs.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('.')):
- uuids.append(uuid)
- yield uuid
+ if os.path.exists(self.get_path("bugs")):
+ for uuid in os.listdir(self.get_path("bugs")):
+ if not (uuid.startswith('.')):
+ uuids.append(uuid)
+ yield uuid
# and the ones that are still just in memory
for bug in self:
if bug.uuid not in uuids:
@@ -476,6 +570,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()
@@ -492,7 +588,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and
def remove_bug(self, bug):
self.remove(bug)
- bug.remove()
+ if bug.sync_with_disk == True:
+ bug.remove()
def bug_shortname(self, bug):
"""
@@ -514,12 +611,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()
@@ -530,7 +628,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):
@@ -548,41 +646,56 @@ settings easy. Don't set this attribute. Set .rcs instead, and
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
+ vcs_init = True
+ else:
+ root = "/"
+ assert_new_BugDir = False
+ vcs_init = False
+ BugDir.__init__(self, root, sink_to_existing_root=False,
+ assert_new_BugDir=assert_new_BugDir,
+ allow_vcs_init=vcs_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
+ allow_vcs_init=True)
+ self.vcs = self.bugdir.vcs
def tearDown(self):
- self.rcs.cleanup()
+ self.bugdir.cleanup()
self.dir.cleanup()
def fullPath(self, path):
return os.path.join(self.dir.path, path)
@@ -593,13 +706,13 @@ class BugDirTestCase(unittest.TestCase):
self.assertRaises(AlreadyInitialized, BugDir,
self.dir.path, assertNewBugDir=True)
def versionTest(self):
- if self.rcs.versioned == False:
+ if self.vcs.versioned == False:
return
- original = self.bugdir.rcs.commit("Began versioning")
+ original = self.bugdir.vcs.commit("Began versioning")
bugA = self.bugdir.bug_from_uuid("a")
bugA.status = "fixed"
self.bugdir.save()
- new = self.rcs.commit("Fixed bug a")
+ new = self.vcs.commit("Fixed bug a")
dupdir = self.bugdir.duplicate_bugdir(original)
self.failUnless(dupdir.root != self.bugdir.root,
"%s, %s" % (dupdir.root, self.bugdir.root))
@@ -645,17 +758,19 @@ class BugDirTestCase(unittest.TestCase):
rep.new_reply("And they have six legs.")
if sync_with_disk == False:
self.bugdir.save()
+ self.bugdir.set_sync_with_disk(True)
self.bugdir._clear_bugs()
bug = self.bugdir.bug_from_uuid("a")
bug.load_comments()
+ if sync_with_disk == False:
+ self.bugdir.set_sync_with_disk(False)
self.failUnless(len(bug.comment_root)==1, len(bug.comment_root))
for index,comment in enumerate(bug.comments()):
if index == 0:
repLoaded = comment
self.failUnless(repLoaded.uuid == rep.uuid, repLoaded.uuid)
- self.failUnless(comment.sync_with_disk == 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",
@@ -672,5 +787,46 @@ class BugDirTestCase(unittest.TestCase):
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_vcs_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 d7cd1e5..e9e0649 100644
--- a/libbe/bzr.py
+++ b/libbe/bzr.py
@@ -17,61 +17,65 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Bazaar (bzr) backend.
+"""
+
import os
import re
import sys
import unittest
import doctest
-import rcs
-from rcs import RCS
+import vcs
+
def new():
return Bzr()
-class Bzr(RCS):
+class Bzr(vcs.VCS):
name = "bzr"
client = "bzr"
versioned = True
- def _rcs_help(self):
+ def _vcs_help(self):
status,output,error = self._u_invoke_client("--help")
return output
- def _rcs_detect(self, path):
+ def _vcs_detect(self, path):
if self._u_search_parent_directories(path, ".bzr") != None :
return True
return False
- def _rcs_root(self, path):
+ def _vcs_root(self, path):
"""Find the root of the deepest repository containing path."""
status,output,error = self._u_invoke_client("root", path)
return output.rstrip('\n')
- def _rcs_init(self, path):
+ def _vcs_init(self, path):
self._u_invoke_client("init", directory=path)
- def _rcs_get_user_id(self):
+ def _vcs_get_user_id(self):
status,output,error = self._u_invoke_client("whoami")
return output.rstrip('\n')
- def _rcs_set_user_id(self, value):
+ def _vcs_set_user_id(self, value):
self._u_invoke_client("whoami", value)
- def _rcs_add(self, path):
+ def _vcs_add(self, path):
self._u_invoke_client("add", path)
- def _rcs_remove(self, path):
+ def _vcs_remove(self, path):
# --force to also remove unversioned files.
self._u_invoke_client("remove", "--force", path)
- def _rcs_update(self, path):
+ def _vcs_update(self, path):
pass
- def _rcs_get_file_contents(self, path, revision=None, binary=False):
+ def _vcs_get_file_contents(self, path, revision=None, binary=False):
if revision == None:
- return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
+ return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary)
else:
status,output,error = \
self._u_invoke_client("cat","-r",revision,path)
return output
- def _rcs_duplicate_repo(self, directory, revision=None):
+ def _vcs_duplicate_repo(self, directory, revision=None):
if revision == None:
- RCS._rcs_duplicate_repo(self, directory, revision)
+ vcs.VCS._vcs_duplicate_repo(self, directory, revision)
else:
self._u_invoke_client("branch", "--revision", revision,
".", directory)
- def _rcs_commit(self, commitfile, allow_empty=False):
+ def _vcs_commit(self, commitfile, allow_empty=False):
args = ["commit", "--file", commitfile]
if allow_empty == True:
args.append("--unchanged")
@@ -83,9 +87,9 @@ class Bzr(RCS):
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()
+ raise vcs.EmptyCommit()
else:
- raise rcs.CommandError(args, status, error)
+ raise vcs.CommandError(args, status, stdout="", stderr=error)
revision = None
revline = re.compile("Committed revision (.*)[.]")
match = revline.search(error)
@@ -93,9 +97,17 @@ class Bzr(RCS):
assert len(match.groups()) == 1
revision = match.groups()[0]
return revision
+ def _vcs_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__])
+vcs.make_vcs_testcase_subclasses(Bzr, sys.modules[__name__])
unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py
index 853a75a..9b64142 100644
--- a/libbe/cmdutil.py
+++ b/libbe/cmdutil.py
@@ -15,6 +15,11 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Define assorted utilities to make command-line handling easier.
+"""
+
import glob
import optparse
import os
@@ -70,10 +75,11 @@ def get_command(command_name):
return cmd
-def execute(cmd, args):
+def execute(cmd, args, manipulate_encodings=True):
enc = encoding.get_encoding()
cmd = get_command(cmd)
- ret = cmd.execute([a.decode(enc) for a in args])
+ ret = cmd.execute([a.decode(enc) for a in args],
+ manipulate_encodings=manipulate_encodings)
if ret == None:
ret = 0
return ret
@@ -206,6 +212,15 @@ def underlined(instring):
return "%s\n%s" % (instring, "="*len(instring))
+def bug_from_shortname(bdir, shortname):
+ """
+ Exception translation for the command-line interface.
+ """
+ try:
+ bug = bdir.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 3249e8b..41bc7e6 100644
--- a/libbe/comment.py
+++ b/libbe/comment.py
@@ -16,6 +16,11 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Define the Comment class for representing bug comments.
+"""
+
import base64
import os
import os.path
@@ -61,6 +66,11 @@ class MissingReference(ValueError):
self.reference = comment.in_reply_to
self.comment = comment
+class DiskAccessRequired (Exception):
+ def __init__(self, goal):
+ msg = "Cannot %s without accessing the disk" % goal
+ Exception.__init__(self, msg)
+
INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!"
def list_to_root(comments, bug, root=None,
@@ -115,8 +125,10 @@ 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):
+ if not os.path.exists(path):
return Comment(bug, uuid=INVALID_UUID)
comments = []
for uuid in os.listdir(path):
@@ -131,6 +143,8 @@ def loadComments(bug, load_full=False):
return list_to_root(comments, bug)
def saveComments(bug):
+ if bug.sync_with_disk == False:
+ raise DiskAccessRequired("save comments")
for comment in bug.comment_root.traverse():
comment.save()
@@ -162,9 +176,9 @@ class Comment(Tree, settings_object.SavedSettingsObject):
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="From",
+ @_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")
@@ -178,28 +192,28 @@ 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:
- import rcs
+ if self.vcs != None and self.sync_with_disk == True:
+ import vcs
binary = not self.content_type.startswith("text/")
- return self.rcs.get_file_contents(self.get_path("body"), binary=binary)
+ return self.vcs.get_file_contents(self.get_path("body"), binary=binary)
def _set_comment_body(self, old=None, new=None, force=False):
- if (self.rcs != None and self.sync_with_disk == True) or force==True:
+ if (self.vcs != None and self.sync_with_disk == True) or force==True:
assert new != None, "Can't save empty comment"
binary = not self.content_type.startswith("text/")
- self.rcs.set_file_contents(self.get_path("body"), new, binary=binary)
+ self.vcs.set_file_contents(self.get_path("body"), new, binary=binary)
@Property
@change_hook_property(hook=_set_comment_body)
@@ -208,15 +222,15 @@ class Comment(Tree, settings_object.SavedSettingsObject):
@doc_property(doc="The meat of the comment")
def body(): return {}
- def _get_rcs(self):
- if hasattr(self.bug, "rcs"):
- return self.bug.rcs
+ def _get_vcs(self):
+ if hasattr(self.bug, "vcs"):
+ return self.bug.vcs
@Property
- @cached_property(generator=_get_rcs)
- @local_property("rcs")
+ @cached_property(generator=_get_vcs)
+ @local_property("vcs")
@doc_property(doc="A revision control system instance.")
- def rcs(): return {}
+ def vcs(): return {}
def _extra_strings_check_fn(value):
return utility.iterable_full_of_strings(value, \
@@ -257,13 +271,29 @@ class Comment(Tree, settings_object.SavedSettingsObject):
if uuid == None:
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()
+ if self.vcs != None:
+ self.author = self.vcs.get_user_id()
self.in_reply_to = in_reply_to
self.body = body
- def set_sync_with_disk(self, value):
- self.sync_with_disk = True
+ 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"""
@@ -272,6 +302,8 @@ class Comment(Tree, settings_object.SavedSettingsObject):
continue
yield comment
+ # serializing methods
+
def _setting_attr_string(self, setting):
value = getattr(self, setting)
if value == None:
@@ -282,12 +314,12 @@ class Comment(Tree, settings_object.SavedSettingsObject):
"""
>>> 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
@@ -309,8 +341,8 @@ class Comment(Tree, settings_object.SavedSettingsObject):
("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>"]
@@ -328,11 +360,11 @@ class Comment(Tree, settings_object.SavedSettingsObject):
<alt-id> fields.
>>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
>>> commA.uuid = "0123"
- >>> commA.time_string = "Thu, 01 Jan 1970 00:00:00 +0000"
+ >>> 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','From','time_string','content_type','body']
+ >>> 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'"
@@ -342,15 +374,15 @@ class Comment(Tree, settings_object.SavedSettingsObject):
Mismatch on alt_id: '0123' should be 'None'
>>> print commB.alt_id
0123
- >>> commA.From
- >>> commB.From
+ >>> 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','from','date','content-type','body']
+ tags=['uuid','alt-id','in-reply-to','author','date','content-type','body']
uuid = None
body = None
for child in comment.getchildren():
@@ -368,10 +400,6 @@ class Comment(Tree, settings_object.SavedSettingsObject):
if child.tag == "body":
body = text
continue # don't set the bug's body yet.
- elif child.tag == 'from':
- attr_name = "From"
- elif child.tag == 'date':
- attr_name = 'time_string'
else:
attr_name = child.tag.replace('-','_')
setattr(self, attr_name, text)
@@ -389,7 +417,7 @@ class Comment(Tree, settings_object.SavedSettingsObject):
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
@@ -405,8 +433,8 @@ 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("")
if self.content_type.startswith("text/"):
lines.extend((self.body or "").splitlines())
@@ -417,78 +445,6 @@ class Comment(Tree, settings_object.SavedSettingsObject):
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):
- 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).
- """
- assert self.body != None, "Can't save blank comment"
- 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)
- 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 string_thread(self, string_method_name="string", name_map={},
indent=0, flatten=True,
auto_name_map=False, bug_shortname=None):
@@ -506,7 +462,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")
@@ -593,6 +549,77 @@ 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, *args):
+ dir = os.path.join(self.bug.get_path("comments"), self.uuid)
+ if len(args) == 0:
+ return dir
+ assert args[0] in ["values", "body"], str(args)
+ return os.path.join(dir, *args)
+
+ 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.vcs, self.get_path("values"))
+ self._setup_saved_settings()
+
+ def save_settings(self):
+ if self.sync_with_disk == False:
+ raise DiskAccessRequired("save settings")
+ self.vcs.mkdir(self.get_path())
+ path = self.get_path("values")
+ mapfile.map_save(self.vcs, 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.vcs.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.
@@ -659,4 +686,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 5e343b9..fb5a028 100644
--- a/libbe/config.py
+++ b/libbe/config.py
@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Create, save, and load the per-user config file at path().
+"""
+
import ConfigParser
import codecs
import locale
@@ -21,6 +26,7 @@ import os.path
import sys
import doctest
+
default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding()
def path():
diff --git a/libbe/darcs.py b/libbe/darcs.py
index e7132c0..16005f2 100644
--- a/libbe/darcs.py
+++ b/libbe/darcs.py
@@ -14,31 +14,40 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Darcs backend.
+"""
+
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 vcs
-import rcs
-from rcs import RCS
def new():
return Darcs()
-class Darcs(RCS):
+class Darcs(vcs.VCS):
name="darcs"
client="darcs"
versioned=True
- def _rcs_help(self):
+ def _vcs_help(self):
status,output,error = self._u_invoke_client("--help")
return output
- def _rcs_detect(self, path):
+ def _vcs_detect(self, path):
if self._u_search_parent_directories(path, "_darcs") != None :
return True
return False
- def _rcs_root(self, path):
+ def _vcs_root(self, path):
"""Find the root of the deepest repository containing path."""
# Assume that nothing funny is going on; in particular, that we aren't
# dealing with a bare repo.
@@ -48,9 +57,9 @@ class Darcs(RCS):
if darcs_dir == None:
return None
return os.path.dirname(darcs_dir)
- def _rcs_init(self, path):
+ def _vcs_init(self, path):
self._u_invoke_client("init", directory=path)
- def _rcs_get_user_id(self):
+ def _vcs_get_user_id(self):
# following http://darcs.net/manual/node4.html#SECTION00410030000000000000
# as of June 29th, 2009
if self.rootdir == None:
@@ -65,32 +74,32 @@ class Darcs(RCS):
if env_variable in os.environ:
return os.environ[env_variable]
return None
- def _rcs_set_user_id(self, value):
+ def _vcs_set_user_id(self, value):
if self.rootdir == None:
self.root(".")
if self.rootdir == None:
- raise rcs.SettingIDnotSupported
+ raise vcs.SettingIDnotSupported
author_path = os.path.join(self.rootdir, "_darcs", "prefs", "author")
f = codecs.open(author_path, "w", self.encoding)
f.write(value)
f.close()
- def _rcs_add(self, path):
+ def _vcs_add(self, path):
if os.path.isdir(path):
return
self._u_invoke_client("add", path)
- def _rcs_remove(self, path):
+ def _vcs_remove(self, path):
if not os.path.isdir(self._u_abspath(path)):
os.remove(os.path.join(self.rootdir, path)) # darcs notices removal
- def _rcs_update(self, path):
+ def _vcs_update(self, path):
pass # darcs notices changes
- def _rcs_get_file_contents(self, path, revision=None, binary=False):
+ def _vcs_get_file_contents(self, path, revision=None, binary=False):
if revision == None:
- return RCS._rcs_get_file_contents(self, path, revision,
+ return vcs.VCS._vcs_get_file_contents(self, path, revision,
binary=binary)
else:
try:
return self._u_invoke_client("show", "contents", "--patch", revision, path)
- except rcs.CommandError:
+ except vcs.CommandError:
# Darcs versions < 2.0.0pre2 lack the "show contents" command
status,output,error = self._u_invoke_client("diff", "--unified",
@@ -113,7 +122,7 @@ class Darcs(RCS):
status,output,error = self._u_invoke(args, stdin=target_patch)
if os.path.exists(os.path.join(self.rootdir, path)) == True:
- contents = RCS._rcs_get_file_contents(self, path,
+ contents = vcs.VCS._vcs_get_file_contents(self, path,
binary=binary)
else:
contents = ""
@@ -123,41 +132,53 @@ class Darcs(RCS):
status,output,error = self._u_invoke(args, stdin=target_patch)
args=["patch", path]
status,output,error = self._u_invoke(args, stdin=major_patch)
- current_contents = RCS._rcs_get_file_contents(self, path,
+ current_contents = vcs.VCS._vcs_get_file_contents(self, path,
binary=binary)
return contents
- def _rcs_duplicate_repo(self, directory, revision=None):
+ def _vcs_duplicate_repo(self, directory, revision=None):
if revision==None:
- RCS._rcs_duplicate_repo(self, directory, revision)
+ vcs.VCS._vcs_duplicate_repo(self, directory, revision)
else:
self._u_invoke_client("put", "--to-patch", revision, directory)
- def _rcs_commit(self, commitfile, allow_empty=False):
+ def _vcs_commit(self, commitfile, allow_empty=False):
id = self.get_user_id()
if '@' not in id:
id = "%s <%s@invalid.com>" % (id, id)
args = ['record', '--all', '--author', id, '--logfile', commitfile]
status,output,error = self._u_invoke_client(*args)
empty_strings = ["No changes!"]
- revision = None
if self._u_any_in_string(empty_strings, output) == True:
if allow_empty == False:
- raise rcs.EmptyCommit()
- else: # we need a extra call to get the current revision
- args = ["changes", "--last=1", "--xml"]
- status,output,error = self._u_invoke_client(*args)
- revline = re.compile("[ \t]*<name>(.*)</name>")
- # note that darcs does _not_ make an empty revision.
- # this returns the last non-empty revision id...
+ raise vcs.EmptyCommit()
+ # note that darcs does _not_ make an empty revision.
+ # this returns the last non-empty revision id...
+ revision = self._vcs_revision_id(-1)
else:
revline = re.compile("Finished recording patch '(.*)'")
- match = revline.search(output)
- assert match != None, output+error
- assert len(match.groups()) == 1
- revision = match.groups()[0]
+ match = revline.search(output)
+ assert match != None, output+error
+ assert len(match.groups()) == 1
+ revision = match.groups()[0]
return revision
-
+ def _vcs_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__])
+vcs.make_vcs_testcase_subclasses(Darcs, sys.modules[__name__])
unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
diff --git a/libbe/diff.py b/libbe/diff.py
index ba48efc..9253a23 100644
--- a/libbe/diff.py
+++ b/libbe/diff.py
@@ -14,112 +14,406 @@
# 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.utility import time_to_str
+
+"""Compare two bug trees."""
+
+import difflib
import doctest
-def bug_diffs(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)
- old_bug.load_comments()
- new_bug.load_comments()
- 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)
+from libbe import bugdir, bug, settings_object, tree
+from libbe.utility import time_to_str
-def diff_report(bug_diffs_data, old_bugdir, new_bugdir):
- bugs_removed,bugs_modified,bugs_added = bug_diffs_data
- def modified_cmp(left, right):
- return bug.cmp_severity(left[1], right[1])
- bugs_added.sort(bug.cmp_severity)
- bugs_removed.sort(bug.cmp_severity)
- bugs_modified.sort(modified_cmp)
- lines = []
-
- if old_bugdir.settings != new_bugdir.settings:
- bugdir_settings = sorted(new_bugdir.settings_properties)
- bugdir_settings.remove("rcs_name") # tweaked by bugdir.duplicate_bugdir
- change_list = change_lines(old_bugdir, new_bugdir, bugdir_settings)
- if len(change_list) > 0:
- lines.append("Modified bug directory:")
- change_strings = ["%s: %s -> %s" % f for f in change_list]
- lines.extend(change_strings)
- lines.append("")
- if len(bugs_added) > 0:
- lines.append("New bug reports:")
- for bg in bugs_added:
- lines.extend(bg.string(shortlist=True).splitlines())
- lines.append("")
- if len(bugs_modified) > 0:
- printed = False
- for old_bug, new_bug in bugs_modified:
- change_str = bug_changes(old_bug, new_bug)
- 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(bugs_removed) > 0:
- lines.append("Removed bug reports:")
- for bg in bugs_removed:
- lines.extend(bg.string(shortlist=True).splitlines())
- lines.append("")
-
- return "\n".join(lines).rstrip("\n")
+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 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:
+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
+
+ # data assembly methods
+
+ 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,
+ ["vcs_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):
- bug_settings = sorted(new.settings_properties)
- change_list = change_lines(old, new, bug_settings)
- change_strings = ["%s: %s -> %s" % f for f in change_list]
+ # report generation methods
- old_comment_ids = [c.uuid for c in old.comments()]
- new_comment_ids = [c.uuid for c in new.comments()]
- 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("vcs_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 93144b8..ec41006 100644
--- a/libbe/editor.py
+++ b/libbe/editor.py
@@ -15,6 +15,11 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Define editor_string(), a function that invokes an editor to accept
+user-produced text as a string.
+"""
+
import codecs
import locale
import os
@@ -22,6 +27,7 @@ import sys
import tempfile
import doctest
+
default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding()
comment_marker = u"== Anything below this line will be ignored\n"
@@ -62,7 +68,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 d603602..fd513b5 100644
--- a/libbe/encoding.py
+++ b/libbe/encoding.py
@@ -14,16 +14,26 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Support input/output/filesystem encodings (e.g. UTF-8).
+"""
+
import codecs
import locale
import sys
import 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 2f9ffa9..3abe3b8 100644
--- a/libbe/git.py
+++ b/libbe/git.py
@@ -16,30 +16,34 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Git backend.
+"""
+
import os
import re
import sys
import unittest
import doctest
-import rcs
-from rcs import RCS
+import vcs
+
def new():
return Git()
-class Git(RCS):
+class Git(vcs.VCS):
name="git"
client="git"
versioned=True
- def _rcs_help(self):
+ def _vcs_help(self):
status,output,error = self._u_invoke_client("--help")
return output
- def _rcs_detect(self, path):
+ def _vcs_detect(self, path):
if self._u_search_parent_directories(path, ".git") != None :
return True
return False
- def _rcs_root(self, path):
+ def _vcs_root(self, path):
"""Find the root of the deepest repository containing path."""
# Assume that nothing funny is going on; in particular, that we aren't
# dealing with a bare repo.
@@ -50,13 +54,21 @@ class Git(RCS):
gitdir = os.path.join(path, output.rstrip('\n'))
dirname = os.path.abspath(os.path.dirname(gitdir))
return dirname
- def _rcs_init(self, path):
+ def _vcs_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')
+ def _vcs_get_user_id(self):
+ status,output,error = \
+ self._u_invoke_client("config", "user.name", expect=(0,1))
+ if status == 0:
+ name = output.rstrip('\n')
+ else:
+ name = ""
+ status,output,error = \
+ self._u_invoke_client("config", "user.email", expect=(0,1))
+ if status == 0:
+ email = output.rstrip('\n')
+ else:
+ email = ""
if name != "" or email != "": # got something!
# guess missing info, if necessary
if name == "":
@@ -65,35 +77,35 @@ class Git(RCS):
email = self._u_get_fallback_email()
return self._u_create_id(name, email)
return None # Git has no infomation
- def _rcs_set_user_id(self, value):
+ def _vcs_set_user_id(self, value):
name,email = self._u_parse_id(value)
if email != None:
self._u_invoke_client("config", "user.email", email)
self._u_invoke_client("config", "user.name", name)
- def _rcs_add(self, path):
+ def _vcs_add(self, path):
if os.path.isdir(path):
return
self._u_invoke_client("add", path)
- def _rcs_remove(self, path):
+ def _vcs_remove(self, path):
if not os.path.isdir(self._u_abspath(path)):
self._u_invoke_client("rm", "-f", path)
- def _rcs_update(self, path):
- self._rcs_add(path)
- def _rcs_get_file_contents(self, path, revision=None, binary=False):
+ def _vcs_update(self, path):
+ self._vcs_add(path)
+ def _vcs_get_file_contents(self, path, revision=None, binary=False):
if revision == None:
- return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
+ return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary)
else:
arg = "%s:%s" % (revision,path)
status,output,error = self._u_invoke_client("show", arg)
return output
- def _rcs_duplicate_repo(self, directory, revision=None):
+ def _vcs_duplicate_repo(self, directory, revision=None):
if revision==None:
- RCS._rcs_duplicate_repo(self, directory, revision)
+ vcs.VCS._vcs_duplicate_repo(self, directory, revision)
else:
#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, allow_empty=False):
+ def _vcs_commit(self, commitfile, allow_empty=False):
args = ['commit', '--all', '--file', commitfile]
if allow_empty == True:
args.append("--allow-empty")
@@ -104,17 +116,33 @@ class Git(RCS):
strings = ["nothing to commit",
"nothing added to commit"]
if self._u_any_in_string(strings, output) == True:
- raise rcs.EmptyCommit()
+ raise vcs.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._vcs_revision_id(-1)
+ assert full_revision.startswith(revision), \
+ "Mismatched revisions:\n%s\n%s" % (revision, full_revision)
+ return full_revision
+ def _vcs_revision_id(self, index):
+ args = ["rev-list", "--first-parent", "--reverse", "HEAD"]
+ kwargs = {"expect":(0,128)}
+ status,output,error = self._u_invoke_client(*args, **kwargs)
+ if status == 128:
+ if error.startswith("fatal: ambiguous argument 'HEAD': unknown "):
+ return None
+ raise vcs.CommandError(args, status, stdout="", stderr=error)
+ commits = output.splitlines()
+ try:
+ return commits[index]
+ except IndexError:
+ return None
-rcs.make_rcs_testcase_subclasses(Git, sys.modules[__name__])
+vcs.make_vcs_testcase_subclasses(Git, sys.modules[__name__])
unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
diff --git a/libbe/hg.py b/libbe/hg.py
index a20eeb5..f8f8121 100644
--- a/libbe/hg.py
+++ b/libbe/hg.py
@@ -16,81 +16,88 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Mercurial (hg) backend.
+"""
+
import os
import re
import sys
import unittest
import doctest
-import rcs
-from rcs import RCS
+import vcs
+
def new():
return Hg()
-class Hg(RCS):
+class Hg(vcs.VCS):
name="hg"
client="hg"
versioned=True
- def _rcs_help(self):
+ def _vcs_help(self):
status,output,error = self._u_invoke_client("--help")
return output
- def _rcs_detect(self, path):
+ def _vcs_detect(self, path):
"""Detect whether a directory is revision-controlled using Mercurial"""
if self._u_search_parent_directories(path, ".hg") != None:
return True
return False
- def _rcs_root(self, path):
+ def _vcs_root(self, path):
status,output,error = self._u_invoke_client("root", directory=path)
return output.rstrip('\n')
- def _rcs_init(self, path):
+ def _vcs_init(self, path):
self._u_invoke_client("init", directory=path)
- def _rcs_get_user_id(self):
+ def _vcs_get_user_id(self):
status,output,error = self._u_invoke_client("showconfig","ui.username")
return output.rstrip('\n')
- def _rcs_set_user_id(self, value):
+ def _vcs_set_user_id(self, value):
"""
Supported by the Config Extension, but that is not part of
standard Mercurial.
http://www.selenic.com/mercurial/wiki/index.cgi/ConfigExtension
"""
- raise rcs.SettingIDnotSupported
- def _rcs_add(self, path):
+ raise vcs.SettingIDnotSupported
+ def _vcs_add(self, path):
self._u_invoke_client("add", path)
- def _rcs_remove(self, path):
+ def _vcs_remove(self, path):
self._u_invoke_client("rm", "--force", path)
- def _rcs_update(self, path):
+ def _vcs_update(self, path):
pass
- def _rcs_get_file_contents(self, path, revision=None, binary=False):
+ def _vcs_get_file_contents(self, path, revision=None, binary=False):
if revision == None:
- return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
+ return vcs.VCS._vcs_get_file_contents(self, path, revision, binary=binary)
else:
status,output,error = \
self._u_invoke_client("cat","-r",revision,path)
return output
- def _rcs_duplicate_repo(self, directory, revision=None):
+ def _vcs_duplicate_repo(self, directory, revision=None):
if revision == None:
- return RCS._rcs_duplicate_repo(self, directory, revision)
+ return vcs.VCS._vcs_duplicate_repo(self, directory, revision)
else:
self._u_invoke_client("archive", "--rev", revision, directory)
- def _rcs_commit(self, commitfile, allow_empty=False):
+ def _vcs_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()
- 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
+ raise vcs.EmptyCommit()
+ return self._vcs_revision_id(-1)
+ def _vcs_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__])
+vcs.make_vcs_testcase_subclasses(Hg, sys.modules[__name__])
unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
diff --git a/libbe/mapfile.py b/libbe/mapfile.py
index b959d76..4d69601 100644
--- a/libbe/mapfile.py
+++ b/libbe/mapfile.py
@@ -14,12 +14,19 @@
# 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
+
+"""
+Provide a means of saving and loading dictionaries of parameters. The
+saved "mapfiles" should be clear, flat-text files, and allow easy merging of
+independent/conflicting changes.
+"""
+
import errno
-import utility
+import os.path
+import yaml
import doctest
+
class IllegalKey(Exception):
def __init__(self, key):
Exception.__init__(self, 'Illegal key "%s"' % key)
@@ -95,33 +102,15 @@ def parse(contents):
>>> dict["e"]
'f'
"""
- old_format = False
- for line in contents.splitlines():
- if len(line.split("=")) == 2:
- old_format = True
- break
- if old_format: # translate to YAML. Hack to deal with old BE bugs.
- newlines = []
- for line in contents.splitlines():
- line = line.rstrip('\n')
- if len(line) == 0:
- continue
- fields = line.split("=")
- if len(fields) == 2:
- key,value = fields
- newlines.append('%s: "%s"' % (key, value.replace('"','\\"')))
- else:
- newlines.append(line)
- contents = '\n'.join(newlines)
return yaml.load(contents) or {}
-def map_save(rcs, path, map, allow_no_rcs=False):
+def map_save(vcs, path, map, allow_no_vcs=False):
"""Save the map as a mapfile to the specified path"""
contents = generate(map)
- rcs.set_file_contents(path, contents, allow_no_rcs)
+ vcs.set_file_contents(path, contents, allow_no_vcs)
-def map_load(rcs, path, allow_no_rcs=False):
- contents = rcs.get_file_contents(path, allow_no_rcs=allow_no_rcs)
+def map_load(vcs, path, allow_no_vcs=False):
+ contents = vcs.get_file_contents(path, allow_no_vcs=allow_no_vcs)
return parse(contents)
suite = doctest.DocTestSuite()
diff --git a/libbe/plugin.py b/libbe/plugin.py
index 0545fd7..d593d69 100644
--- a/libbe/plugin.py
+++ b/libbe/plugin.py
@@ -15,6 +15,12 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Allow simple listing and loading of the various becommands and libbe
+submodules (i.e. "plugins").
+"""
+
import os
import os.path
import sys
diff --git a/libbe/properties.py b/libbe/properties.py
index 144220b..09dd20e 100644
--- a/libbe/properties.py
+++ b/libbe/properties.py
@@ -160,10 +160,10 @@ 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)
@@ -327,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
@@ -359,9 +359,9 @@ def change_hook_property(hook, mutable=False):
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) != 0:
+ 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
diff --git a/libbe/settings_object.py b/libbe/settings_object.py
index dde247f..ceea9d5 100644
--- a/libbe/settings_object.py
+++ b/libbe/settings_object.py
@@ -148,7 +148,8 @@ def versioned_property(name, doc,
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)
@@ -385,30 +386,24 @@ class SavedSettingsObjectTests(unittest.TestCase):
self.failUnless(SAVES == [], SAVES)
self.failUnless(t._settings_loaded == True, t._settings_loaded)
self.failUnless(t.list_type == None, t.list_type)
- self.failUnless(SAVES == [
- "'None' -> '<class 'libbe.settings_object.EMPTY'>'"
- ], SAVES)
+ 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'>' -> '[]'",
], 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'>' -> '[]'",
], 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'>' -> '[]'",
"'[]' -> '[5]'"
], SAVES)
diff --git a/libbe/tree.py b/libbe/tree.py
index 45ae085..06d09e5 100644
--- a/libbe/tree.py
+++ b/libbe/tree.py
@@ -15,6 +15,10 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Define a traversable tree structure.
+"""
+
import doctest
class Tree(list):
diff --git a/libbe/upgrade.py b/libbe/upgrade.py
new file mode 100644
index 0000000..4123c72
--- /dev/null
+++ b/libbe/upgrade.py
@@ -0,0 +1,187 @@
+# 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.
+
+"""
+Handle conversion between the various on-disk images.
+"""
+
+import os, os.path
+import sys
+import doctest
+
+import encoding
+import mapfile
+import vcs
+
+# a list of all past versions
+BUGDIR_DISK_VERSIONS = ["Bugs Everywhere Tree 1 0",
+ "Bugs Everywhere Directory v1.1",
+ "Bugs Everywhere Directory v1.2"]
+
+# the current version
+BUGDIR_DISK_VERSION = BUGDIR_DISK_VERSIONS[-1]
+
+class Upgrader (object):
+ "Class for converting "
+ initial_version = None
+ final_version = None
+ def __init__(self, root):
+ self.root = root
+ # use the "None" VCS to ensure proper encoding/decoding and
+ # simplify path construction.
+ self.vcs = vcs.vcs_by_name("None")
+ self.vcs.root(self.root)
+ self.vcs.encoding = encoding.get_encoding()
+
+ def get_path(self, *args):
+ """
+ Return a path relative to .root.
+ """
+ dir = os.path.join(self.root, ".be")
+ if len(args) == 0:
+ return dir
+ assert args[0] in ["version", "settings", "bugs"], str(args)
+ return os.path.join(dir, *args)
+
+ def check_initial_version(self):
+ path = self.get_path("version")
+ version = self.vcs.get_file_contents(path).rstrip("\n")
+ assert version == self.initial_version, version
+
+ def set_version(self):
+ path = self.get_path("version")
+ self.vcs.set_file_contents(path, self.final_version+"\n")
+
+ def upgrade(self):
+ print >> sys.stderr, "upgrading bugdir from '%s' to '%s'" \
+ % (self.initial_version, self.final_version)
+ self.check_initial_version()
+ self.set_version()
+ self._upgrade()
+
+ def _upgrade(self):
+ raise NotImplementedError
+
+
+class Upgrade_1_0_to_1_1 (Upgrader):
+ initial_version = "Bugs Everywhere Tree 1 0"
+ final_version = "Bugs Everywhere Directory v1.1"
+ def _upgrade_mapfile(self, path):
+ contents = self.vcs.get_file_contents(path)
+ old_format = False
+ for line in contents.splitlines():
+ if len(line.split("=")) == 2:
+ old_format = True
+ break
+ if old_format == True:
+ # translate to YAML.
+ newlines = []
+ for line in contents.splitlines():
+ line = line.rstrip('\n')
+ if len(line) == 0:
+ continue
+ fields = line.split("=")
+ if len(fields) == 2:
+ key,value = fields
+ newlines.append('%s: "%s"' % (key, value.replace('"','\\"')))
+ else:
+ newlines.append(line)
+ contents = '\n'.join(newlines)
+ # load the YAML and save
+ map = mapfile.parse(contents)
+ mapfile.map_save(self.vcs, path, map)
+
+ def _upgrade(self):
+ """
+ Comment value field "From" -> "Author".
+ Homegrown mapfile -> YAML.
+ """
+ path = self.get_path("settings")
+ self._upgrade_mapfile(path)
+ for bug_uuid in os.listdir(self.get_path("bugs")):
+ path = self.get_path("bugs", bug_uuid, "values")
+ self._upgrade_mapfile(path)
+ c_path = ["bugs", bug_uuid, "comments"]
+ if not os.path.exists(self.get_path(*c_path)):
+ continue # no comments for this bug
+ for comment_uuid in os.listdir(self.get_path(*c_path)):
+ path_list = c_path + [comment_uuid, "values"]
+ path = self.get_path(*path_list)
+ self._upgrade_mapfile(path)
+ settings = mapfile.map_load(self.vcs, path)
+ if "From" in settings:
+ settings["Author"] = settings.pop("From")
+ mapfile.map_save(self.vcs, path, settings)
+
+
+class Upgrade_1_1_to_1_2 (Upgrader):
+ initial_version = "Bugs Everywhere Directory v1.1"
+ final_version = "Bugs Everywhere Directory v1.2"
+ def _upgrade(self):
+ """
+ BugDir settings field "rcs_name" -> "vcs_name".
+ """
+ path = self.get_path("settings")
+ settings = mapfile.map_load(self.vcs, path)
+ if "rcs_name" in settings:
+ settings["vcs_name"] = settings.pop("rcs_name")
+ mapfile.map_save(self.vcs, path, settings)
+
+
+upgraders = [Upgrade_1_0_to_1_1,
+ Upgrade_1_1_to_1_2]
+upgrade_classes = {}
+for upgrader in upgraders:
+ upgrade_classes[(upgrader.initial_version,upgrader.final_version)]=upgrader
+
+def upgrade(path, current_version,
+ target_version=BUGDIR_DISK_VERSION):
+ """
+ Call the appropriate upgrade function to convert current_version
+ to target_version. If a direct conversion function does not exist,
+ use consecutive conversion functions.
+ """
+ if current_version not in BUGDIR_DISK_VERSIONS:
+ raise NotImplementedError, \
+ "Cannot handle version '%s' yet." % version
+ if target_version not in BUGDIR_DISK_VERSIONS:
+ raise NotImplementedError, \
+ "Cannot handle version '%s' yet." % version
+
+ if (current_version, target_version) in upgrade_classes:
+ # direct conversion
+ upgrade_class = upgrade_classes[(current_version, target_version)]
+ u = upgrade_class(path)
+ u.upgrade()
+ else:
+ # consecutive single-step conversion
+ i = BUGDIR_DISK_VERSIONS.index(current_version)
+ while True:
+ version_a = BUGDIR_DISK_VERSIONS[i]
+ version_b = BUGDIR_DISK_VERSIONS[i+1]
+ try:
+ upgrade_class = upgrade_classes[(version_a, version_b)]
+ except KeyError:
+ raise NotImplementedError, \
+ "Cannot convert version '%s' to '%s' yet." \
+ % (version_a, version_b)
+ u = upgrade_class(path)
+ u.upgrade()
+ if version_b == target_version:
+ break
+ i += 1
+
+suite = doctest.DocTestSuite()
diff --git a/libbe/utility.py b/libbe/utility.py
index 3df06b4..aafbf8d 100644
--- a/libbe/utility.py
+++ b/libbe/utility.py
@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+Assorted utility functions that don't fit in anywhere else.
+"""
+
import calendar
import codecs
import os
diff --git a/libbe/rcs.py b/libbe/vcs.py
index 294b8e0..179339e 100644
--- a/libbe/rcs.py
+++ b/libbe/vcs.py
@@ -18,6 +18,12 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Define the base VCS (Version Control System) class, which should be
+subclassed by other Version Control System backends. The base class
+implements a "do not version" VCS.
+"""
+
from subprocess import Popen, PIPE
import codecs
import os
@@ -33,48 +39,49 @@ import doctest
from utility import Dir, search_parent_directories
-def _get_matching_rcs(matchfn):
- """Return the first module for which matchfn(RCS_instance) is true"""
+def _get_matching_vcs(matchfn):
+ """Return the first module for which matchfn(VCS_instance) is true"""
import arch
import bzr
import darcs
import git
import hg
for module in [arch, bzr, darcs, git, hg]:
- rcs = module.new()
- if matchfn(rcs) == True:
- return rcs
- del(rcs)
- return RCS()
+ vcs = module.new()
+ if matchfn(vcs) == True:
+ return vcs
+ del(vcs)
+ return VCS()
-def rcs_by_name(rcs_name):
- """Return the module for the RCS with the given name"""
- return _get_matching_rcs(lambda rcs: rcs.name == rcs_name)
+def vcs_by_name(vcs_name):
+ """Return the module for the VCS with the given name"""
+ return _get_matching_vcs(lambda vcs: vcs.name == vcs_name)
-def detect_rcs(dir):
- """Return an RCS instance for the rcs being used in this directory"""
- return _get_matching_rcs(lambda rcs: rcs.detect(dir))
+def detect_vcs(dir):
+ """Return an VCS instance for the vcs being used in this directory"""
+ return _get_matching_vcs(lambda vcs: vcs.detect(dir))
-def installed_rcs():
- """Return an instance of an installed RCS"""
- return _get_matching_rcs(lambda rcs: rcs.installed())
+def installed_vcs():
+ """Return an instance of an installed VCS"""
+ return _get_matching_vcs(lambda vcs: vcs.installed())
class CommandError(Exception):
- def __init__(self, command, status, err_str):
- strerror = ["Command failed (%d):\n %s\n" % (status, err_str),
+ def __init__(self, command, status, stdout, stderr):
+ strerror = ["Command failed (%d):\n %s\n" % (status, stderr),
"while executing\n %s" % command]
Exception.__init__(self, "\n".join(strerror))
self.command = command
self.status = status
- self.err_str = err_str
+ self.stdout = stdout
+ self.stderr = stderr
class SettingIDnotSupported(NotImplementedError):
pass
-class RCSnotRooted(Exception):
+class VCSnotRooted(Exception):
def __init__(self):
- msg = "RCS not rooted"
+ msg = "VCS not rooted"
Exception.__init__(self, msg)
class PathNotInRoot(Exception):
@@ -95,16 +102,16 @@ class EmptyCommit(Exception):
def new():
- return RCS()
+ return VCS()
-class RCS(object):
+class VCS(object):
"""
- This class implements a 'no-rcs' interface.
+ This class implements a 'no-vcs' interface.
- Support for other RCSs can be added by subclassing this class, and
- overriding methods _rcs_*() with code appropriate for your RCS.
+ Support for other VCSs can be added by subclassing this class, and
+ overriding methods _vcs_*() with code appropriate for your VCS.
- The methods _u_*() are utility methods available to the _rcs_*()
+ The methods _u_*() are utility methods available to the _vcs_*()
methods.
"""
name = "None"
@@ -120,76 +127,76 @@ class RCS(object):
def __del__(self):
self.cleanup()
- def _rcs_help(self):
+ def _vcs_help(self):
"""
Return the command help string.
(Allows a simple test to see if the client is installed.)
"""
pass
- def _rcs_detect(self, path=None):
+ def _vcs_detect(self, path=None):
"""
- Detect whether a directory is revision controlled with this RCS.
+ Detect whether a directory is revision controlled with this VCS.
"""
return True
- def _rcs_root(self, path):
+ def _vcs_root(self, path):
"""
- Get the RCS root. This is the default working directory for
+ Get the VCS root. This is the default working directory for
future invocations. You would normally set this to the root
- directory for your RCS.
+ directory for your VCS.
"""
if os.path.isdir(path)==False:
path = os.path.dirname(path)
if path == "":
path = os.path.abspath(".")
return path
- def _rcs_init(self, path):
+ def _vcs_init(self, path):
"""
Begin versioning the tree based at path.
"""
pass
- def _rcs_cleanup(self):
+ def _vcs_cleanup(self):
"""
- Remove any cruft that _rcs_init() created outside of the
+ Remove any cruft that _vcs_init() created outside of the
versioned tree.
"""
pass
- def _rcs_get_user_id(self):
+ def _vcs_get_user_id(self):
"""
- Get the RCS's suggested user id (e.g. "John Doe <jdoe@example.com>").
- If the RCS has not been configured with a username, return None.
+ Get the VCS's suggested user id (e.g. "John Doe <jdoe@example.com>").
+ If the VCS has not been configured with a username, return None.
"""
return None
- def _rcs_set_user_id(self, value):
+ def _vcs_set_user_id(self, value):
"""
- Set the RCS's suggested user id (e.g "John Doe <jdoe@example.com>").
- This is run if the RCS has not been configured with a usename, so
+ Set the VCS's suggested user id (e.g "John Doe <jdoe@example.com>").
+ This is run if the VCS has not been configured with a usename, so
that commits will have a reasonable FROM value.
"""
raise SettingIDnotSupported
- def _rcs_add(self, path):
+ def _vcs_add(self, path):
"""
Add the already created file at path to version control.
"""
pass
- def _rcs_remove(self, path):
+ def _vcs_remove(self, path):
"""
Remove the file at path from version control. Optionally
remove the file from the filesystem as well.
"""
pass
- def _rcs_update(self, path):
+ def _vcs_update(self, path):
"""
Notify the versioning system of changes to the versioned file
at path.
"""
pass
- def _rcs_get_file_contents(self, path, revision=None, binary=False):
+ def _vcs_get_file_contents(self, path, revision=None, binary=False):
"""
Get the file contents as they were in a given revision.
Revision==None specifies the current revision.
"""
assert revision == None, \
- "The %s RCS does not support revision specifiers" % self.name
+ "The %s VCS does not support revision specifiers" % self.name
if binary == False:
f = codecs.open(os.path.join(self.rootdir, path), "r", self.encoding)
else:
@@ -197,14 +204,14 @@ class RCS(object):
contents = f.read()
f.close()
return contents
- def _rcs_duplicate_repo(self, directory, revision=None):
+ def _vcs_duplicate_repo(self, directory, revision=None):
"""
Get the repository as it was in a given revision.
revision==None specifies the current revision.
dir specifies a directory to create the duplicate in.
"""
shutil.copytree(self.rootdir, directory, True)
- def _rcs_commit(self, commitfile, allow_empty=False):
+ def _vcs_commit(self, commitfile, allow_empty=False):
"""
Commit the current working directory, using the contents of
commitfile as the comment. Return the name of the old
@@ -214,9 +221,19 @@ class RCS(object):
changes to commit.
"""
return None
+ def _vcs_revision_id(self, index):
+ """
+ Return the name of the <index>th revision. Index will be an
+ integer (possibly <= 0). The choice of which branch to follow
+ when crossing branches/merges is not defined.
+
+ Return None if revision IDs are not supported, or if the
+ specified revision does not exist.
+ """
+ return None
def installed(self):
try:
- self._rcs_help()
+ self._vcs_help()
return True
except OSError, e:
if e.errno == errno.ENOENT:
@@ -225,37 +242,37 @@ class RCS(object):
return False
def detect(self, path="."):
"""
- Detect whether a directory is revision controlled with this RCS.
+ Detect whether a directory is revision controlled with this VCS.
"""
- return self._rcs_detect(path)
+ return self._vcs_detect(path)
def root(self, path):
"""
- Set the root directory to the path's RCS root. This is the
+ Set the root directory to the path's VCS root. This is the
default working directory for future invocations.
"""
- self.rootdir = self._rcs_root(path)
+ self.rootdir = self._vcs_root(path)
def init(self, path):
"""
Begin versioning the tree based at path.
- Also roots the rcs at path.
+ Also roots the vcs at path.
"""
if os.path.isdir(path)==False:
path = os.path.dirname(path)
- self._rcs_init(path)
+ self._vcs_init(path)
self.root(path)
def cleanup(self):
- self._rcs_cleanup()
+ self._vcs_cleanup()
def get_user_id(self):
"""
- Get the RCS's suggested user id (e.g. "John Doe <jdoe@example.com>").
- If the RCS has not been configured with a username, return the user's
+ Get the VCS's suggested user id (e.g. "John Doe <jdoe@example.com>").
+ If the VCS has not been configured with a username, return the user's
id. You can override the automatic lookup procedure by setting the
- RCS.user_id attribute to a string of your choice.
+ VCS.user_id attribute to a string of your choice.
"""
if hasattr(self, "user_id"):
if self.user_id != None:
return self.user_id
- id = self._rcs_get_user_id()
+ id = self._vcs_get_user_id()
if id == None:
name = self._u_get_fallback_username()
email = self._u_get_fallback_email()
@@ -268,21 +285,21 @@ class RCS(object):
return id
def set_user_id(self, value):
"""
- Set the RCS's suggested user id (e.g "John Doe <jdoe@example.com>").
- This is run if the RCS has not been configured with a usename, so
+ Set the VCS's suggested user id (e.g "John Doe <jdoe@example.com>").
+ This is run if the VCS has not been configured with a usename, so
that commits will have a reasonable FROM value.
"""
- self._rcs_set_user_id(value)
+ self._vcs_set_user_id(value)
def add(self, path):
"""
Add the already created file at path to version control.
"""
- self._rcs_add(self._u_rel_path(path))
+ self._vcs_add(self._u_rel_path(path))
def remove(self, path):
"""
Remove a file from both version control and the filesystem.
"""
- self._rcs_remove(self._u_rel_path(path))
+ self._vcs_remove(self._u_rel_path(path))
if os.path.exists(path):
os.remove(path)
def recursive_remove(self, dirname):
@@ -298,7 +315,7 @@ class RCS(object):
fullpath = os.path.join(dirpath, path)
if os.path.exists(fullpath) == False:
continue
- self._rcs_remove(self._u_rel_path(fullpath))
+ self._vcs_remove(self._u_rel_path(fullpath))
if os.path.exists(dirname):
shutil.rmtree(dirname)
def update(self, path):
@@ -306,23 +323,23 @@ class RCS(object):
Notify the versioning system of changes to the versioned file
at path.
"""
- self._rcs_update(self._u_rel_path(path))
- def get_file_contents(self, path, revision=None, allow_no_rcs=False, binary=False):
+ self._vcs_update(self._u_rel_path(path))
+ def get_file_contents(self, path, revision=None, allow_no_vcs=False, binary=False):
"""
Get the file as it was in a given revision.
Revision==None specifies the current revision.
"""
if not os.path.exists(path):
raise NoSuchFile(path)
- if self._use_rcs(path, allow_no_rcs):
+ if self._use_vcs(path, allow_no_vcs):
relpath = self._u_rel_path(path)
- contents = self._rcs_get_file_contents(relpath,revision,binary=binary)
+ contents = self._vcs_get_file_contents(relpath,revision,binary=binary)
else:
f = codecs.open(path, "r", self.encoding)
contents = f.read()
f.close()
return contents
- def set_file_contents(self, path, contents, allow_no_rcs=False, binary=False):
+ def set_file_contents(self, path, contents, allow_no_vcs=False, binary=False):
"""
Set the file contents under version control.
"""
@@ -334,12 +351,12 @@ class RCS(object):
f.write(contents)
f.close()
- if self._use_rcs(path, allow_no_rcs):
+ if self._use_vcs(path, allow_no_vcs):
if add:
self.add(path)
else:
self.update(path)
- def mkdir(self, path, allow_no_rcs=False, check_parents=True):
+ def mkdir(self, path, allow_no_vcs=False, check_parents=True):
"""
Create (if neccessary) a directory at path under version
control.
@@ -347,14 +364,14 @@ class RCS(object):
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)
+ self.mkdir(parent, allow_no_vcs, check_parents)
if not os.path.exists(path):
os.mkdir(path)
- if self._use_rcs(path, allow_no_rcs):
+ if self._use_vcs(path, allow_no_vcs):
self.add(path)
else:
assert os.path.isdir(path)
- if self._use_rcs(path, allow_no_rcs):
+ if self._use_vcs(path, allow_no_vcs):
#self.update(path)# Don't update directories. Changing files
pass # underneath them should be sufficient.
@@ -366,10 +383,10 @@ class RCS(object):
"""
# Dirname in Baseir to protect against simlink attacks.
if self._duplicateBasedir == None:
- self._duplicateBasedir = tempfile.mkdtemp(prefix='BErcs')
+ self._duplicateBasedir = tempfile.mkdtemp(prefix='BEvcs')
self._duplicateDirname = \
os.path.join(self._duplicateBasedir, "duplicate")
- self._rcs_duplicate_repo(directory=self._duplicateDirname,
+ self._vcs_duplicate_repo(directory=self._duplicateDirname,
revision=revision)
return self._duplicateDirname
def remove_duplicate_repo(self):
@@ -399,7 +416,7 @@ class RCS(object):
temp_file.write(summary)
temp_file.flush()
self.precommit()
- revision = self._rcs_commit(filename, allow_empty=allow_empty)
+ revision = self._vcs_commit(filename, allow_empty=allow_empty)
temp_file.close()
self.postcommit()
finally:
@@ -415,6 +432,18 @@ class RCS(object):
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._vcs_revision_id(index)
def _u_any_in_string(self, list, string):
"""
Return True if any of the strings in list are in string.
@@ -441,13 +470,13 @@ class RCS(object):
q = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
shell=True, cwd=cwd)
except OSError, e :
- raise CommandError(args, e.args[0], e)
- output, error = q.communicate(input=stdin)
+ raise CommandError(args, status=e.args[0], stdout="", stderr=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:
- raise CommandError(args, status, error)
+ raise CommandError(args, status, output, error)
return status, output, error
def _u_invoke_client(self, *args, **kwargs):
directory = kwargs.get('directory',None)
@@ -471,36 +500,36 @@ class RCS(object):
or None if none of those files exist.
"""
return search_parent_directories(path, filename)
- def _use_rcs(self, path, allow_no_rcs):
+ def _use_vcs(self, path, allow_no_vcs):
"""
- Try and decide if _rcs_add/update/mkdir/etc calls will
- succeed. Returns True is we think the rcs_call would
+ Try and decide if _vcs_add/update/mkdir/etc calls will
+ succeed. Returns True is we think the vcs_call would
succeeed, and False otherwise.
"""
- use_rcs = True
+ use_vcs = True
exception = None
if self.rootdir != None:
if self.path_in_root(path) == False:
- use_rcs = False
+ use_vcs = False
exception = PathNotInRoot(path, self.rootdir)
else:
- use_rcs = False
- exception = RCSnotRooted
- if use_rcs == False and allow_no_rcs==False:
+ use_vcs = False
+ exception = VCSnotRooted
+ if use_vcs == False and allow_no_vcs==False:
raise exception
- return use_rcs
+ return use_vcs
def path_in_root(self, path, root=None):
"""
Return the relative path to path from root.
- >>> rcs = new()
- >>> rcs.path_in_root("/a.b/c/.be", "/a.b/c")
+ >>> vcs = new()
+ >>> vcs.path_in_root("/a.b/c/.be", "/a.b/c")
True
- >>> rcs.path_in_root("/a.b/.be", "/a.b/c")
+ >>> vcs.path_in_root("/a.b/.be", "/a.b/c")
False
"""
if root == None:
if self.rootdir == None:
- raise RCSnotRooted
+ raise VCSnotRooted
root = self.rootdir
path = os.path.abspath(path)
absRoot = os.path.abspath(root)
@@ -511,13 +540,13 @@ class RCS(object):
def _u_rel_path(self, path, root=None):
"""
Return the relative path to path from root.
- >>> rcs = new()
- >>> rcs._u_rel_path("/a.b/c/.be", "/a.b/c")
+ >>> vcs = new()
+ >>> vcs._u_rel_path("/a.b/c/.be", "/a.b/c")
'.be'
"""
if root == None:
if self.rootdir == None:
- raise RCSnotRooted
+ raise VCSnotRooted
root = self.rootdir
path = os.path.abspath(path)
absRoot = os.path.abspath(root)
@@ -531,20 +560,20 @@ class RCS(object):
def _u_abspath(self, path, root=None):
"""
Return the absolute path from a path realtive to root.
- >>> rcs = new()
- >>> rcs._u_abspath(".be", "/a.b/c")
+ >>> vcs = new()
+ >>> vcs._u_abspath(".be", "/a.b/c")
'/a.b/c/.be'
"""
if root == None:
- assert self.rootdir != None, "RCS not rooted"
+ assert self.rootdir != None, "VCS not rooted"
root = self.rootdir
return os.path.abspath(os.path.join(root, path))
def _u_create_id(self, name, email=None):
"""
- >>> rcs = new()
- >>> rcs._u_create_id("John Doe", "jdoe@example.com")
+ >>> vcs = new()
+ >>> vcs._u_create_id("John Doe", "jdoe@example.com")
'John Doe <jdoe@example.com>'
- >>> rcs._u_create_id("John Doe")
+ >>> vcs._u_create_id("John Doe")
'John Doe'
"""
assert len(name) > 0
@@ -554,13 +583,13 @@ class RCS(object):
return "%s <%s>" % (name, email)
def _u_parse_id(self, value):
"""
- >>> rcs = new()
- >>> rcs._u_parse_id("John Doe <jdoe@example.com>")
+ >>> vcs = new()
+ >>> vcs._u_parse_id("John Doe <jdoe@example.com>")
('John Doe', 'jdoe@example.com')
- >>> rcs._u_parse_id("John Doe")
+ >>> vcs._u_parse_id("John Doe")
('John Doe', None)
>>> try:
- ... rcs._u_parse_id("John Doe <jdoe@example.com><what?>")
+ ... vcs._u_parse_id("John Doe <jdoe@example.com><what?>")
... except AssertionError:
... print "Invalid match"
Invalid match
@@ -605,131 +634,131 @@ class RCS(object):
return (summary, body)
-def setup_rcs_test_fixtures(testcase):
- """Set up test fixtures for RCS test case."""
- testcase.rcs = testcase.Class()
+def setup_vcs_test_fixtures(testcase):
+ """Set up test fixtures for VCS test case."""
+ testcase.vcs = testcase.Class()
testcase.dir = Dir()
testcase.dirname = testcase.dir.path
- rcs_not_supporting_uninitialized_user_id = []
- rcs_not_supporting_set_user_id = ["None", "hg"]
- testcase.rcs_supports_uninitialized_user_id = (
- testcase.rcs.name not in rcs_not_supporting_uninitialized_user_id)
- testcase.rcs_supports_set_user_id = (
- testcase.rcs.name not in rcs_not_supporting_set_user_id)
+ vcs_not_supporting_uninitialized_user_id = []
+ vcs_not_supporting_set_user_id = ["None", "hg"]
+ testcase.vcs_supports_uninitialized_user_id = (
+ testcase.vcs.name not in vcs_not_supporting_uninitialized_user_id)
+ testcase.vcs_supports_set_user_id = (
+ testcase.vcs.name not in vcs_not_supporting_set_user_id)
- if not testcase.rcs.installed():
+ if not testcase.vcs.installed():
testcase.fail(
- "%(name)s RCS not found" % vars(testcase.Class))
+ "%(name)s VCS not found" % vars(testcase.Class))
if testcase.Class.name != "None":
testcase.failIf(
- testcase.rcs.detect(testcase.dirname),
- "Detected %(name)s RCS before initialising"
+ testcase.vcs.detect(testcase.dirname),
+ "Detected %(name)s VCS before initialising"
% vars(testcase.Class))
- testcase.rcs.init(testcase.dirname)
+ testcase.vcs.init(testcase.dirname)
-class RCSTestCase(unittest.TestCase):
- """Test cases for base RCS class."""
+class VCSTestCase(unittest.TestCase):
+ """Test cases for base VCS class."""
- Class = RCS
+ Class = VCS
def __init__(self, *args, **kwargs):
- super(RCSTestCase, self).__init__(*args, **kwargs)
+ super(VCSTestCase, self).__init__(*args, **kwargs)
self.dirname = None
def setUp(self):
- super(RCSTestCase, self).setUp()
- setup_rcs_test_fixtures(self)
+ super(VCSTestCase, self).setUp()
+ setup_vcs_test_fixtures(self)
def tearDown(self):
- del(self.rcs)
- super(RCSTestCase, self).tearDown()
+ del(self.vcs)
+ super(VCSTestCase, self).tearDown()
def full_path(self, rel_path):
return os.path.join(self.dirname, rel_path)
-class RCS_init_TestCase(RCSTestCase):
- """Test cases for RCS.init method."""
+class VCS_init_TestCase(VCSTestCase):
+ """Test cases for VCS.init method."""
def test_detect_should_succeed_after_init(self):
- """Should detect RCS in directory after initialization."""
+ """Should detect VCS in directory after initialization."""
self.failUnless(
- self.rcs.detect(self.dirname),
- "Did not detect %(name)s RCS after initialising"
+ self.vcs.detect(self.dirname),
+ "Did not detect %(name)s VCS after initialising"
% vars(self.Class))
- def test_rcs_rootdir_in_specified_root_path(self):
- """RCS root directory should be in specified root path."""
- rp = os.path.realpath(self.rcs.rootdir)
+ def test_vcs_rootdir_in_specified_root_path(self):
+ """VCS root directory should be in specified root path."""
+ rp = os.path.realpath(self.vcs.rootdir)
dp = os.path.realpath(self.dirname)
- rcs_name = self.Class.name
+ vcs_name = self.Class.name
self.failUnless(
dp == rp or rp == None,
- "%(rcs_name)s RCS root in wrong dir (%(dp)s %(rp)s)" % vars())
+ "%(vcs_name)s VCS root in wrong dir (%(dp)s %(rp)s)" % vars())
-class RCS_get_user_id_TestCase(RCSTestCase):
- """Test cases for RCS.get_user_id method."""
+class VCS_get_user_id_TestCase(VCSTestCase):
+ """Test cases for VCS.get_user_id method."""
def test_gets_existing_user_id(self):
"""Should get the existing user ID."""
- if not self.rcs_supports_uninitialized_user_id:
+ if not self.vcs_supports_uninitialized_user_id:
return
- user_id = self.rcs.get_user_id()
+ user_id = self.vcs.get_user_id()
self.failUnless(
user_id is not None,
"unable to get a user id")
-class RCS_set_user_id_TestCase(RCSTestCase):
- """Test cases for RCS.set_user_id method."""
+class VCS_set_user_id_TestCase(VCSTestCase):
+ """Test cases for VCS.set_user_id method."""
def setUp(self):
- super(RCS_set_user_id_TestCase, self).setUp()
+ super(VCS_set_user_id_TestCase, self).setUp()
- if self.rcs_supports_uninitialized_user_id:
- self.prev_user_id = self.rcs.get_user_id()
+ if self.vcs_supports_uninitialized_user_id:
+ self.prev_user_id = self.vcs.get_user_id()
else:
self.prev_user_id = "Uninitialized identity <bogus@example.org>"
- if self.rcs_supports_set_user_id:
+ if self.vcs_supports_set_user_id:
self.test_new_user_id = "John Doe <jdoe@example.com>"
- self.rcs.set_user_id(self.test_new_user_id)
+ self.vcs.set_user_id(self.test_new_user_id)
def tearDown(self):
- if self.rcs_supports_set_user_id:
- self.rcs.set_user_id(self.prev_user_id)
- super(RCS_set_user_id_TestCase, self).tearDown()
+ if self.vcs_supports_set_user_id:
+ self.vcs.set_user_id(self.prev_user_id)
+ super(VCS_set_user_id_TestCase, self).tearDown()
def test_raises_error_in_unsupported_vcs(self):
"""Should raise an error in a VCS that doesn't support it."""
- if self.rcs_supports_set_user_id:
+ if self.vcs_supports_set_user_id:
return
self.assertRaises(
SettingIDnotSupported,
- self.rcs.set_user_id, "foo")
+ self.vcs.set_user_id, "foo")
- def test_updates_user_id_in_supporting_rcs(self):
- """Should update the user ID in an RCS that supports it."""
- if not self.rcs_supports_set_user_id:
+ def test_updates_user_id_in_supporting_vcs(self):
+ """Should update the user ID in an VCS that supports it."""
+ if not self.vcs_supports_set_user_id:
return
- user_id = self.rcs.get_user_id()
+ user_id = self.vcs.get_user_id()
self.failUnlessEqual(
self.test_new_user_id, user_id,
"user id not set correctly (expected %s, got %s)"
% (self.test_new_user_id, user_id))
-def setup_rcs_revision_test_fixtures(testcase):
- """Set up revision test fixtures for RCS test case."""
+def setup_vcs_revision_test_fixtures(testcase):
+ """Set up revision test fixtures for VCS test case."""
testcase.test_dirs = ['a', 'a/b', 'c']
for path in testcase.test_dirs:
- testcase.rcs.mkdir(testcase.full_path(path))
+ testcase.vcs.mkdir(testcase.full_path(path))
testcase.test_files = ['a/text', 'a/b/text']
@@ -739,17 +768,17 @@ def setup_rcs_revision_test_fixtures(testcase):
}
-class RCS_mkdir_TestCase(RCSTestCase):
- """Test cases for RCS.mkdir method."""
+class VCS_mkdir_TestCase(VCSTestCase):
+ """Test cases for VCS.mkdir method."""
def setUp(self):
- super(RCS_mkdir_TestCase, self).setUp()
- setup_rcs_revision_test_fixtures(self)
+ super(VCS_mkdir_TestCase, self).setUp()
+ setup_vcs_revision_test_fixtures(self)
def tearDown(self):
for path in reversed(sorted(self.test_dirs)):
- self.rcs.recursive_remove(self.full_path(path))
- super(RCS_mkdir_TestCase, self).tearDown()
+ self.vcs.recursive_remove(self.full_path(path))
+ super(VCS_mkdir_TestCase, self).tearDown()
def test_mkdir_creates_directory(self):
"""Should create specified directory in filesystem."""
@@ -760,25 +789,25 @@ class RCS_mkdir_TestCase(RCSTestCase):
"path %(full_path)s does not exist" % vars())
-class RCS_commit_TestCase(RCSTestCase):
- """Test cases for RCS.commit method."""
+class VCS_commit_TestCase(VCSTestCase):
+ """Test cases for VCS.commit method."""
def setUp(self):
- super(RCS_commit_TestCase, self).setUp()
- setup_rcs_revision_test_fixtures(self)
+ super(VCS_commit_TestCase, self).setUp()
+ setup_vcs_revision_test_fixtures(self)
def tearDown(self):
for path in reversed(sorted(self.test_dirs)):
- self.rcs.recursive_remove(self.full_path(path))
- super(RCS_commit_TestCase, self).tearDown()
+ self.vcs.recursive_remove(self.full_path(path))
+ super(VCS_commit_TestCase, self).tearDown()
def test_file_contents_as_specified(self):
"""Should set file contents as specified."""
test_contents = self.test_contents['rev_1']
for path in self.test_files:
full_path = self.full_path(path)
- self.rcs.set_file_contents(full_path, test_contents)
- current_contents = self.rcs.get_file_contents(full_path)
+ self.vcs.set_file_contents(full_path, test_contents)
+ current_contents = self.vcs.get_file_contents(full_path)
self.failUnlessEqual(test_contents, current_contents)
def test_file_contents_as_committed(self):
@@ -786,87 +815,121 @@ class RCS_commit_TestCase(RCSTestCase):
test_contents = self.test_contents['rev_1']
for path in self.test_files:
full_path = self.full_path(path)
- self.rcs.set_file_contents(full_path, test_contents)
- revision = self.rcs.commit("Initial file contents.")
- current_contents = self.rcs.get_file_contents(full_path)
+ self.vcs.set_file_contents(full_path, test_contents)
+ revision = self.vcs.commit("Initial file contents.")
+ current_contents = self.vcs.get_file_contents(full_path)
self.failUnlessEqual(test_contents, current_contents)
def test_file_contents_as_set_when_uncommitted(self):
"""Should set file contents as specified after commit."""
- if not self.rcs.versioned:
+ if not self.vcs.versioned:
return
for path in self.test_files:
full_path = self.full_path(path)
- self.rcs.set_file_contents(
+ self.vcs.set_file_contents(
full_path, self.test_contents['rev_1'])
- revision = self.rcs.commit("Initial file contents.")
- self.rcs.set_file_contents(
+ revision = self.vcs.commit("Initial file contents.")
+ self.vcs.set_file_contents(
full_path, self.test_contents['uncommitted'])
- current_contents = self.rcs.get_file_contents(full_path)
+ current_contents = self.vcs.get_file_contents(full_path)
self.failUnlessEqual(
self.test_contents['uncommitted'], current_contents)
def test_revision_file_contents_as_committed(self):
"""Should get file contents as committed to specified revision."""
- if not self.rcs.versioned:
+ import sys
+ if not self.vcs.versioned:
return
for path in self.test_files:
full_path = self.full_path(path)
- self.rcs.set_file_contents(
+ self.vcs.set_file_contents(
full_path, self.test_contents['rev_1'])
- revision = self.rcs.commit("Initial file contents.")
- self.rcs.set_file_contents(
+ revision = self.vcs.commit("Initial file contents.")
+ self.vcs.set_file_contents(
full_path, self.test_contents['uncommitted'])
- committed_contents = self.rcs.get_file_contents(
+ committed_contents = self.vcs.get_file_contents(
full_path, revision)
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.vcs.versioned:
+ self.failUnlessEqual(self.vcs.revision_id(5), None)
+ return
+ committed_revisions = []
+ for path in self.test_files:
+ full_path = self.full_path(path)
+ self.vcs.set_file_contents(
+ full_path, self.test_contents['rev_1'])
+ revision = self.vcs.commit("Initial %s contents." % path)
+ committed_revisions.append(revision)
+ self.vcs.set_file_contents(
+ full_path, self.test_contents['uncommitted'])
+ revision = self.vcs.commit("Altered %s contents." % path)
+ committed_revisions.append(revision)
+ for i,revision in enumerate(committed_revisions):
+ self.failUnlessEqual(self.vcs.revision_id(i), revision)
+ i += -len(committed_revisions) # check negative indices
+ self.failUnlessEqual(self.vcs.revision_id(i), revision)
+ i = len(committed_revisions)
+ self.failUnlessEqual(self.vcs.revision_id(i), None)
+ self.failUnlessEqual(self.vcs.revision_id(-i-1), None)
+
+ def test_revision_id_as_committed(self):
+ """Check revision id before first commit"""
+ if not self.vcs.versioned:
+ self.failUnlessEqual(self.vcs.revision_id(5), None)
+ return
+ committed_revisions = []
+ for path in self.test_files:
+ self.failUnlessEqual(self.vcs.revision_id(0), None)
+
-class RCS_duplicate_repo_TestCase(RCSTestCase):
- """Test cases for RCS.duplicate_repo method."""
+class VCS_duplicate_repo_TestCase(VCSTestCase):
+ """Test cases for VCS.duplicate_repo method."""
def setUp(self):
- super(RCS_duplicate_repo_TestCase, self).setUp()
- setup_rcs_revision_test_fixtures(self)
+ super(VCS_duplicate_repo_TestCase, self).setUp()
+ setup_vcs_revision_test_fixtures(self)
def tearDown(self):
- self.rcs.remove_duplicate_repo()
+ self.vcs.remove_duplicate_repo()
for path in reversed(sorted(self.test_dirs)):
- self.rcs.recursive_remove(self.full_path(path))
- super(RCS_duplicate_repo_TestCase, self).tearDown()
+ self.vcs.recursive_remove(self.full_path(path))
+ super(VCS_duplicate_repo_TestCase, self).tearDown()
def test_revision_file_contents_as_committed(self):
"""Should match file contents as committed to specified revision."""
- if not self.rcs.versioned:
+ if not self.vcs.versioned:
return
for path in self.test_files:
full_path = self.full_path(path)
- self.rcs.set_file_contents(
+ self.vcs.set_file_contents(
full_path, self.test_contents['rev_1'])
- revision = self.rcs.commit("Commit current status")
- self.rcs.set_file_contents(
+ revision = self.vcs.commit("Commit current status")
+ self.vcs.set_file_contents(
full_path, self.test_contents['uncommitted'])
- dup_repo_path = self.rcs.duplicate_repo(revision)
+ dup_repo_path = self.vcs.duplicate_repo(revision)
dup_file_path = os.path.join(dup_repo_path, path)
dup_file_contents = file(dup_file_path, 'rb').read()
self.failUnlessEqual(
self.test_contents['rev_1'], dup_file_contents)
- self.rcs.remove_duplicate_repo()
+ self.vcs.remove_duplicate_repo()
-def make_rcs_testcase_subclasses(rcs_class, namespace):
- """Make RCSTestCase subclasses for rcs_class in the namespace."""
- rcs_testcase_classes = [
+def make_vcs_testcase_subclasses(vcs_class, namespace):
+ """Make VCSTestCase subclasses for vcs_class in the namespace."""
+ vcs_testcase_classes = [
c for c in (
ob for ob in globals().values() if isinstance(ob, type))
- if issubclass(c, RCSTestCase)]
+ if issubclass(c, VCSTestCase)]
- for base_class in rcs_testcase_classes:
- testcase_class_name = rcs_class.__name__ + base_class.__name__
+ for base_class in vcs_testcase_classes:
+ testcase_class_name = vcs_class.__name__ + base_class.__name__
testcase_class_bases = (base_class,)
testcase_class_dict = dict(base_class.__dict__)
- testcase_class_dict['Class'] = rcs_class
+ testcase_class_dict['Class'] = vcs_class
testcase_class = type(
testcase_class_name, testcase_class_bases, testcase_class_dict)
setattr(namespace, testcase_class_name, testcase_class)
diff --git a/libbe/version.py b/libbe/version.py
new file mode 100644
index 0000000..f8eebbd
--- /dev/null
+++ b/libbe/version.py
@@ -0,0 +1,50 @@
+#!/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.
+
+"""
+Store version info for this BE installation. By default, use the
+bzr-generated information in _version.py, but allow manual overriding
+by setting _VERSION. This allows support of both the "I don't want to
+be bothered setting version strings" and the "I want complete control
+over the version strings" workflows.
+"""
+
+import libbe._version as _version
+
+# Manually set a version string (optional, defaults to bzr revision id)
+#_VERSION = "1.2.3"
+
+def version(verbose=False):
+ """
+ Returns the version string for this BE installation. If
+ verbose==True, the string will include extra lines with more
+ detail (e.g. bzr branch nickname, etc.).
+ """
+ if "_VERSION" in globals():
+ string = _VERSION
+ else:
+ string = _version.version_info["revision_id"]
+ if verbose == True:
+ string += ("\n"
+ "revision: %(revno)d\n"
+ "nick: %(branch_nick)s\n"
+ "revision id: %(revision_id)s"
+ % _version.version_info)
+ return string
+
+if __name__ == "__main__":
+ print version(verbose=True)
diff --git a/update_copyright.sh b/update_copyright.sh
index 28eb0e0..84a5913 100755
--- a/update_copyright.sh
+++ b/update_copyright.sh
@@ -59,7 +59,7 @@ ESCAPED_TEXT=`echo "$COPYRIGHT_TEXT" | awk '{printf("%s\\\\n", $0)}' | sed "$SED
# 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
+AUTHORS=`echo "$AUTHORS" | grep -v 'j\^\|wking\|John Doe\|gianluca'` # remove non-names
echo "Bugs Everywhere was written by:" > AUTHORS
echo "$AUTHORS" >> AUTHORS
@@ -99,6 +99,12 @@ do
# Tweak the author list to make up for problems in the bzr
# history, change of email address, etc.
+ # Consolidate Chris Ball
+ GREP_OUT=`echo "$AUTHORS" | grep 'Chris Ball <cjb@laptop.org>'`
+ if [ -n "$GREP_OUT" ]; then
+ AUTHORS=`echo "$AUTHORS" | grep -v '^Chris Ball <cjb@thunk.printf.net>$'`
+ fi
+
# Consolidate Aaron Bentley
AUTHORS=`echo "$AUTHORS" | sed 's/<abentley@panoramicfeedback.com>/and Panometrics, Inc./'`
GREP_OUT=`echo "$AUTHORS" | grep 'Aaron Bentley and Panometrics, Inc.'`
@@ -113,15 +119,12 @@ do
AUTHORS=`echo "$AUTHORS" | grep -v '^Ben Finney <benf@cybersource.com.au>$'`
fi
- # Consolidate Chris Ball
- GREP_OUT=`echo "$AUTHORS" | grep 'Chris Ball <cjb@laptop.org>'`
- if [ -n "$GREP_OUT" ]; then
- AUTHORS=`echo "$AUTHORS" | grep -v '^Chris Ball <cjb@thunk.printf.net>$'`
- fi
-
# Consolidate Trevor King
AUTHORS=`echo "$AUTHORS" | grep -v "wking <wking@mjolnir>"`
+ # Consolidate Gianluca Montecchi
+ AUTHORS=`echo "$AUTHORS" | grep -v "gianluca"`
+
# Sort again...
AUTHORS=`echo "$AUTHORS" | sort | uniq`