From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 45C1B34752E for ; Sat, 12 Sep 2026 10:58:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789210695; cv=none; b=dh2uSgNde5ETvBWLvn4FlsoHmy1Gfu1sZfC+N5qYsfz+YP08uRClEmkHocoO3j61fvOhF+sZj33p28ot5Lv23Oyvl/b9ybzqKBa/5Fe47+MJ5I2eRPHNe2bXnCEarykX1kgapgfIq2oOTH1N6rICojF15wHV5fgQXvHVv3fdSVA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789210695; c=relaxed/simple; bh=u2HFwnIlkp+ccCZKlQpCyZu36RJPyg6hAfjBZNI679g=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type; b=a8Zs5/XQhj1OK6+JosXsJ/3betXjq+bjmvRm/8tQ1ateQdFeqiwzfYlPVxftRGFTmPD9WymVFjl22pVFpmvptnZhCI9Ixju8vvK+FgVMi1TRna5yADJT5F6gDrpowCT/69Q/cDf2gwtpHtto0FoHlw7TML7Q8FfVJIr7yGroIkk= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lHENCLnn; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lHENCLnn" Received: by smtp.kernel.org (Postfix) id 28A821F000FF; Sat, 12 Sep 2026 10:58:13 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 1A7011F00893; Sat, 12 Sep 2026 10:58:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789210693; bh=c3Q8jiJA6JKkZdibIhrCf3H/QyyPATnnaG9rb2E5foc=; h=From:To:Cc:Subject:Date; b=lHENCLnn/eh4cDFjbPxZJxAXVW5SA9t7kz6yBcH+3oUynCz9yQqqRsA7mXRycDiKm 0MHV3/YhwOyJjIrjwnUoqteDrNDyNKOKIE2GYPltsxmYOSopuL715i4QlgUcINusQi LGBX+3boX2iMM3a2jy4yZOO7qkwyBasGzvlH7j+OWsvTbIBBDFykR3v7rJOrWmSJdZ dOPtzMla1I0Y5VagQ+kuSUnJpsEND1tHuG7mphoYYmBMj9Gq8s023y6480T95WVnDR APRiMhHNGsY8j1cDJBJ+J9nMPMyt7t2LfxBWOnPAI86ScrdPTxx2AFXek42erHjYEV iiMAA7W39tT0g== From: Mark Brown To: Konstantin Ryabitsev Cc: tools@kernel.org, Mark Brown Subject: [PATCH v2] review: Generate thanks commit URLs for known repositories automatically Date: Sat, 12 Sep 2026 11:58:07 +0100 Message-ID: <20260912105809.990519-1-broonie@kernel.org> X-Mailer: git-send-email 2.47.3 Precedence: bulk X-Mailing-List: tools@linux.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At present generating links to commits in web interfaces for inclusion in thanks mails requires manual configuration of a global configuration option b4.thanks-commit-url-mask, or the per remote equivalent b4-thanks-url-mask. Given that we also have the b4-check-repo option to configure where to check to see if thanks are ready to be sent and practically speaking this is likely to be one of a very small set of hosting providers this is all a bit redundant. If one of git.kernel.org, github or gitlab is configured for the check repo or remote URL for the branch then default to generating a link based on our knowledge of the remote, a manual configuration will still override the default. Signed-off-by: Mark Brown --- Also fall back to the remote URL. docs/config.rst | 32 ++++- docs/maintainer/review.rst | 15 +-- src/b4/command.py | 4 + src/b4/review_tui/_tracking_app.py | 4 +- src/b4/templates/thanks-am-template.example | 3 + src/b4/templates/thanks-pr-template.example | 3 + src/b4/ty.py | 131 +++++++++++++++----- src/tests/test_tui_tracking.py | 45 +++++++ src/tests/test_ty.py | 88 +++++++++++++ 9 files changed, 281 insertions(+), 44 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 4dce689..91d783e 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -463,6 +463,11 @@ Attestation settings [remote "spi"] b4-check-repo = https://git.kernel.org/pub/scm/linux/kernel/git/username/spi.git + For recognized HTTP(S) web hosts (currently git.kernel.org, + github.com, and gitlab.com), b4 also uses this URL to link commits in + the thank-you message. Other transports and hosting layouts can use + the per-remote ``b4-commit-url-mask`` described below. + See :ref:`queuing thanks ` for details. Default: ``None`` @@ -471,14 +476,29 @@ Attestation settings Used when creating summaries for ``b4 ty`` and the review TUI thank-you flow. Can be a value like:: - thanks-commit-url-mask = https://git.kernel.org/username/c/%.12s + thanks-commit-url-mask = https://git.kernel.org/username/tree/c/%.12s + + If no explicit mask or recognized check-repository URL is available, + b4 falls back to using commit hashes. - If not set, b4 falls back to using commit hashes. + For repositories with multiple remotes, an explicit per-remote mask + takes precedence over this setting:: - When this option is set, the review TUI's thank-you preview offers - a **Queue** option that stores the message for delayed delivery. - Queued messages are held until their commit is verified published, - so you can push your commits before the thank-you is sent. + [remote "spi"] + b4-commit-url-mask = https://git.kernel.org/username/spi/c/%.12s + + When no explicit per-remote mask is set, b4 automatically derives one + from that remote's ``b4-check-repo`` for recognized HTTP(S) web hosts. + The same derivation is attempted from :term:`b4.thanks-check-repo`, and + then from the URL of the remote the target branch tracks, when neither + a per-remote nor this global mask is available — the same repository + URLs, in the same order, that :term:`b4.thanks-check-repo` resolution + uses. A URL that is not a recognized web host is simply skipped. + + When a commit URL can be generated, the review TUI's thank-you preview + offers a **Queue** option that stores the message for delayed delivery. + Queued messages are held until their commit is verified published, so + you can push your commits before the thank-you is sent. See :ref:`queuing thanks ` for details. .. note:: diff --git a/docs/maintainer/review.rst b/docs/maintainer/review.rst index 47aa8e7..842120b 100644 --- a/docs/maintainer/review.rst +++ b/docs/maintainer/review.rst @@ -1023,13 +1023,14 @@ screen shows the generated message with keybindings to **Send** **Queuing thanks for delayed delivery** -When :term:`b4.thanks-commit-url-mask` is configured, the thank-you -preview also offers a **Queue** option (``W``). Queuing writes the -message as an RFC 2822 file in ``.git/b4-review/queue/`` instead of -sending it immediately. This is useful when you want to push your -commits to a public tree before sending thank-you messages — the -message references commit URLs that may not resolve until the push is -complete. +When b4 can generate a web link for the commit — from +:term:`b4.thanks-commit-url-mask`, a per-remote ``b4-commit-url-mask``, or +a recognized HTTP(S) check-repository or remote URL — the thank-you preview +also offers a **Queue** option (``W``). Queuing writes the message as an RFC 2822 +file in ``.git/b4-review/queue/`` instead of sending it immediately. This +is useful when you want to push your commits to a public tree before +sending thank-you messages — the message references commit URLs that may +not resolve until the push is complete. Files are named ``{change-id}-v{revision}.msg`` and the check target is recorded as ``X-Check-URL``, ``X-Check-Commit``, ``X-Check-Repo`` and diff --git a/src/b4/command.py b/src/b4/command.py index 00c4c84..41bb9ae 100644 --- a/src/b4/command.py +++ b/src/b4/command.py @@ -249,6 +249,10 @@ def cmd_pr(cmdargs: argparse.Namespace) -> None: def cmd_ty(cmdargs: argparse.Namespace) -> None: import b4.ty + if cmdargs.gitdir: + # The initial setup happens relative to cwd. Reload repository-local + # thank-you settings when -g names a different tree. + b4.setup_config(cmdargs, topdir=cmdargs.gitdir) b4.ty.main(cmdargs) diff --git a/src/b4/review_tui/_tracking_app.py b/src/b4/review_tui/_tracking_app.py index 4c25f8c..0f39b8f 100644 --- a/src/b4/review_tui/_tracking_app.py +++ b/src/b4/review_tui/_tracking_app.py @@ -4926,8 +4926,8 @@ class TrackingApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[Optional[str]]): # Compute checkurl from last taken commit for queue support checkurl: Optional[str] = None checkcommit: Optional[str] = None - cidmask = config.get('thanks-commit-url-mask') - if isinstance(cidmask, str) and cidmask and '%' in cidmask: + cidmask = b4.ty.get_commit_url_mask(topdir, target_branch) + if cidmask and '%' in cidmask: # Find the last commit ID (highest patch index with a commit) last_cid: Optional[str] = None for _idx, cid in commits: diff --git a/src/b4/templates/thanks-am-template.example b/src/b4/templates/thanks-am-template.example index b2c8986..bac889a 100644 --- a/src/b4/templates/thanks-am-template.example +++ b/src/b4/templates/thanks-am-template.example @@ -20,6 +20,9 @@ ${quote} # url = https://... # fetch = ... # b4-treename = uname/sound.git +# b4-check-repo = https://git.kernel.org/pub/scm/linux/kernel/git/uname/sound.git +# The check repo is also used to generate commit links for known web hosts. +# For other hosts, or to choose a different link format, set an explicit mask: # b4-commit-url-mask = https://git.kernel.org/uname/sound/c/%.8s Applied to ${branch}, thanks! diff --git a/src/b4/templates/thanks-pr-template.example b/src/b4/templates/thanks-pr-template.example index 13bd205..afae499 100644 --- a/src/b4/templates/thanks-pr-template.example +++ b/src/b4/templates/thanks-pr-template.example @@ -20,6 +20,9 @@ ${quote} # url = https://... # fetch = ... # b4-treename = uname/sound.git +# b4-check-repo = https://git.kernel.org/pub/scm/linux/kernel/git/uname/sound.git +# The check repo is also used to generate commit links for known web hosts. +# For other hosts, or to choose a different link format, set an explicit mask: # b4-commit-url-mask = https://git.kernel.org/uname/sound/c/%.8s Merged into ${branch}, thanks! diff --git a/src/b4/ty.py b/src/b4/ty.py index bbce149..9aca439 100644 --- a/src/b4/ty.py +++ b/src/b4/ty.py @@ -13,6 +13,7 @@ import json import os import re import sys +import urllib.parse from email.message import EmailMessage from pathlib import Path from string import Template @@ -61,8 +62,6 @@ ${signature} # Used to track commits created by current user MY_COMMITS: Optional[Dict[str, Tuple[str, str, List[str]]]] = None -# Used to track additional branch info -BRANCH_INFO: Optional[Dict[str, str]] = None def git_get_merge_id( @@ -294,6 +293,87 @@ def auto_locate_series( return found +def _derive_commit_url_mask(repo: str) -> Optional[str]: + """Return a commit URL mask for known web repository layouts.""" + try: + parsed = urllib.parse.urlparse(repo.rstrip('/')) + hostname = parsed.hostname + has_credentials = parsed.username is not None or parsed.password is not None + except ValueError: + return None + if parsed.scheme not in ('http', 'https') or not hostname: + return None + if has_credentials or parsed.query or parsed.fragment: + return None + + hostname = hostname.lower() + path = parsed.path.rstrip('/') + if hostname in ('github.com', 'www.github.com', 'gitlab.com', 'www.gitlab.com'): + path = re.sub(r'\.git$', '', path) + if hostname == 'git.kernel.org': + repo_parts = re.sub(r'\.git$', '', path).strip('/').split('/') + if len(repo_parts) < 2 or not all(repo_parts): + return None + short_path = f'/{repo_parts[-2]}/{repo_parts[-1]}' + base = urllib.parse.urlunparse( + (parsed.scheme, parsed.netloc, short_path, '', '', '') + ).replace('%', '%%') + return f'{base}/c/%.12s' + base = urllib.parse.urlunparse( + (parsed.scheme, parsed.netloc, path, '', '', '') + ).replace('%', '%%') + if hostname in ('github.com', 'www.github.com'): + return f'{base}/commit/%s' + if hostname in ('gitlab.com', 'www.gitlab.com'): + return f'{base}/-/commit/%s' + return None + + +def _get_commit_url_mask(config: ConfigDictT, binfo: Dict[str, str]) -> Optional[str]: + """Resolve the commit URL mask for one branch's remote. + + An explicit mask always wins, per-remote before global. Failing that + we derive one from the same repository URLs check-repo resolution + uses, in the same order: the per-remote b4-check-repo, the + b4.thanks-check-repo config option, then the branch's remote URL. A + URL we cannot derive a mask from does not end the search, so an + unrecognized check repo still gets a link if the remote URL is a host + we know. See get_check_repo_for_branch(). + """ + if 'b4-commit-url-mask' in binfo: + return binfo['b4-commit-url-mask'] or None + + checkrepo = binfo.get('b4-check-repo') + if checkrepo: + cidmask = _derive_commit_url_mask(checkrepo) + if cidmask: + return cidmask + + global_cidmask = config.get('thanks-commit-url-mask') + if global_cidmask: + assert isinstance(global_cidmask, str), ( + 'thanks-commit-url-mask must be a string' + ) + return global_cidmask + + global_checkrepo = config.get('thanks-check-repo') + if global_checkrepo: + assert isinstance(global_checkrepo, str), 'thanks-check-repo must be a string' + cidmask = _derive_commit_url_mask(global_checkrepo) + if cidmask: + return cidmask + + remoteurl = binfo.get('url') + if remoteurl: + return _derive_commit_url_mask(remoteurl) + return None + + +def get_commit_url_mask(gitdir: Optional[str], branch: str) -> Optional[str]: + """Return the effective commit URL mask for a branch.""" + return _get_commit_url_mask(b4.get_main_config(), get_branch_info(gitdir, branch)) + + def set_branch_details( gitdir: Optional[str], branch: str, jsondata: JsonDictT, config: ConfigDictT ) -> Tuple[JsonDictT, ConfigDictT]: @@ -302,8 +382,6 @@ def set_branch_details( for key, val in binfo.items(): if key == 'b4-treename': config['thanks-treename'] = val - elif key == 'b4-commit-url-mask': - config['thanks-commit-url-mask'] = val elif key == 'b4-pr-template': config['thanks-pr-template'] = val elif key == 'b4-am-template': @@ -311,6 +389,8 @@ def set_branch_details( elif key == 'branch': jsondata['branch'] = val + config['thanks-commit-url-mask'] = _get_commit_url_mask(config, binfo) + if 'thanks-treename' in config and isinstance(config['thanks-treename'], str): jsondata['treename'] = config['thanks-treename'] elif 'url' in binfo: @@ -330,7 +410,7 @@ def set_branch_details( def generate_pr_thanks( gitdir: Optional[str], jsondata: JsonDictT, branch: str, cmdargs: argparse.Namespace ) -> EmailMessage: - config = b4.get_main_config() + config = b4.get_main_config().copy() jsondata, config = set_branch_details(gitdir, branch, jsondata, config) thanks_template = DEFAULT_PR_TEMPLATE _ctpr = config.get('thanks-pr-template') @@ -369,9 +449,7 @@ def generate_pr_thanks( def generate_am_thanks( gitdir: Optional[str], jsondata: JsonDictT, branch: str, cmdargs: argparse.Namespace ) -> EmailMessage: - global BRANCH_INFO - BRANCH_INFO = None - config = b4.get_main_config() + config = b4.get_main_config().copy() jsondata, config = set_branch_details(gitdir, branch, jsondata, config) thanks_template = DEFAULT_AM_TEMPLATE _ctat = config.get('thanks-am-template') @@ -900,7 +978,6 @@ def check_stale_thanks(outdir: str) -> None: def get_wanted_branch(cmdargs: argparse.Namespace) -> str: - global BRANCH_INFO gitdir = cmdargs.gitdir if not cmdargs.branch: # Find out our current branch @@ -995,7 +1072,7 @@ def _parse_checkurl(checkurl: str) -> Tuple[Optional[str], Optional[str]]: """Extract (repo_url, commit_id) from a thanks-commit-url-mask URL. The repo URL is only derivable from cgit/github/gitlab-style commit - URLs; for anything else (e.g. git.kernel.org /username/c/ shortlinks) + URLs; for anything else (e.g. git.kernel.org /username/tree/c/ shortlinks) we can usually still recover the commit id from a trailing hex run. Either element may be None. """ @@ -1037,10 +1114,6 @@ def get_check_repo_for_branch( 3. the branch's remote URL 4. a repo URL derived from the commit check URL """ - # Long-lived callers (the TUI) thank series on different branches; - # never serve another branch's cached remote info - global BRANCH_INFO - BRANCH_INFO = None binfo = get_branch_info(gitdir, branch) crepo = binfo.get('b4-check-repo') if crepo: @@ -1568,20 +1641,18 @@ def _process_queue_locked( def get_branch_info(gitdir: Optional[str], branch: str) -> Dict[str, str]: - global BRANCH_INFO - if BRANCH_INFO is not None: - return BRANCH_INFO + branch_info: Dict[str, str] = dict() - BRANCH_INFO = dict() - - remotecfg = b4.get_config_from_git('branch\\.%s\\..*' % branch) + remotecfg = b4.get_config_from_git( + r'^branch\.%s\.' % re.escape(branch), gitdir=gitdir + ) if 'remote' not in remotecfg: # Did not find a matching branch entry, so look at remotes gitargs = ['remote', 'show'] lines = b4.git_get_command_lines(gitdir, gitargs) if not len(lines): # No remotes? Hmm... - return BRANCH_INFO + return branch_info remote = None for entry in lines: @@ -1591,21 +1662,23 @@ def get_branch_info(gitdir: Optional[str], branch: str) -> Dict[str, str]: if remote is None: # Not found any matching remotes - return BRANCH_INFO + return branch_info - BRANCH_INFO['remote'] = remote - BRANCH_INFO['branch'] = branch.replace(f'{remote}/', '') + branch_info['remote'] = remote + branch_info['branch'] = branch.replace(f'{remote}/', '') else: - BRANCH_INFO['remote'] = remotecfg['remote'] + branch_info['remote'] = remotecfg['remote'] if 'merge' in remotecfg: - BRANCH_INFO['branch'] = re.sub(r'^refs/heads/', '', remotecfg['merge']) + branch_info['branch'] = re.sub(r'^refs/heads/', '', remotecfg['merge']) # Grab template overrides - remotecfg = b4.get_config_from_git('remote\\.%s\\..*' % BRANCH_INFO['remote']) - BRANCH_INFO.update(remotecfg) + remotecfg = b4.get_config_from_git( + r'^remote\.%s\.' % re.escape(branch_info['remote']), gitdir=gitdir + ) + branch_info.update(remotecfg) - return BRANCH_INFO + return branch_info def main(cmdargs: argparse.Namespace) -> None: diff --git a/src/tests/test_tui_tracking.py b/src/tests/test_tui_tracking.py index e3d79ef..1e064cf 100644 --- a/src/tests/test_tui_tracking.py +++ b/src/tests/test_tui_tracking.py @@ -2931,6 +2931,51 @@ class TestSeriesLifecycle: # Only the two taken patches contribute commits (by 1-based index). assert jsondata['commits'] == [(1, 'aaa111'), (2, 'bbb222')] + def test_thank_preview_uses_branch_check_repo_url(self, gitdir: str) -> None: + """The queue target uses the same per-remote URL as the message.""" + from unittest import mock + + checkrepo = 'https://git.kernel.org/pub/scm/linux/kernel/git/example/spi.git' + b4.git_set_config(gitdir, 'branch.for-next.remote', 'spi') + b4.git_set_config(gitdir, 'branch.for-next.merge', 'refs/heads/for-next') + b4.git_set_config(gitdir, 'remote.spi.b4-check-repo', checkrepo) + commit = '0123456789abcdef0123456789abcdef01234567' + tracking_data: Dict[str, Any] = { + 'series': { + 'header-info': {}, + 'taken': {'branch': 'for-next'}, + 'expected': 1, + }, + 'patches': [ + { + 'title': '[PATCH] test patch', + 'header-info': {'msgid': 'patch@example.com'}, + 'taken': {'commit-id': commit}, + } + ], + } + series = {'change_id': 'test-change-id'} + app = mock.Mock() + generated = email.message.EmailMessage() + + with ( + patch('b4.git_get_toplevel', return_value=gitdir), + patch('b4.review.load_tracking', return_value=('cover', tracking_data)), + patch('b4.ty.generate_am_thanks', return_value=generated), + patch('b4.get_email_signature', return_value='Test'), + ): + TrackingApp._start_thank(app, series) + + app._show_thank_preview.assert_called_once_with( + generated, + series, + checkurl=f'https://git.kernel.org/example/spi/c/{commit[:12]}', + checkcommit=commit, + archive_after=False, + checkrepo=checkrepo, + checkbranch='for-next', + ) + def test_partial_series_ingests_new_revision(self, gitdir: str) -> None: """A 'partial' series must ingest an incoming v2 and record it. diff --git a/src/tests/test_ty.py b/src/tests/test_ty.py index be132ff..50ec18b 100644 --- a/src/tests/test_ty.py +++ b/src/tests/test_ty.py @@ -7,6 +7,7 @@ from unittest import mock import pytest import b4 +import b4.command import b4.ty @@ -62,6 +63,20 @@ def test_render_ty_review_layout() -> None: assert b4.ty.parse_ty_review(buf, sections) == set() +def test_cmd_ty_reloads_config_for_gitdir(monkeypatch: pytest.MonkeyPatch) -> None: + """-g makes repository-local thank-you settings come from that tree.""" + cmdargs = mock.Mock(gitdir='/other/repo') + setup_config = mock.Mock() + ty_main = mock.Mock() + monkeypatch.setattr(b4, 'setup_config', setup_config) + monkeypatch.setattr(b4.ty, 'main', ty_main) + + b4.command.cmd_ty(cmdargs) + + setup_config.assert_called_once_with(cmdargs, topdir='/other/repo') + ty_main.assert_called_once_with(cmdargs) + + def test_parse_ty_review_marks_skips() -> None: """Flipping '+' to 'x' on an item marks it (by position) skipped.""" sections = _review_sections() @@ -436,6 +451,79 @@ def test_commit_reachable_uses_the_gitdir_it_is_given( ) +@pytest.mark.parametrize( + 'repo,mask', + [ + ( + 'https://git.kernel.org/pub/scm/linux/kernel/git/example/spi.git', + 'https://git.kernel.org/example/spi/c/%.12s', + ), + ('https://github.com/user/repo.git', 'https://github.com/user/repo/commit/%s'), + ( + 'https://gitlab.com/group/repo.git', + 'https://gitlab.com/group/repo/-/commit/%s', + ), + # Unrecognized host, and transports we cannot map to a web view + ('https://example.com/spi.git', None), + ('git://git.kernel.org/pub/scm/linux/kernel/git/example/spi.git', None), + ('git@github.com:user/repo.git', None), + ], +) +def test_derive_commit_url_mask(repo: str, mask: Optional[str]) -> None: + assert b4.ty._derive_commit_url_mask(repo) == mask + + +def test_get_commit_url_mask_priority(monkeypatch: pytest.MonkeyPatch) -> None: + """Mask resolution: explicit per-remote mask, then derivation from the + per-remote b4-check-repo, then the global mask, then derivation from + b4.thanks-check-repo and finally from the branch's remote URL.""" + binfo = {'url': 'https://github.com/user/repo.git'} + + # Nothing configured: the branch's remote URL is the last resort + assert ( + b4.ty._get_commit_url_mask(b4.MAIN_CONFIG, binfo) + == 'https://github.com/user/repo/commit/%s' + ) + # A check repo we cannot derive from does not stop the search + monkeypatch.setitem( + b4.MAIN_CONFIG, 'thanks-check-repo', 'https://example.com/r.git' + ) + assert ( + b4.ty._get_commit_url_mask(b4.MAIN_CONFIG, binfo) + == 'https://github.com/user/repo/commit/%s' + ) + # b4.thanks-check-repo beats the remote URL + monkeypatch.setitem( + b4.MAIN_CONFIG, 'thanks-check-repo', 'https://gitlab.com/group/repo.git' + ) + assert ( + b4.ty._get_commit_url_mask(b4.MAIN_CONFIG, binfo) + == 'https://gitlab.com/group/repo/-/commit/%s' + ) + # The global mask beats anything derived from a check repo + monkeypatch.setitem( + b4.MAIN_CONFIG, 'thanks-commit-url-mask', 'https://example.com/c/%.8s' + ) + assert ( + b4.ty._get_commit_url_mask(b4.MAIN_CONFIG, binfo) + == 'https://example.com/c/%.8s' + ) + # remote..b4-check-repo beats the global mask + binfo['b4-check-repo'] = ( + 'https://git.kernel.org/pub/scm/linux/kernel/git/example/spi.git' + ) + assert ( + b4.ty._get_commit_url_mask(b4.MAIN_CONFIG, binfo) + == 'https://git.kernel.org/example/spi/c/%.12s' + ) + # ... and an explicit per-remote mask beats everything + binfo['b4-commit-url-mask'] = 'https://example.com/spi/c/%.10s' + assert ( + b4.ty._get_commit_url_mask(b4.MAIN_CONFIG, binfo) + == 'https://example.com/spi/c/%.10s' + ) + + def test_get_check_repo_for_branch_priority( monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path ) -> None: -- 2.47.3