4

I’m working on a project which is version controlled using GitLab. GitLab, unlike GitHub, uses two separate URL endpoints for issues and merge requests (AKA Pull Requests):

https://gitlab.example.com/group/project/issues/<issue_number>
https://gitlab.example.com/group/project/merge_requests/<mr_number>

Also, issues are represented as #1 while merge requests are represented as !1.

It is fairly easy to write a regex to match both, but is there a way to tell bug-reference-mode to use different URL formats for them?

1 Answer 1

9

Update

Starting with Emacs 28, bug-reference is able to automatically detect and configure itself for Git forges including GitHub and GitLab. Quoth (info "(emacs) Bug Reference"):

[...]

   For its working, bug reference mode needs to know the syntax of bug
references (‘bug-reference-bug-regexp’), and the URL of the tracker
where bug reports can be looked up (‘bug-reference-url-format’).  Since
those are typically different from project to project, it makes sense to
specify them in Directory Variables or File Variables.

   For example, let's assume in our project, we usually write references
to bug reports as bug#1234, or Bug-1234 and that this bug's page on the
issue tracker is <https://project.org/issues/1234>, then these local
variables section would do.

     ;; Local Variables:
     ;; bug-reference-bug-regexp: "\\([Bb]ug[#-]\\([0-9]+\\)\\)"
     ;; bug-reference-url-format: "https://project.org/issues/%s"
     ;; End:

   The string captured by the first regexp group defines the bounds of
the overlay bug-reference creates, i.e., the part which is highlighted
and made clickable.

   The string captured by the second regexp group in
‘bug-reference-bug-regexp’ is used to replace the ‘%s’ template in the
‘bug-reference-url-format’.

   Note that ‘bug-reference-url-format’ may also be a function in order
to cater for more complex scenarios, e.g., when different parts of the
bug reference have to be used to distinguish between issues and merge
requests resulting in different URLs.

Automatic Setup
===============

If ‘bug-reference-mode’ is activated, ‘bug-reference-mode-hook’ has been
run, and either ‘bug-reference-bug-regexp’ or ‘bug-reference-url-format’
is still ‘nil’, the mode will try to automatically find a suitable value
for these two variables by calling the functions in
‘bug-reference-auto-setup-functions’ one by one until one succeeds.

   Right now, there are three types of setup functions.

  1. Setup for version-controlled files configurable by the variables
     ‘bug-reference-forge-alist’, and
     ‘bug-reference-setup-from-vc-alist’.  The defaults are able to set
     up GNU projects where <https://debbugs.gnu.org> is used as issue
     tracker and issues are usually referenced as ‘bug#13’ (but many
     different notations are considered, too), as well as several other
     kinds of software forges such as GitLab, Gitea, SourceHut, and
     GitHub.  If you deploy a self-hosted instance of such a forge, the
     easiest way to tell bug-reference about it is through
     ‘bug-reference-forge-alist’.

[...]

Adding support for third-party packages
=======================================

Adding support for bug-reference auto-setup is usually quite
straightforward: write a setup function of zero arguments which gathers
the required information (e.g., List-Id/To/From/Cc mail header values in
the case of a MUA), and then calls one of the following helper
functions:

   • ‘bug-reference-maybe-setup-from-vc’, which does the setup according
     to ‘bug-reference-setup-from-vc-alist’;

   • ‘bug-reference-maybe-setup-from-mail’, which does the setup
     according to ‘bug-reference-setup-from-mail-alist’; and

   • ‘bug-reference-maybe-setup-from-irc’, which does the setup
     according to ‘bug-reference-setup-from-irc-alist’.

   A setup function should return non-‘nil’ if it could set up
bug-reference mode, which is the case if the last thing the function
does is call one of the helper functions above.

   Finally, the setup function has to be added to
‘bug-reference-auto-setup-functions’.

   Note that these auto-setup functions should check as a first step if
they are applicable, e.g., by checking the value of ‘major-mode’.

As Mike Crowe correctly pointed out in the comments, bug-reference also changed how it interprets (and warns about) regexp grouping constructs in Emacs 28. I have tried to update the rest of the original answer below accordingly.

Summary

is there a way to tell bug-reference-mode to use different URL formats for them?

If you are using Emacs 24+, there most certainly is.

The key to making bug-reference-mode more flexible lies in:

  • customising bug-reference-bug-regexp to match all your preferred bug reference formats (which should be differentiable); and

  • setting bug-reference-url-format to the nullary function to actually differentiate between them by analysing the match data pertaining to the custom bug-reference-bug-regexp.

After that, you need to find a way for these customisations to apply only to the files in question, e.g. via file or directory variables, or even, say, a custom minor mode.

Documentation

Before I list a sample configuration, see the docstrings of the aforementioned bug-reference variables:

  • bug-reference-url-format
bug-reference-url-format is a variable defined in ‘bug-reference.el’.

Its value is nil

Format used to turn a bug number into a URL.
The bug number is supplied as a string, so this should have a single %s.
This can also be a function designator; it is called without arguments
 and should return a string.
