Rev 2446: (robertc) bzr --fixes support to allow recording of bugs that are fixed by commits. (Jonathan Lange, James Henstridge, Robert Collins) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Mon Apr 23 08:50:18 BST 2007


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 2446
revision-id: pqm at pqm.ubuntu.com-20070423075015-340ajk1vo3pzxheu
parent: pqm at pqm.ubuntu.com-20070423070809-7imm5hgj6g0n78s5
parent: jml at canonical.com-20070423065558-wbkypy8qlhzrxugz
committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Mon 2007-04-23 08:50:15 +0100
message:
  (robertc) bzr --fixes support to allow recording of bugs that are fixed by commits. (Jonathan Lange, James Henstridge, Robert Collins)
added:
  bzrlib/bugtracker.py           bugtracker.py-20070410073305-vu1vu1qosjurg8kb-1
  bzrlib/tests/test_bugtracker.py test_bugtracker.py-20070410073305-vu1vu1qosjurg8kb-2
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/help_topics.py          help_topics.py-20060920210027-rnim90q9e0bwxvy4-1
  bzrlib/option.py               option.py-20051014052914-661fb36e76e7362f
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/blackbox/test_commit.py test_commit.py-20060212094538-ae88fc861d969db0
  bzrlib/tests/test_errors.py    test_errors.py-20060210110251-41aba2deddf936a8
  bzrlib/tests/test_options.py   testoptions.py-20051014093702-96457cfc86319a8f
  doc/configuration.txt          configuration.txt-20060314161707-868350809502af01
    ------------------------------------------------------------
    revno: 2376.4.43
    merged: jml at canonical.com-20070423065558-wbkypy8qlhzrxugz
    parent: jml at canonical.com-20070423055929-chgdr92da63lhcri
    parent: pqm at pqm.ubuntu.com-20070423041629-v7moohhajrjbc2xw
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: bug-support
    timestamp: Mon 2007-04-23 16:55:58 +1000
    message:
      Merge from mainline, resolving conflict in help_topics
    ------------------------------------------------------------
    revno: 2376.4.42
    merged: jml at canonical.com-20070423055929-chgdr92da63lhcri
    parent: jml at canonical.com-20070423052447-q9qv5n7mhkdsqj46
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 15:59:29 +1000
    message:
      Parametrize URLParametrizedIntegerBugTracker even further so we don't need to
      subclass.
    ------------------------------------------------------------
    revno: 2376.4.41
    merged: jml at canonical.com-20070423052447-q9qv5n7mhkdsqj46
    parent: jml at canonical.com-20070423052217-nz0zuaesa0dcbvy2
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 15:24:47 +1000
    message:
      Update UniqueIntegerBugTracker docstring for new API
    ------------------------------------------------------------
    revno: 2376.4.40
    merged: jml at canonical.com-20070423052217-nz0zuaesa0dcbvy2
    parent: jml at canonical.com-20070423013035-zuqiamuro8h1hba9
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 15:22:17 +1000
    message:
      Redo the hierarchy of bug trackers to reduce duplication.
    ------------------------------------------------------------
    revno: 2376.4.39
    merged: jml at canonical.com-20070423013035-zuqiamuro8h1hba9
    parent: jml at canonical.com-20070422230056-mfu7bo3ydrbbag7p
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 11:30:35 +1000
    message:
      Can also set the bug config options in branch.conf
    ------------------------------------------------------------
    revno: 2376.4.38
    merged: jml at canonical.com-20070422230056-mfu7bo3ydrbbag7p
    parent: jml at canonical.com-20070422224848-cm8v6p5wmpx1psco
    parent: pqm at pqm.ubuntu.com-20070421151139-5wau2ukbpx5z1hv2
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 09:00:56 +1000
    message:
      Merge bzr.dev, resolving conflicts in error code.
    ------------------------------------------------------------
    revno: 2376.4.37
    merged: jml at canonical.com-20070422224848-cm8v6p5wmpx1psco
    parent: jml at canonical.com-20070422224519-wixpd8wnwxw70641
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 08:48:48 +1000
    message:
      Mention API additions in NEWS.
    ------------------------------------------------------------
    revno: 2376.4.36
    merged: jml at canonical.com-20070422224519-wixpd8wnwxw70641
    parent: jml at canonical.com-20070422222300-7xlpc7u275pvh2iw
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 08:45:19 +1000
    message:
      Provide really basic help topic for our bug tracker support.
    ------------------------------------------------------------
    revno: 2376.4.35
    merged: jml at canonical.com-20070422222300-7xlpc7u275pvh2iw
    parent: jml at canonical.com-20070422222226-pkjyasbt28k1aug7
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 08:23:00 +1000
    message:
      Include configuration documentation for tracker config options.
    ------------------------------------------------------------
    revno: 2376.4.34
    merged: jml at canonical.com-20070422222226-pkjyasbt28k1aug7
    parent: jml at canonical.com-20070420041308-93e07kmw1s9t1ceg
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Mon 2007-04-23 08:22:26 +1000
    message:
      Remove spurious quote mark in docstring.
    ------------------------------------------------------------
    revno: 2376.4.33
    merged: jml at canonical.com-20070420041308-93e07kmw1s9t1ceg
    parent: jml at canonical.com-20070420041143-td2407zcd2xygk70
    parent: pqm at pqm.ubuntu.com-20070419224637-jvlshh6kibtj43a5
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 14:13:08 +1000
    message:
      Merge from bzr.dev
    ------------------------------------------------------------
    revno: 2376.4.32
    merged: jml at canonical.com-20070420041143-td2407zcd2xygk70
    parent: jml at canonical.com-20070420035328-i3hfywon1imyw0q7
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 14:11:43 +1000
    message:
      Basic documentation for --fixes option
    ------------------------------------------------------------
    revno: 2376.4.31
    merged: jml at canonical.com-20070420035328-i3hfywon1imyw0q7
    parent: jml at canonical.com-20070420034009-ii53ulxxwj1jnppt
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 13:53:28 +1000
    message:
      Whitespace normalization and use squid as an example rather than apache.
    ------------------------------------------------------------
    revno: 2376.4.30
    merged: jml at canonical.com-20070420034009-ii53ulxxwj1jnppt
    parent: jml at canonical.com-20070420031943-ufx3jtikoxp0mlpb
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 13:40:09 +1000
    message:
      Support for Bugzilla bug trackers.
    ------------------------------------------------------------
    revno: 2376.4.29
    merged: jml at canonical.com-20070420031943-ufx3jtikoxp0mlpb
    parent: jml at canonical.com-20070420031049-kwt6hkq8qdlgtm3z
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 13:19:43 +1000
    message:
      Tests for builtin trackers.
    ------------------------------------------------------------
    revno: 2376.4.28
    merged: jml at canonical.com-20070420031049-kwt6hkq8qdlgtm3z
    parent: jml at canonical.com-20070420022738-07tr3bqahw14ftdh
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 13:10:49 +1000
    message:
      Focus the tests better and clean up some dodgy bits in UnknownBugTrackerAbbreviation
    ------------------------------------------------------------
    revno: 2376.4.27
    merged: jml at canonical.com-20070420022738-07tr3bqahw14ftdh
    parent: jml at canonical.com-20070420022157-ampptjzryc2fju7t
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 12:27:38 +1000
    message:
      Include branch information in UnknownBugTrackerAbbreviation
    ------------------------------------------------------------
    revno: 2376.4.26
    merged: jml at canonical.com-20070420022157-ampptjzryc2fju7t
    parent: jml at canonical.com-20070420021008-eqpr2ta14upnb0ne
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 12:21:57 +1000
    message:
      Tests for MalformedBugIdentifier and new error UnknownBugTrackerAbbreviation.
    ------------------------------------------------------------
    revno: 2376.4.25
    merged: jml at canonical.com-20070420021008-eqpr2ta14upnb0ne
    parent: jml at canonical.com-20070420015214-y9ufmdqpwd75zog8
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 12:10:08 +1000
    message:
      Make singleton bug tracker thing work via instances. 
    ------------------------------------------------------------
    revno: 2376.4.24
    merged: jml at canonical.com-20070420015214-y9ufmdqpwd75zog8
    parent: jml at canonical.com-20070420005627-p6rrsxowxkf7w5sy
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 11:52:14 +1000
    message:
      Make the Tracker class attribute 'abbrevation', not 'abbreviated_bugtracker_name'
    ------------------------------------------------------------
    revno: 2376.4.23
    merged: jml at canonical.com-20070420005627-p6rrsxowxkf7w5sy
    parent: jml at canonical.com-20070417075942-1vfzpwjf8rvbty1f
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-20 10:56:27 +1000
    message:
      Change 'tag' to 'abbreviated_tracker_name'
    ------------------------------------------------------------
    revno: 2376.4.22
    merged: jml at canonical.com-20070417075942-1vfzpwjf8rvbty1f
    parent: jml at canonical.com-20070417073819-m0r79gdukrq2j8kv
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Tue 2007-04-17 17:59:42 +1000
    message:
      Variety of whitespace cleanups, tightening of tests and docstring changes in
      response to review of bug support.
    ------------------------------------------------------------
    revno: 2376.4.21
    merged: jml at canonical.com-20070417073819-m0r79gdukrq2j8kv
    parent: jml at canonical.com-20070413072937-ndsxwg5svbk2542f
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Tue 2007-04-17 17:38:19 +1000
    message:
      Change the bugs separator to \n from ,
    ------------------------------------------------------------
    revno: 2376.4.20
    merged: jml at canonical.com-20070413072937-ndsxwg5svbk2542f
    parent: jml at canonical.com-20070413071912-vkcaqm87lduwlfs7
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 17:29:37 +1000
    message:
      Direct tests for UniqueBugTracker and UniqueIntegerBugTracker
    ------------------------------------------------------------
    revno: 2376.4.19
    merged: jml at canonical.com-20070413071912-vkcaqm87lduwlfs7
    parent: jml at canonical.com-20070413065621-hv9tv4ws25remckj
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 17:19:12 +1000
    message:
      Rename SimpleBugTracker to UniqueBugTracker
    ------------------------------------------------------------
    revno: 2376.4.18
    merged: jml at canonical.com-20070413065621-hv9tv4ws25remckj
    parent: jml at canonical.com-20070413064225-cmuz00itroirlro4
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 16:56:21 +1000
    message:
      Store all bug fix URLs in a single property.
    ------------------------------------------------------------
    revno: 2376.4.17
    merged: jml at canonical.com-20070413064225-cmuz00itroirlro4
    parent: jml at canonical.com-20070413062439-tn32nfugq13jg6kg
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 16:42:25 +1000
    message:
      Use urlutils.join instead of ugly, buggy, string mashing
    ------------------------------------------------------------
    revno: 2376.4.16
    merged: jml at canonical.com-20070413062439-tn32nfugq13jg6kg
    parent: jml at canonical.com-20070413061233-v14vlu2n6k1rxjgz
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 16:24:39 +1000
    message:
      "bug tracker" is two words, so use BugTracker in class names, not 'Bugtracker'
    ------------------------------------------------------------
    revno: 2376.4.15
    merged: jml at canonical.com-20070413061233-v14vlu2n6k1rxjgz
    parent: jml at canonical.com-20070413061123-r2vzcu0mpqcyaret
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 16:12:33 +1000
    message:
      Whitespace cleanup
    ------------------------------------------------------------
    revno: 2376.4.14
    merged: jml at canonical.com-20070413061123-r2vzcu0mpqcyaret
    parent: jml at canonical.com-20070413051007-cq318kt5uj59k1ty
    parent: jw+debian at jameswestby.net-20070412205830-77i29wqt0002bbxj
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 16:11:23 +1000
    message:
      Merge James Westby's refactoring
        ------------------------------------------------------------
        revno: 2376.4.8.1.1
        merged: jw+debian at jameswestby.net-20070412205830-77i29wqt0002bbxj
        parent: jml at canonical.com-20070411042318-y7tqzml42jkh1ux2
        committer: James Westby <jw+debian at jameswestby.net>
        branch nick: bzr.dev.bug
        timestamp: Thu 2007-04-12 21:58:30 +0100
        message:
          Add a superclass for easy bug trackers. Also add bugs.debian.org as deb:
    ------------------------------------------------------------
    revno: 2376.4.13
    merged: jml at canonical.com-20070413051007-cq318kt5uj59k1ty
    parent: jml at canonical.com-20070413045808-fx86n9ctjcbf4hvq
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 15:10:07 +1000
    message:
      Some stylistic cleanups
    ------------------------------------------------------------
    revno: 2376.4.12
    merged: jml at canonical.com-20070413045808-fx86n9ctjcbf4hvq
    parent: jml at canonical.com-20070413035248-l5lqpwr1t0yzp872
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 14:58:08 +1000
    message:
      Update NEWS file.
      Use non-lazy import
      Use run_bzr for running bzr in fixes tests.
    ------------------------------------------------------------
    revno: 2376.4.11
    merged: jml at canonical.com-20070413035248-l5lqpwr1t0yzp872
    parent: jml at canonical.com-20070413035020-z4gdg5sgmd5szc3o
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 13:52:48 +1000
    message:
      Provide a way of resetting list options (specifying '-' as the argument)
    ------------------------------------------------------------
    revno: 2376.4.10
    merged: jml at canonical.com-20070413035020-z4gdg5sgmd5szc3o
    parent: jml at canonical.com-20070413034711-pn129rtch4usvmdk
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 13:50:20 +1000
    message:
      Oops. Update tests to account for changed options to commit.
    ------------------------------------------------------------
    revno: 2376.4.9
    merged: jml at canonical.com-20070413034711-pn129rtch4usvmdk
    parent: jml at canonical.com-20070411042318-y7tqzml42jkh1ux2
    parent: pqm at pqm.ubuntu.com-20070412150356-jeie6iap22sae8xf
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: lp-bugs
    timestamp: Fri 2007-04-13 13:47:11 +1000
    message:
      Merge from mainline.
    ------------------------------------------------------------
    revno: 2376.4.8
    merged: jml at canonical.com-20070411042318-y7tqzml42jkh1ux2
    parent: jml at canonical.com-20070411035653-himmtk357n8d3fbv
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Wed 2007-04-11 14:23:18 +1000
    message:
      Import sub-module, not member, following advice in HACKING
    ------------------------------------------------------------
    revno: 2376.4.7
    merged: jml at canonical.com-20070411035653-himmtk357n8d3fbv
    parent: jml at canonical.com-20070411002703-2jbq0152stafryqg
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Wed 2007-04-11 13:56:53 +1000
    message:
      - Add docstrings to tests.
      - Make the 'commit' command use the bugtracker infrastructure.
      - Update the blackbox tests
      - Move the 'is this a valid bug id' logic to the tracker
    ------------------------------------------------------------
    revno: 2376.4.6
    merged: jml at canonical.com-20070411002703-2jbq0152stafryqg
    parent: jml at canonical.com-20070410232958-2pl3j2qwujq5yjy9
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Wed 2007-04-11 10:27:03 +1000
    message:
      Basic docstrings for bugtracker.py
    ------------------------------------------------------------
    revno: 2376.4.5
    merged: jml at canonical.com-20070410232958-2pl3j2qwujq5yjy9
    parent: jml at canonical.com-20070410073307-3nrm81zu32wz9qbs
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Wed 2007-04-11 09:29:58 +1000
    message:
      Rename get_by_tag to get_tracker, to be more accurate.
    ------------------------------------------------------------
    revno: 2376.4.4
    merged: jml at canonical.com-20070410073307-3nrm81zu32wz9qbs
    parent: jml at canonical.com-20070410012227-k5ixuw4mik7z9kfh
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Tue 2007-04-10 17:33:07 +1000
    message:
      Beginnings of generic bug-tracker plugin system.
    ------------------------------------------------------------
    revno: 2376.4.3
    merged: jml at canonical.com-20070410012227-k5ixuw4mik7z9kfh
    parent: jml at canonical.com-20070402072630-0bjkptemr6wh50s3
    parent: pqm at pqm.ubuntu.com-20070405073143-8fa894c829ab5e50
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Tue 2007-04-10 11:22:27 +1000
    message:
      Merge RF
    ------------------------------------------------------------
    revno: 2376.4.2
    merged: jml at canonical.com-20070402072630-0bjkptemr6wh50s3
    parent: jml at canonical.com-20070328065202-1r825jxnabsy31ak
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Mon 2007-04-02 17:26:30 +1000
    message:
      More sophisticated error handling for --fixes option
    ------------------------------------------------------------
    revno: 2376.4.1
    merged: jml at canonical.com-20070328065202-1r825jxnabsy31ak
    parent: pqm at pqm.ubuntu.com-20070326073003-37941d0fa5a5a6c4
    committer: jml at canonical.com
    branch nick: lp-bugs
    timestamp: Wed 2007-03-28 16:52:02 +1000
    message:
      Blackbox-driven --fixes option to commit.
