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
17
from __future__ import absolute_import
19
from bzrlib import registry
20
from bzrlib.lazy_import import lazy_import
21
from .lazy_import import lazy_import
21
22
lazy_import(globals(), """
22
from bzrlib import errors, urlutils
23
from breezy import urlutils
43
"""When making a commit, metadata about bugs fixed by that change can be
44
When making a commit, metadata about bugs fixed by that change can be
44
45
recorded by using the ``--fixes`` option. For each bug marked as fixed, an
45
46
entry is included in the 'bugs' revision property stating '<url> <status>'.
46
47
(The only ``status`` value currently supported is ``fixed.``)
82
83
If you use Bugzilla or Trac, then you only need to set a configuration
83
84
variable which contains the base URL of the bug tracker. These options
84
can go into ``bazaar.conf``, ``branch.conf`` or into a branch-specific
85
can go into ``breezy.conf``, ``branch.conf`` or into a branch-specific
85
86
configuration section in ``locations.conf``. You can set up these values
86
87
for each of the projects you work on.
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):
148
203
def get_bug_url(abbreviated_bugtracker_name, branch, bug_id):
149
204
"""Return a URL pointing to the canonical web page of the bug identified by
167
222
tracker = tracker_type.get(abbreviated_bugtracker_name, branch)
168
223
if tracker is not None:
170
raise errors.UnknownBugTrackerAbbreviation(abbreviated_bugtracker_name,
225
raise UnknownBugTrackerAbbreviation(
226
abbreviated_bugtracker_name, branch)
173
228
def help_topic(self, topic):
174
229
return _bugs_help
219
274
self.base_url = base_url
221
276
def get(self, abbreviated_bugtracker_name, branch):
222
"""Returns the tracker if the abbreviation matches. Returns None
224
if abbreviated_bugtracker_name != self.abbreviation:
228
def _get_bug_url(self, bug_id):
229
"""Return the URL for bug_id."""
230
return self.base_url + bug_id
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))
233
328
tracker_registry.register(
238
333
'debian', UniqueIntegerBugTracker('deb', 'http://bugs.debian.org/'))
241
tracker_registry.register('gnome',
242
UniqueIntegerBugTracker('gnome',
243
'http://bugzilla.gnome.org/show_bug.cgi?id='))
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}'))
246
346
class URLParametrizedBugTracker(BugTracker):
304
405
def _get_bug_url(self, bug_id):
305
406
"""Given a validated bug_id, return the bug's web page's URL."""
306
407
if '{id}' not in self._base_url:
307
raise errors.InvalidBugTrackerURL(self._abbreviation,
408
raise InvalidBugTrackerURL(self._abbreviation, self._base_url)
309
409
return self._base_url.replace('{id}', str(bug_id))
317
ALLOWED_BUG_STATUSES = set([FIXED])
418
ALLOWED_BUG_STATUSES = {FIXED, RELATED}
320
421
def encode_fixes_bug_urls(bug_urls):
321
422
"""Get the revision property value for a commit that fixes bugs.
323
:param bug_urls: An iterable of escaped URLs to bugs. These normally
424
:param bug_urls: An iterable of (escaped URL, tag) tuples. These normally
324
425
come from `get_bug_url`.
325
426
:return: A string that will be set as the 'bugs' property of a revision
326
427
as part of a commit.
328
return '\n'.join(('%s %s' % (url, FIXED)) for url in bug_urls)
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)