It can use ‘match-string’ to get parts matched against
‘bug-reference-bug-regexp’, specifically:
 1. issue kind (bug, patch, rfe &c)
 2. issue number.

There is no default setting for this, it must be set per file.
If you set it to a symbol in the file Local Variables section,
you need to add a ‘bug-reference-url-format’ property to it:
(put 'my-bug-reference-url-format 'bug-reference-url-format t)
so that it is considered safe, see ‘enable-local-variables’.

  This variable is safe as a file local variable if its value
  satisfies the predicate which is a compiled expression.
  Probably introduced at or before Emacs version 28.1.
  • bug-reference-bug-regexp
bug-reference-bug-regexp is a variable defined in ‘bug-reference.el’.

Its value is
"\\(\\b\\(?:[Bb]ug ?#?\\|[Pp]atch ?#\\|RFE ?#\\|PR [a-z+-]+/\\)\\([0-9]+\\(?:#[0-9]+\\)?\\)\\)"

Regular expression matching bug references.
The first subexpression defines the region of the bug-reference
overlay, i.e., the region being fontified and made clickable in
order to browse the referenced bug in the corresponding project’s
issue tracker.

If ‘bug-reference-url-format’ is set to a format string with
single %s placeholder, the second subexpression must match
the (part of the) bug reference which needs to be injected in
place of the %s in order to form the bug’s ticket URL.

If ‘bug-reference-url-format’ is a function, the interpretation
of the subexpressions larger than 1 is up to the function.
However, it is checked that the bounds of all matching
subexpressions from 2 to 10 are within the bounds of the
subexpression 1 defining the overlay region.  Larger
subexpressions may also be used by the function but may lay
outside the bounds of subexpressions 1 and then don’t contribute
to the highlighted and clickable region.

  This variable is safe as a file local variable if its value
  satisfies the predicate ‘stringp’.
  This variable was introduced, or its default value was changed, in
  version 28.1 of Emacs.

Example

Assuming issue references are formatted as some variation of Issue #N or issue#N, and merge requests as some variation of MR !N or MR!N, the following code will create a new sample buffer with an Elisp comment showcasing the two types of bug-reference button.

(defun my-gitlab-url ()
  "Return a GitLab merge request or issue URL.
Intended as a value for `bug-reference-url-format'."
  (format "https://gitlab.example.com/group/project/%s/%s"
          (if (string-suffix-p "!" (match-string-no-properties 2))
              "merge_requests"
            "issues")
          (match-string-no-properties 3)))

(with-current-buffer-window
    (generate-new-buffer-name "tmp") () nil
  (emacs-lisp-mode)
  (setq-local bug-reference-bug-regexp
              (rx (group (group (| (: (in ?I ?i) "ssue" (? ?\s) ?#)
                                   (: "MR" (? ?\s) ?!)))
                         (group (+ digit)))))
  (setq-local bug-reference-url-format #'my-gitlab-url)
  (insert ";; MR!101 solves issue #100\n")
  (bug-reference-prog-mode))

Note:

  • bug-reference-bug-regexp must contain three grouping constructs, one for complete button text, one for the bug type, and one for the bug number.

  • In my example, I differentiate between issues and merge requests by checking whether the bug type (i.e. the first match group) ends in a trailing exclamation mark. You can adapt this to some other check, such as whether the bug type matches the pattern issue or similar.

  • In the sample buffer, I enable bug-reference-prog-mode instead of bug-reference-mode. The former is a restricted version of the latter, where the former only creates buttons in comments and strings.

  • As phils points out, if you would like to use the custom function my-gitlab-url as the file- or directory-local value of bug-reference-url-format, then as per its docstring you also need the following:

    (function-put 'my-gitlab-url 'bug-reference-url-format t)
    
7
  • It appears that a change in Emacs 28 means that an extra pair of parentheses (or an extra group) are now required around the value of bug-reference-bug-regexp to ensure that everything that is matched is inside subexpression 1. Without this, Emacs generates a warning.
    – Mike Crowe
    Commented Dec 1, 2021 at 17:34
  • I forgot to say that the my-gitlab-url function probably needs to be adapted to look at subexpression 2 and 3 rather than 1 and 2 too.
    – Mike Crowe
    Commented Dec 1, 2021 at 17:42
  • @MikeCrowe Good catch, thanks. I've updated the answer accordingly, please check whether it's up to scratch.
    – Basil
    Commented Dec 13, 2021 at 14:39
  • Thank you. That looks good to me, but I haven't tried it.
    – Mike Crowe
    Commented Dec 14, 2021 at 20:26
  • In my bug-reference config I've commented on the issue raised by Mike: "Note that if bug-reference-url-format is a function, group 1 should encompass the other groups (see bug-reference-bug-regexp); but this then conflicts with the need for group 2 to match the issue number if bug-reference-url-format actually a format string (if the default group numbers are used). This is why I'm using explicit group numbers here, so that group 2 has the desired value in both scenarios." E.g.: "\\(?2:foo\\)" or (rx (group-n 2 "foo")) to match foo in group 2 regardless of group sequence.
    – phils
    Commented Apr 11 at 9:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.