=== added file 'bzrlib/bugtracker.py'
--- a/bzrlib/bugtracker.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/bugtracker.py	2007-04-23 05:59:29 +0000
@@ -0,0 +1,173 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import textwrap
+
+from bzrlib import registry, help_topics
+from bzrlib.lazy_import import lazy_import
+lazy_import(globals(), """
+from bzrlib import errors, urlutils
+""")
+
+
+"""Provides a shorthand for referring to bugs on a variety of bug trackers.
+
+'commit --fixes' stores references to bugs as a <bug_url> -> <bug_status>
+mapping in the properties for that revision.
+
+However, it's inconvenient to type out full URLs for bugs on the command line,
+particularly given that many users will be using only a single bug tracker per
+branch.
+
+Thus, this module provides a registry of types of bug tracker (e.g. Launchpad,
+Trac). Given an abbreviated name (e.g. 'lp', 'twisted') and a branch with
+configuration information, these tracker types can return an instance capable
+of converting bug IDs into URLs.
+"""
+
+
+def get_bug_url(abbreviated_bugtracker_name, branch, bug_id):
+    """Return a URL pointing to the canonical web page of the bug identified by
+    'bug_id'.
+    """
+    tracker = tracker_registry.get_tracker(abbreviated_bugtracker_name, branch)
+    return tracker.get_bug_url(bug_id)
+
+
+class TrackerRegistry(registry.Registry):
+    """Registry of bug tracker types."""
+
+    def get_tracker(self, abbreviated_bugtracker_name, branch):
+        """Return the first registered tracker that understands
+        'abbreviated_bugtracker_name'.
+
+        If no such tracker is found, raise KeyError.
+        """
+        for tracker_name in self.keys():
+            tracker_type = self.get(tracker_name)
+            tracker = tracker_type.get(abbreviated_bugtracker_name, branch)
+            if tracker is not None:
+                return tracker
+        raise errors.UnknownBugTrackerAbbreviation(abbreviated_bugtracker_name,
+                                                   branch)
+
+    def help_topic(self, topic):
+        return textwrap.dedent("""\
+        Bazaar provides the ability to store information about bugs being fixed
+        as metadata on a revision.
+
+        For each bug marked as fixed, an entry is included in the 'bugs'
+        revision property stating '<url> <status>'.
+        """)
+
+
+tracker_registry = TrackerRegistry()
+"""Registry of bug trackers."""
+
+
+class BugTracker(object):
+    """Base class for bug trackers."""
+
+    def check_bug_id(self, bug_id):
+        """Check that the bug_id is valid.
+
+        The base implementation assumes that all bug_ids are valid.
+        """
+
+    def get_bug_url(self, bug_id):
+        """Return the URL for bug_id. Raise an error if bug ID is malformed."""
+        self.check_bug_id(bug_id)
+        return self._get_bug_url(bug_id)
+
+    def _get_bug_url(self, bug_id):
+        """Given a validated bug_id, return the bug's web page's URL."""
+
+
+class IntegerBugTracker(BugTracker):
+    """A bug tracker that only allows integer bug IDs."""
+
+    def check_bug_id(self, bug_id):
+        try:
+            int(bug_id)
+        except ValueError:
+            raise errors.MalformedBugIdentifier(bug_id, "Must be an integer")
+
+
+class UniqueIntegerBugTracker(IntegerBugTracker):
+    """A style of bug tracker that exists in one place only, such as Launchpad.
+
+    If you have one of these trackers then register an instance passing in an
+    abbreviated name for the bug tracker and a base URL.
+    """
+
+    def __init__(self, abbreviated_bugtracker_name, base_url):
+        self.abbreviation = abbreviated_bugtracker_name
+        self.base_url = base_url
+
+    def get(self, abbreviated_bugtracker_name, branch):
+        """Returns the tracker if the abbreviation matches. Returns None
+        otherwise."""
+        if abbreviated_bugtracker_name != self.abbreviation:
+            return None
+        return self
+
+    def _get_bug_url(self, bug_id):
+        """Return the URL for bug_id."""
+        return urlutils.join(self.base_url, bug_id)
+
+
+tracker_registry.register(
+    'launchpad', UniqueIntegerBugTracker('lp', 'https://launchpad.net/bugs/'))
+
+
+tracker_registry.register(
+    'debian', UniqueIntegerBugTracker('deb', 'http://bugs.debian.org/'))
+
+
+class URLParametrizedIntegerBugTracker(IntegerBugTracker):
+    """A type of bug tracker that can be found on a variety of different sites,
+    and thus needs to have the base URL configured.
+
+    Looks for a config setting in the form '<type_name>_<abbreviation>_url'.
+    `type_name` is the name of the type of tracker (e.g. 'bugzilla' or 'trac')
+    and `abbreviation` is a short name for the particular instance (e.g.
+    'squid' or 'apache').
+    """
+
+    def get(self, abbreviation, branch):
+        config = branch.get_config()
+        url = config.get_user_option(
+            "%s_%s_url" % (self.type_name, abbreviation))
+        if url is None:
+            return None
+        self._base_url = url
+        return self
+
+    def __init__(self, type_name, bug_area):
+        self.type_name = type_name
+        self._bug_area = bug_area
+
+    def _get_bug_url(self, bug_id):
+        """Return a URL for a bug on this Trac instance."""
+        return urlutils.join(self._base_url, self._bug_area) + str(bug_id)
+
+
+tracker_registry.register(
+    'trac', URLParametrizedIntegerBugTracker('trac', 'ticket/'))
+
+tracker_registry.register(
+    'bugzilla',
+    URLParametrizedIntegerBugTracker('bugzilla', 'show_bug.cgi?id='))

