14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
from .lazy_import import lazy_import
17
from bzrlib import registry, help_topics
18
from bzrlib.lazy_import import lazy_import
22
19
lazy_import(globals(), """
23
from breezy import urlutils
20
from bzrlib import errors, urlutils
44
When making a commit, metadata about bugs fixed by that change can be
45
recorded by using the ``--fixes`` option. For each bug marked as fixed, an
41
"""When making a commit, metadata about bugs fixed by that change can be
42
recorded by using the --fixes option. For each bug marked as fixed, an
46
43
entry is included in the 'bugs' revision property stating '<url> <status>'.
47
44
(The only ``status`` value currently supported is ``fixed.``)
49
The ``--fixes`` option allows you to specify a bug tracker and a bug identifier
50
rather than a full URL. This looks like::
46
The --fixes option allows you to specify a bug tracker and a bug identifier
47
rather than a full URL. This looks like
52
49
bzr commit --fixes <tracker>:<id>
56
bzr commit --fixes <id>
58
51
where "<tracker>" is an identifier for the bug tracker, and "<id>" is the
59
52
identifier for that bug within the bugtracker, usually the bug number.
60
If "<tracker>" is not specified the ``bugtracker`` set in the branch
61
or global configuration is used.
63
54
Bazaar knows about a few bug trackers that have many users. If
64
55
you use one of these bug trackers then there is no setup required to
65
56
use this feature, you just need to know the tracker identifier to use.
66
57
These are the bugtrackers that are built in:
68
============================ ============ ============
69
URL Abbreviation Example
70
============================ ============ ============
71
https://bugs.launchpad.net/ lp lp:12345
72
http://bugs.debian.org/ deb deb:12345
73
http://bugzilla.gnome.org/ gnome gnome:12345
74
============================ ============ ============
59
URL | Abbreviation | Example
60
https://bugs.launchpad.net/ | lp | lp:12345
61
http://bugs.debian.org/ | deb | deb:12345
62
http://bugzilla.gnome.org/ | gnome | gnome:12345
76
64
For the bug trackers not listed above configuration is required.
77
65
Support for generating the URLs for any project using Bugzilla or Trac
83
71
If you use Bugzilla or Trac, then you only need to set a configuration
84
72
variable which contains the base URL of the bug tracker. These options
85
can go into ``breezy.conf``, ``branch.conf`` or into a branch-specific
73
can go into ``bazaar.conf``, ``branch.conf`` or into a branch-specific
86
74
configuration section in ``locations.conf``. You can set up these values
87
75
for each of the projects you work on.
95
83
Use ``bzr commit --fixes lp:2`` to record that this commit fixes bug 2.
97
bugzilla_<tracker>_url
98
----------------------
85
bugzilla_<tracker_abbreviation>_url
86
-----------------------------------
100
88
If present, the location of the Bugzilla bug tracker referred to by
101
<tracker>. This option can then be used together with ``bzr commit
89
<tracker_abbreviation>. This option can then be used together with ``bzr commit
102
90
--fixes`` to mark bugs in that tracker as being fixed by that commit. For
105
bugzilla_squid_url = http://bugs.squid-cache.org
93
bugzilla_squid_url = http://www.squid-cache.org/bugs
107
95
would allow ``bzr commit --fixes squid:1234`` to mark Squid's bug 1234 as
98
trac_<tracker_abbrevation>_url
99
------------------------------
113
101
If present, the location of the Trac instance referred to by
114
<tracker>. This option can then be used together with ``bzr commit
102
<tracker_abbreviation>. This option can then be used together with ``bzr commit
115
103
--fixes`` to mark bugs in that tracker as being fixed by that commit. For
120
108
would allow ``bzr commit --fixes twisted:1234`` to mark Twisted's bug 1234 as
123
bugtracker_<tracker>_url
124
------------------------
111
bugtracker_<tracker_abbrevation>_url
112
------------------------------------
126
114
If present, the location of a generic bug tracker instance referred to by
127
<tracker>. The location must contain an ``{id}`` placeholder,
115
<tracker_abbreviation>. The location must contain an ``{id}`` placeholder,
128
116
which will be replaced by a specific bug ID. This option can then be used
129
117
together with ``bzr commit --fixes`` to mark bugs in that tracker as being
130
118
fixed by that commit. For example::
137
125
bugtracker_cpan_url = http://rt.cpan.org/Public/Bug/Display.html?id={id}
139
would allow ``bzr commit --fixes cpan:1234`` to mark bug 1234 in CPAN's
140
RT bug tracker as fixed, or::
142
bugtracker_hudson_url = http://issues.hudson-ci.org/browse/{id}
144
would allow ``bzr commit --fixes hudson:HUDSON-1234`` to mark bug HUDSON-1234
145
in Hudson's JIRA bug tracker as fixed.
127
for CPAN's RT bug tracker.
149
class MalformedBugIdentifier(errors.BzrError):
151
_fmt = ('Did not understand bug identifier %(bug_id)s: %(reason)s. '
152
'See "brz help bugs" for more information on this feature.')
154
def __init__(self, bug_id, reason):
159
class InvalidBugTrackerURL(errors.BzrError):
161
_fmt = ("The URL for bug tracker \"%(abbreviation)s\" doesn't "
162
"contain {id}: %(url)s")
164
def __init__(self, abbreviation, url):
165
self.abbreviation = abbreviation
169
class UnknownBugTrackerAbbreviation(errors.BzrError):
171
_fmt = ("Cannot find registered bug tracker called %(abbreviation)s "
174
def __init__(self, abbreviation, branch):
175
self.abbreviation = abbreviation
179
class InvalidLineInBugsProperty(errors.BzrError):
181
_fmt = ("Invalid line in bugs property: '%(line)s'")
183
def __init__(self, line):
187
class InvalidBugUrl(errors.BzrError):
189
_fmt = "Invalid bug URL: %(url)s"
191
def __init__(self, url):
195
class InvalidBugStatus(errors.BzrError):
197
_fmt = ("Invalid bug status: '%(status)s'")
199
def __init__(self, status):
203
131
def get_bug_url(abbreviated_bugtracker_name, branch, bug_id):
204
132
"""Return a URL pointing to the canonical web page of the bug identified by
222
150
tracker = tracker_type.get(abbreviated_bugtracker_name, branch)
223
151
if tracker is not None:
225
raise UnknownBugTrackerAbbreviation(
226
abbreviated_bugtracker_name, branch)
153
raise errors.UnknownBugTrackerAbbreviation(abbreviated_bugtracker_name,
228
156
def help_topic(self, topic):
229
157
return _bugs_help
274
202
self.base_url = base_url
276
204
def get(self, abbreviated_bugtracker_name, branch):
277
"""Returns the tracker if the abbreviation matches, otherwise ``None``.
279
if abbreviated_bugtracker_name != self.abbreviation:
283
def _get_bug_url(self, bug_id):
284
"""Return the URL for bug_id."""
285
return self.base_url + str(bug_id)
288
class ProjectIntegerBugTracker(IntegerBugTracker):
289
"""A bug tracker that exists in one place only with per-project ids.
291
If you have one of these trackers then register an instance passing in an
292
abbreviated name for the bug tracker and a base URL. The bug ids are
293
appended directly to the URL.
296
def __init__(self, abbreviated_bugtracker_name, base_url):
297
self.abbreviation = abbreviated_bugtracker_name
298
self._base_url = base_url
300
def get(self, abbreviated_bugtracker_name, branch):
301
"""Returns the tracker if the abbreviation matches, otherwise ``None``.
303
if abbreviated_bugtracker_name != self.abbreviation:
307
def check_bug_id(self, bug_id):
309
(project, bug_id) = bug_id.rsplit('/', 1)
311
raise MalformedBugIdentifier(bug_id, "Expected format: project/id")
315
raise MalformedBugIdentifier(bug_id, "Bug id must be an integer")
317
def _get_bug_url(self, bug_id):
318
(project, bug_id) = bug_id.rsplit('/', 1)
319
"""Return the URL for bug_id."""
320
if '{id}' not in self._base_url:
321
raise InvalidBugTrackerURL(self._abbreviation, self._base_url)
322
if '{project}' not in self._base_url:
323
raise InvalidBugTrackerURL(self._abbreviation, self._base_url)
324
return self._base_url.replace(
325
'{project}', project).replace('{id}', str(bug_id))
205
"""Returns the tracker if the abbreviation matches. Returns None
207
if abbreviated_bugtracker_name != self.abbreviation:
211
def _get_bug_url(self, bug_id):
212
"""Return the URL for bug_id."""
213
return self.base_url + bug_id
328
216
tracker_registry.register(
333
221
'debian', UniqueIntegerBugTracker('deb', 'http://bugs.debian.org/'))
336
tracker_registry.register(
337
'gnome', UniqueIntegerBugTracker(
338
'gnome', 'http://bugzilla.gnome.org/show_bug.cgi?id='))
341
tracker_registry.register(
342
'github', ProjectIntegerBugTracker(
343
'github', 'https://github.com/{project}/issues/{id}'))
346
class URLParametrizedBugTracker(BugTracker):
224
tracker_registry.register('gnome',
225
UniqueIntegerBugTracker('gnome', 'http://bugzilla.gnome.org/show_bug.cgi?id='))
228
class URLParametrizedIntegerBugTracker(IntegerBugTracker):
347
229
"""A type of bug tracker that can be found on a variety of different sites,
348
230
and thus needs to have the base URL configured.
350
232
Looks for a config setting in the form '<type_name>_<abbreviation>_url'.
351
`type_name` is the name of the type of tracker and `abbreviation`
352
is a short name for the particular instance.
233
`type_name` is the name of the type of tracker (e.g. 'bugzilla' or 'trac')
234
and `abbreviation` is a short name for the particular instance (e.g.
235
'squid' or 'apache').
355
238
def get(self, abbreviation, branch):
356
239
config = branch.get_config()
357
240
url = config.get_user_option(
358
"%s_%s_url" % (self.type_name, abbreviation), expand=False)
241
"%s_%s_url" % (self.type_name, abbreviation))
361
244
self._base_url = url
370
253
return urlutils.join(self._base_url, self._bug_area) + str(bug_id)
373
class URLParametrizedIntegerBugTracker(IntegerBugTracker,
374
URLParametrizedBugTracker):
375
"""A type of bug tracker that only allows integer bug IDs.
377
This can be found on a variety of different sites, and thus needs to have
378
the base URL configured.
380
Looks for a config setting in the form '<type_name>_<abbreviation>_url'.
381
`type_name` is the name of the type of tracker (e.g. 'bugzilla' or 'trac')
382
and `abbreviation` is a short name for the particular instance (e.g.
383
'squid' or 'apache').
387
256
tracker_registry.register(
388
257
'trac', URLParametrizedIntegerBugTracker('trac', 'ticket/'))
405
274
def _get_bug_url(self, bug_id):
406
275
"""Given a validated bug_id, return the bug's web page's URL."""
407
276
if '{id}' not in self._base_url:
408
raise InvalidBugTrackerURL(self._abbreviation, self._base_url)
277
raise errors.InvalidBugTrackerURL(self._abbreviation,
409
279
return self._base_url.replace('{id}', str(bug_id))
418
ALLOWED_BUG_STATUSES = {FIXED, RELATED}
287
ALLOWED_BUG_STATUSES = set([FIXED])
421
290
def encode_fixes_bug_urls(bug_urls):
422
291
"""Get the revision property value for a commit that fixes bugs.
424
:param bug_urls: An iterable of (escaped URL, tag) tuples. These normally
293
:param bug_urls: An iterable of escaped URLs to bugs. These normally
425
294
come from `get_bug_url`.
426
295
:return: A string that will be set as the 'bugs' property of a revision
427
296
as part of a commit.
430
for (url, tag) in bug_urls:
432
raise InvalidBugUrl(url)
433
lines.append('%s %s' % (url, tag))
434
return '\n'.join(lines)
437
def decode_bug_urls(bug_text):
438
"""Decode a bug property text.
440
:param bug_text: Contents of a bugs property
441
:return: iterator over (url, status) tuples
443
for line in bug_text.splitlines():
445
url, status = line.split(None, 2)
447
raise InvalidLineInBugsProperty(line)
448
if status not in ALLOWED_BUG_STATUSES:
449
raise InvalidBugStatus(status)
298
return '\n'.join(('%s %s' % (url, FIXED)) for url in bug_urls)