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

8

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 still ‘bug-reference-bug-regexp’, and ‘bug-reference-url-format’
aren’t both set, it’ll try to setup suitable values for these two
variables itself by calling the functions in
‘bug-reference-auto-setup-functions’ one after the other until one is
able to set the variables.

   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
     setup 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), and several kinds of
     modern software forges such as GitLab, Gitea, SourceHut, or 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
straight-forward: 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 setup
bug-reference mode which is the case if the last thing the function does
is calling 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 ‘major-mode’ value.

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 byte-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.
      You can customize this variable.
    

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.

4
  • 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
    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
    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
    Dec 13, 2021 at 14:39
  • Thank you. That looks good to me, but I haven't tried it.
    – Mike Crowe
    Dec 14, 2021 at 20:26

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.