=== added file 'bzrlib/tests/test_bugtracker.py'
--- a/bzrlib/tests/test_bugtracker.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/test_bugtracker.py	2007-04-23 05:59:29 +0000
@@ -0,0 +1,185 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+from bzrlib import bugtracker, errors, urlutils
+from bzrlib.tests import TestCaseWithMemoryTransport
+
+
+class TestGetBugURL(TestCaseWithMemoryTransport):
+    """Tests for bugtracker.get_bug_url"""
+
+    class TransientTracker(object):
+        """An transient tracker used for testing."""
+
+        @classmethod
+        def get(klass, abbreviation, branch):
+            klass.log.append(('get', abbreviation, branch))
+            if abbreviation != 'transient':
+                return None
+            return klass()
+
+        def get_bug_url(self, bug_id):
+            self.log.append(('get_bug_url', bug_id))
+            return "http://bugs.com/%s" % bug_id
+
+    def setUp(self):
+        TestCaseWithMemoryTransport.setUp(self)
+        self.tracker_type = TestGetBugURL.TransientTracker
+        self.tracker_type.log = []
+        bugtracker.tracker_registry.register('transient', self.tracker_type)
+        self.addCleanup(lambda:
+                        bugtracker.tracker_registry.remove('transient'))
+
+    def test_get_bug_url_for_transient_tracker(self):
+        branch = self.make_branch('some_branch')
+        self.assertEqual('http://bugs.com/1234',
+                         bugtracker.get_bug_url('transient', branch, '1234'))
+        self.assertEqual(
+            [('get', 'transient', branch), ('get_bug_url', '1234')],
+            self.tracker_type.log)
+
+    def test_unrecognized_abbreviation_raises_error(self):
+        """If the abbreviation is unrecognized, then raise an error."""
+        branch = self.make_branch('some_branch')
+        self.assertRaises(errors.UnknownBugTrackerAbbreviation,
+                          bugtracker.get_bug_url, 'xxx', branch, '1234')
+        self.assertEqual([('get', 'xxx', branch)], self.tracker_type.log)
+
+
+class TestBuiltinTrackers(TestCaseWithMemoryTransport):
+    """Test that the builtin trackers are registered and return sane URLs."""
+
+    def test_launchpad_registered(self):
+        """The Launchpad bug tracker should be registered by default and
+        generate Launchpad bug page URLs.
+        """
+        branch = self.make_branch('some_branch')
+        tracker = bugtracker.tracker_registry.get_tracker('lp', branch)
+        self.assertEqual('https://launchpad.net/bugs/1234',
+                         tracker.get_bug_url('1234'))
+
+    def test_debian_registered(self):
+        """The Debian bug tracker should be registered by default and generate
+        bugs.debian.org bug page URLs.
+        """
+        branch = self.make_branch('some_branch')
+        tracker = bugtracker.tracker_registry.get_tracker('deb', branch)
+        self.assertEqual('http://bugs.debian.org/1234',
+                         tracker.get_bug_url('1234'))
+
+    def test_trac_registered(self):
+        """The Trac bug tracker should be registered by default and generate
+        Trac bug page URLs when the appropriate configuration is present.
+        """
+        branch = self.make_branch('some_branch')
+        config = branch.get_config()
+        config.set_user_option('trac_foo_url', 'http://bugs.com/trac')
+        tracker = bugtracker.tracker_registry.get_tracker('foo', branch)
+        self.assertEqual('http://bugs.com/trac/ticket/1234',
+                         tracker.get_bug_url('1234'))
+
+    def test_bugzilla_registered(self):
+        """The Bugzilla bug tracker should be registered by default and
+        generate Bugzilla bug page URLs when the appropriate configuration is
+        present.
+        """
+        branch = self.make_branch('some_branch')
+        config = branch.get_config()
+        config.set_user_option('bugzilla_foo_url', 'http://bugs.com')
+        tracker = bugtracker.tracker_registry.get_tracker('foo', branch)
+        self.assertEqual('http://bugs.com/show_bug.cgi?id=1234',
+                         tracker.get_bug_url('1234'))
+
+
+class TestUniqueIntegerBugTracker(TestCaseWithMemoryTransport):
+
+    def test_joins_id_to_base_url(self):
+        """The URL of a bug is the base URL joined to the identifier."""
+        tracker = bugtracker.UniqueIntegerBugTracker('xxx', 'http://bugs.com')
+        self.assertEqual('http://bugs.com/1234', tracker.get_bug_url('1234'))
+
+    def test_returns_tracker_if_abbreviation_matches(self):
+        """The get() method should return an instance of the tracker if the
+        given abbreviation matches the tracker's abbreviated name.
+        """
+        tracker = bugtracker.UniqueIntegerBugTracker('xxx', 'http://bugs.com')
+        branch = self.make_branch('some_branch')
+        self.assertIs(tracker, tracker.get('xxx', branch))
+
+    def test_returns_none_if_abbreviation_doesnt_match(self):
+        """The get() method should return None if the given abbreviated name
+        doesn't match the tracker's abbreviation.
+        """
+        tracker = bugtracker.UniqueIntegerBugTracker('xxx', 'http://bugs.com')
+        branch = self.make_branch('some_branch')
+        self.assertIs(None, tracker.get('yyy', branch))
+
+    def test_doesnt_consult_branch(self):
+        """A UniqueIntegerBugTracker shouldn't consult the branch for tracker
+        information.
+        """
+        tracker = bugtracker.UniqueIntegerBugTracker('xxx', 'http://bugs.com')
+        self.assertIs(tracker, tracker.get('xxx', None))
+        self.assertIs(None, tracker.get('yyy', None))
+
+    def test_check_bug_id_only_accepts_integers(self):
+        """A UniqueIntegerBugTracker accepts integers as bug IDs."""
+        tracker = bugtracker.UniqueIntegerBugTracker('xxx', 'http://bugs.com')
+        tracker.check_bug_id('1234')
+
+    def test_check_bug_id_doesnt_accept_non_integers(self):
+        """A UniqueIntegerBugTracker rejects non-integers as bug IDs."""
+        tracker = bugtracker.UniqueIntegerBugTracker('xxx', 'http://bugs.com')
+        self.assertRaises(
+            errors.MalformedBugIdentifier, tracker.check_bug_id, 'red')
+
+
+class TestURLParametrizedIntegerBugTracker(TestCaseWithMemoryTransport):
+    """Tests for TracTracker."""
+
+    def setUp(self):
+        TestCaseWithMemoryTransport.setUp(self)
+        self.url = 'http://twistedmatrix.com/trac'
+        self.tracker = bugtracker.URLParametrizedIntegerBugTracker('some',
+                                                                   'ticket/')
+
+    def test_get_with_unsupported_tag(self):
+        """If asked for an unrecognized or unconfigured tag, return None."""
+        branch = self.make_branch('some_branch')
+        self.assertEqual(None, self.tracker.get('lp', branch))
+        self.assertEqual(None, self.tracker.get('twisted', branch))
+
+    def test_get_with_supported_tag(self):
+        """If asked for a valid tag, return a tracker instance that can map bug
+        IDs to <base_url>/<bug_area> + <bug_id>."""
+        bugtracker.tracker_registry.register('some', self.tracker)
+        self.addCleanup(lambda: bugtracker.tracker_registry.remove('some'))
+
+        branch = self.make_branch('some_branch')
+        config = branch.get_config()
+        config.set_user_option('some_twisted_url', self.url)
+        tracker = self.tracker.get('twisted', branch)
+        self.assertEqual(
+            urlutils.join(self.url, 'ticket/') + '1234',
+            tracker.get_bug_url('1234'))
+
+    def test_get_bug_url_for_bad_bug(self):
+        """When given a bug identifier that is invalid for Trac, get_bug_url
+        should raise an error.
+        """
+        self.assertRaises(
+            errors.MalformedBugIdentifier, self.tracker.get_bug_url, 'bad')

=== modified file 'NEWS'
--- a/NEWS	2007-04-23 07:08:09 +0000
+++ b/NEWS	2007-04-23 07:50:15 +0000
@@ -5,6 +5,14 @@
     * Merge directives can now be supplied as input to `merge` and `pull`,
       like bundles can.  (Aaron Bentley)
 
+    * New option ``--fixes`` to commit, which stores bug fixing annotations as
+      revision properties. Built-in support for Launchpad, Debian, Trac and
+      Bugzilla bug trackers. (Jonathan Lange, James Henstridge, Robert Collins)
+
+    * New API, ``bzrlib.bugtracker.tracker_registry``, for adding support for
+      other bug trackers to ``fixes``. (Jonathan Lange, James Henstridge,
+      Robert Collins)
+
     * ``selftest`` has new short options ``-f`` and ``-1``.  (Martin
       Pool)
 

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2007-04-23 04:38:50 +0000
+++ b/bzrlib/builtins.py	2007-04-23 07:50:15 +0000
@@ -31,6 +31,7 @@
 import bzrlib
 from bzrlib import (
     branch,
+    bugtracker,
     bundle,
     bzrdir,
     delta,
@@ -59,7 +60,7 @@
 """)
 
 from bzrlib.commands import Command, display_command
-from bzrlib.option import Option, RegistryOption
+from bzrlib.option import ListOption, Option, RegistryOption
 from bzrlib.progress import DummyProgress, ProgressPhase
 from bzrlib.trace import mutter, note, log_error, warning, is_quiet, info
 
@@ -2087,7 +2088,7 @@
 
     # XXX: verbose currently does nothing
 
-    _see_also = ['uncommit']
+    _see_also = ['bugs', 'uncommit']
     takes_args = ['selected*']
     takes_options = ['message', 'verbose', 
                      Option('unchanged',
@@ -2099,6 +2100,9 @@
                      Option('strict',
                             help="refuse to commit if there are unknown "
                             "files in the working tree."),
+                     ListOption('fixes', type=str,
+                                help="mark a bug as being fixed by this "
+                                     "revision."),
                      Option('local',
                             help="perform a local only commit in a bound "
                                  "branch. Such commits are not pushed to "
@@ -2108,8 +2112,30 @@
                      ]
     aliases = ['ci', 'checkin']
 
+    def _get_bug_fix_properties(self, fixes, branch):
+        properties = []
+        # Configure the properties for bug fixing attributes.
+        for fixed_bug in fixes:
+            tokens = fixed_bug.split(':')
+            if len(tokens) != 2:
+                raise errors.BzrCommandError(
+                    "Invalid bug %s. Must be in the form of 'tag:id'. "
+                    "Commit refused." % fixed_bug)
+            tag, bug_id = tokens
+            try:
+                bug_url = bugtracker.get_bug_url(tag, branch, bug_id)
+            except errors.UnknownBugTrackerAbbreviation:
+                raise errors.BzrCommandError(
+                    'Unrecognized bug %s. Commit refused.' % fixed_bug)
+            except errors.MalformedBugIdentifier:
+                raise errors.BzrCommandError(
+                    "Invalid bug identifier for %s. Commit refused."
+                    % fixed_bug)
+            properties.append('%s fixed' % bug_url)
+        return '\n'.join(properties)
+
     def run(self, message=None, file=None, verbose=True, selected_list=None,
-            unchanged=False, strict=False, local=False):
+            unchanged=False, strict=False, local=False, fixes=None):
         from bzrlib.commit import (NullCommitReporter, ReportCommitToLog)
         from bzrlib.errors import (PointlessCommit, ConflictsInTree,
                 StrictCommitFailed)
@@ -2121,6 +2147,9 @@
 
         # TODO: do more checks that the commit will succeed before 
         # spending the user's valuable time typing a commit message.
+
+        properties = {}
+
         tree, selected_list = tree_files(selected_list)
         if selected_list == ['']:
             # workaround - commit of root of tree should be exactly the same
@@ -2128,6 +2157,8 @@
             # selected-file merge commit is not done yet
             selected_list = []
 
+        properties['bugs'] = self._get_bug_fix_properties(fixes, tree.branch)
+
         if local and not tree.branch.get_bound_location():
             raise errors.LocalRequiresBoundBranch()
 
@@ -2149,7 +2180,7 @@
             if my_message == "":
                 raise errors.BzrCommandError("empty commit message specified")
             return my_message
-        
+
         if verbose:
             reporter = ReportCommitToLog()
         else:
@@ -2159,7 +2190,7 @@
             tree.commit(message_callback=get_message,
                         specific_files=selected_list,
                         allow_pointless=unchanged, strict=strict, local=local,
-                        reporter=reporter)
+                        reporter=reporter, revprops=properties)
         except PointlessCommit:
             # FIXME: This should really happen before the file is read in;
             # perhaps prepare the commit; get the message; then actually commit

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2007-04-23 00:28:58 +0000
+++ b/bzrlib/errors.py	2007-04-23 06:55:58 +0000
@@ -2087,10 +2087,28 @@
         self.tag_name = tag_name
 
 
+class MalformedBugIdentifier(BzrError):
+
+    _fmt = "Did not understand bug identifier %(bug_id)s: %(reason)s"
+
+    def __init__(self, bug_id, reason):
+        self.bug_id = bug_id
+        self.reason = reason
+
+
+class UnknownBugTrackerAbbreviation(BzrError):
+
+    _fmt = ("Cannot find registered bug tracker called %(abbreviation)s "
+            "on %(branch)s")
+
+    def __init__(self, abbreviation, branch):
+        self.abbreviation = abbreviation
+        self.branch = branch
+
+
 class UnexpectedSmartServerResponse(BzrError):
 
     _fmt = "Could not understand response from smart server: %(response_tuple)r"
 
     def __init__(self, response_tuple):
         self.response_tuple = response_tuple
-

=== modified file 'bzrlib/help_topics.py'
--- a/bzrlib/help_topics.py	2007-04-23 02:58:30 +0000
+++ b/bzrlib/help_topics.py	2007-04-23 06:55:58 +0000
@@ -81,7 +81,7 @@
 
 
 def _help_on_revisionspec(name):
-    """"Write the summary help for all documented topics to outfile."""
+    """Write the summary help for all documented topics to outfile."""
     import bzrlib.revisionspec
 
     out = []
@@ -291,6 +291,10 @@
                         'Information on what a checkout is')
 topic_registry.register('urlspec', _help_on_transport,
                         "Supported transport protocols")
+def get_bugs_topic(topic):
+    from bzrlib import bugtracker
+    return bugtracker.tracker_registry.help_topic(topic)
+topic_registry.register('bugs', get_bugs_topic, 'Bug tracker support')
 
 
 class HelpTopicIndex(object):

=== modified file 'bzrlib/option.py'
--- a/bzrlib/option.py	2007-03-14 04:43:45 +0000
+++ b/bzrlib/option.py	2007-04-17 07:59:42 +0000
@@ -205,6 +205,36 @@
         yield self.name, self.short_name(), argname, self.help
 
 
+class ListOption(Option):
+    """Option used to provide a list of values.
+
+    On the command line, arguments are specified by a repeated use of the
+    option. '-' is a special argument that resets the list. For example,
+      --foo=a --foo=b
+    sets the value of the 'foo' option to ['a', 'b'], and
+      --foo=a --foo=b --foo=- --foo=c
+    sets the value of the 'foo' option to ['c'].
+    """
+
+    def add_option(self, parser, short_name):
+        """Add this option to an Optparse parser."""
+        option_strings = ['--%s' % self.name]
+        if short_name is not None:
+            option_strings.append('-%s' % short_name)
+        parser.add_option(action='callback',
+                          callback=self._optparse_callback,
+                          type='string', metavar=self.argname.upper(),
+                          help=self.help, default=[],
+                          *option_strings)
+
+    def _optparse_callback(self, option, opt, value, parser):
+        values = getattr(parser.values, self.name)
+        if value == '-':
+            del values[:]
+        else:
+            values.append(self.type(value))
+
+
 class RegistryOption(Option):
     """Option based on a registry
 

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2007-04-23 03:41:48 +0000
+++ b/bzrlib/tests/__init__.py	2007-04-23 06:55:58 +0000
@@ -2154,6 +2154,7 @@
                    'bzrlib.tests.test_atomicfile',
                    'bzrlib.tests.test_bad_files',
                    'bzrlib.tests.test_branch',
+                   'bzrlib.tests.test_bugtracker',
                    'bzrlib.tests.test_bundle',
                    'bzrlib.tests.test_bzrdir',
                    'bzrlib.tests.test_cache_utf8',

=== modified file 'bzrlib/tests/blackbox/test_commit.py'
--- a/bzrlib/tests/blackbox/test_commit.py	2007-03-03 17:17:53 +0000
+++ b/bzrlib/tests/blackbox/test_commit.py	2007-04-17 07:59:42 +0000
@@ -358,3 +358,103 @@
         # --no-strict overrides --strict
         self.run_bzr('commit', '--strict', '-m', 'add b', '--no-strict',
                      working_dir='tree')
+
+    def test_fixes_bug_output(self):
+        """commit --fixes=lp:23452 succeeds without output."""
+        tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/hello.txt'])
+        tree.add('hello.txt')
+        output, err = self.run_bzr(
+            'commit', '-m', 'hello', '--fixes=lp:23452', 'tree/hello.txt')
+        self.assertEqual('', output)
+        self.assertEqual('added hello.txt\nCommitted revision 1.\n', err)
+
+    def test_fixes_bug_sets_property(self):
+        """commit --fixes=lp:234 sets the lp:234 revprop to 'fixed'."""
+        tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/hello.txt'])
+        tree.add('hello.txt')
+        self.run_bzr(
+            'commit', '-m', 'hello', '--fixes=lp:234', 'tree/hello.txt')
+
+        # Get the revision properties, ignoring the branch-nick property, which
+        # we don't care about for this test.
+        last_rev = tree.branch.repository.get_revision(tree.last_revision())
+        properties = dict(last_rev.properties)
+        del properties['branch-nick']
+
+        self.assertEqual({'bugs': 'https://launchpad.net/bugs/234 fixed'},
+                         properties)
+
+    def test_fixes_multiple_bugs_sets_properties(self):
+        """--fixes can be used more than once to show that bugs are fixed."""
+        tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/hello.txt'])
+        tree.add('hello.txt')
+        self.run_bzr(
+            'commit', '-m', 'hello', '--fixes=lp:123', '--fixes=lp:235',
+            'tree/hello.txt')
+
+        # Get the revision properties, ignoring the branch-nick property, which
+        # we don't care about for this test.
+        last_rev = tree.branch.repository.get_revision(tree.last_revision())
+        properties = dict(last_rev.properties)
+        del properties['branch-nick']
+
+        self.assertEqual(
+            {'bugs': 'https://launchpad.net/bugs/123 fixed\n'
+                     'https://launchpad.net/bugs/235 fixed'},
+            properties)
+
+    def test_fixes_bug_with_alternate_trackers(self):
+        """--fixes can be used on a properly configured branch to mark bug
+        fixes on multiple trackers.
+        """
+        tree = self.make_branch_and_tree('tree')
+        tree.branch.get_config().set_user_option(
+            'trac_twisted_url', 'http://twistedmatrix.com/trac')
+        self.build_tree(['tree/hello.txt'])
+        tree.add('hello.txt')
+        self.run_bzr(
+            'commit', '-m', 'hello', '--fixes=lp:123',
+            '--fixes=twisted:235', 'tree/')
+
+        # Get the revision properties, ignoring the branch-nick property, which
+        # we don't care about for this test.
+        last_rev = tree.branch.repository.get_revision(tree.last_revision())
+        properties = dict(last_rev.properties)
+        del properties['branch-nick']
+
+        self.assertEqual(
+            {'bugs': 'https://launchpad.net/bugs/123 fixed\n'
+                     'http://twistedmatrix.com/trac/ticket/235 fixed'},
+            properties)
+
+    def test_fixes_unknown_bug_prefix(self):
+        tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/hello.txt'])
+        tree.add('hello.txt')
+        self.run_bzr_error(
+            ["Unrecognized bug %s. Commit refused." % 'xxx:123'],
+            'commit', '-m', 'add b', '--fixes=xxx:123',
+            working_dir='tree')
+
+    def test_fixes_invalid_bug_number(self):
+        tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/hello.txt'])
+        tree.add('hello.txt')
+        self.run_bzr_error(
+            ["Invalid bug identifier for %s. Commit refused." % 'lp:orange'],
+            'commit', '-m', 'add b', '--fixes=lp:orange',
+            working_dir='tree')
+
+    def test_fixes_invalid_argument(self):
+        """Raise an appropriate error when the fixes argument isn't tag:id."""
+        tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/hello.txt'])
+        tree.add('hello.txt')
+        self.run_bzr_error(
+            [r"Invalid bug orange. Must be in the form of 'tag:id'\. "
+             r"Commit refused\."],
+            'commit', '-m', 'add b', '--fixes=orange',
+            working_dir='tree')

=== modified file 'bzrlib/tests/test_errors.py'
--- a/bzrlib/tests/test_errors.py	2007-04-23 00:28:58 +0000
+++ b/bzrlib/tests/test_errors.py	2007-04-23 06:55:58 +0000
@@ -244,6 +244,21 @@
             host='ahost', port=444, msg='Unable to connect to ssh host',
             orig_error='my_error')
 
+    def test_malformed_bug_identifier(self):
+        """Test the formatting of MalformedBugIdentifier."""
+        error = errors.MalformedBugIdentifier('bogus', 'reason for bogosity')
+        self.assertEqual(
+            "Did not understand bug identifier bogus: reason for bogosity",
+            str(error))
+
+    def test_unknown_bug_tracker_abbreviation(self):
+        """Test the formatting of UnknownBugTrackerAbbreviation."""
+        branch = self.make_branch('some_branch')
+        error = errors.UnknownBugTrackerAbbreviation('xxx', branch)
+        self.assertEqual(
+            "Cannot find registered bug tracker called xxx on %s" % branch,
+            str(error))
+
     def test_unexpected_smart_server_response(self):
         e = errors.UnexpectedSmartServerResponse(('not yes',))
         self.assertEqual(

=== modified file 'bzrlib/tests/test_options.py'
--- a/bzrlib/tests/test_options.py	2007-03-14 04:43:45 +0000
+++ b/bzrlib/tests/test_options.py	2007-04-17 07:59:42 +0000
@@ -27,10 +27,12 @@
 from bzrlib.tests import TestCase
 from bzrlib.repofmt import knitrepo
 
+
 def parse(options, args):
     parser = option.get_optparser(dict((o.name, o) for o in options))
     return parser.parse_args(args)
 
+
 class OptionTests(TestCase):
     """Command-line option tests"""
 
@@ -38,16 +40,14 @@
         """Option parser"""
         eq = self.assertEquals
         eq(parse_args(cmd_commit(), ['--help']),
-           ([], {'help': True}))
+           ([], {'fixes': [], 'help': True}))
         eq(parse_args(cmd_commit(), ['--message=biter']),
-           ([], {'message': 'biter'}))
-        ## eq(parse_args(cmd_log(),  '-r 500'.split()),
-        ##   ([], {'revision': RevisionSpec_int(500)}))
+           ([], {'fixes': [], 'message': 'biter'}))
 
     def test_no_more_opts(self):
         """Terminated options"""
         self.assertEquals(parse_args(cmd_commit(), ['--', '-file-with-dashes']),
-                          (['-file-with-dashes'], {}))
+                          (['-file-with-dashes'], {'fixes': []}))
 
     def test_option_help(self):
         """Options have help strings."""
@@ -112,7 +112,8 @@
 
     def test_allow_dash(self):
         """Test that we can pass a plain '-' as an argument."""
-        self.assertEqual((['-'], {}), parse_args(cmd_commit(), ['-']))
+        self.assertEqual(
+            (['-'], {'fixes': []}), parse_args(cmd_commit(), ['-']))
 
     def parse(self, options, args):
         parser = option.get_optparser(dict((o.name, o) for o in options))
@@ -231,11 +232,38 @@
                           ('two', None, None, 'two help'),
                           ])
 
-#     >>> parse_args('log -r 500'.split())
-#     (['log'], {'revision': [<RevisionSpec_int 500>]})
-#     >>> parse_args('log -r500..600'.split())
-#     (['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
-#     >>> parse_args('log -vr500..600'.split())
-#     (['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
-#     >>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
-#     (['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
+
+class TestListOptions(TestCase):
+    """Tests for ListOption, used to specify lists on the command-line."""
+
+    def parse(self, options, args):
+        parser = option.get_optparser(dict((o.name, o) for o in options))
+        return parser.parse_args(args)
+
+    def test_list_option(self):
+        options = [option.ListOption('hello', type=str)]
+        opts, args = self.parse(options, ['--hello=world', '--hello=sailor'])
+        self.assertEqual(['world', 'sailor'], opts.hello)
+
+    def test_list_option_no_arguments(self):
+        options = [option.ListOption('hello', type=str)]
+        opts, args = self.parse(options, [])
+        self.assertEqual([], opts.hello)
+
+    def test_list_option_with_int_type(self):
+        options = [option.ListOption('hello', type=int)]
+        opts, args = self.parse(options, ['--hello=2', '--hello=3'])
+        self.assertEqual([2, 3], opts.hello)
+
+    def test_list_option_with_int_type_can_be_reset(self):
+        options = [option.ListOption('hello', type=int)]
+        opts, args = self.parse(options, ['--hello=2', '--hello=3',
+                                          '--hello=-', '--hello=5'])
+        self.assertEqual([5], opts.hello)
+
+    def test_list_option_can_be_reset(self):
+        """Passing an option of '-' to a list option should reset the list."""
+        options = [option.ListOption('hello', type=str)]
+        opts, args = self.parse(
+            options, ['--hello=a', '--hello=b', '--hello=-', '--hello=c'])
+        self.assertEqual(['c'], opts.hello)

=== modified file 'doc/configuration.txt'
--- a/doc/configuration.txt	2007-03-02 12:40:45 +0000
+++ b/doc/configuration.txt	2007-04-23 01:30:35 +0000
@@ -139,7 +139,7 @@
     The gnupg signature for revisions must be present and must be valid.
 
 ignore
-    Do not check gnupg signatures of revisions. 
+    Do not check gnupg signatures of revisions.
 
 check-available
     (default) If gnupg signatures for revisions are present, check them.
@@ -171,7 +171,7 @@
 
 false
     This section only applies to the branch at this directory and not
-    branches below it. 
+    branches below it.
 
 gpg_signing_command
 -------------------
@@ -215,3 +215,34 @@
 -----
 If set to "True", the branch should act as a checkout, and push each commit to
 the bound_location.  This option is normally set by ``bind``/``unbind``.
+
+
+Bug Tracker Options
+===================
+
+These options can go into bazaar.conf, branch.conf or into a branch-specific
+configuration section in locations.conf.
+
+bugzilla_<tracker_abbreviation>_url
+-----------------------------------
+If present, the location of the Bugzilla bug tracker referred to by
+<tracker_abbreviation>. This option can then be used together with ``bzr commit
+--fixes`` to mark bugs in that tracker as being fixed by that commit. For
+example::
+
+    bugzilla_squid_url = http://www.squid-cache.org/bugs
+
+would allow ``bzr commit --fixes squid:1234`` to mark Squid's bug 1234 as
+fixed.
+
+trac_<tracker_abbrevation>_url
+------------------------------
+If present, the location of the Trac instance referred to by
+<tracker_abbreviation>. This option can then be used together with ``bzr commit
+--fixes`` to mark bugs in that tracker as being fixed by that commit. For
+example::
+
+    trac_twisted_url = http://www.twistedmatrix.com/trac
+
+would allow ``bzr commit --fixes twisted:1234`` to mark Twisted's bug 1234 as
+fixed.




More information about the bazaar-commits mailing list