diff --git a/api/updates.py b/api/updates.py index 9e1d9f4e..e3e025c2 100644 --- a/api/updates.py +++ b/api/updates.py @@ -150,6 +150,25 @@ WEBUI_VERSION: str = _detect_webui_version() AGENT_VERSION: str = _detect_agent_version() +def _normalize_remote_url(remote_url): + """Return the browser-facing repository URL for update compare links. + + Git remotes may be HTTPS or SSH and may include a literal ``.git`` suffix. + Strip only that literal suffix — never use ``str.rstrip('.git')`` because it + treats the argument as a character set and can truncate ``hermes-webui`` to + ``hermes-webu``. + """ + if not remote_url: + return remote_url + remote_url = remote_url.strip() + if remote_url.startswith('git@'): + remote_url = remote_url.replace(':', '/', 1).replace('git@', 'https://', 1) + remote_url = remote_url.rstrip('/') + if remote_url.endswith('.git'): + remote_url = remote_url[:-4] + return remote_url.rstrip('/') + + def _split_remote_ref(ref): """Split 'origin/branch-name' into ('origin', 'branch-name'). @@ -234,11 +253,7 @@ def _check_repo(path, name): # Get repo URL for "What's new?" link remote_url, _ = _run_git(['remote', 'get-url', 'origin'], path) - # Convert SSH URLs (git@github.com:org/repo.git) to HTTPS - if remote_url and remote_url.startswith('git@'): - remote_url = remote_url.replace(':', '/', 1).replace('git@', 'https://', 1) - if remote_url and remote_url.endswith('.git'): - remote_url = remote_url[:-4] + remote_url = _normalize_remote_url(remote_url) return { 'name': name, diff --git a/tests/test_update_banner_fixes.py b/tests/test_update_banner_fixes.py index 357dace1..5eaee61e 100644 --- a/tests/test_update_banner_fixes.py +++ b/tests/test_update_banner_fixes.py @@ -79,6 +79,31 @@ class TestUpdateChecker: assert result['repo_url'] == 'https://github.com/NousResearch/hermes-agent' + def test_repo_url_strips_dot_git_before_trailing_slashes(self, tmp_path, monkeypatch): + import api.updates as upd + + (tmp_path / '.git').mkdir() + + def fake_run(args, cwd, timeout=10): + if args[0] == 'fetch': + return '', True + if args[:2] == ['rev-parse', '--abbrev-ref']: + return 'origin/master', True + if args[:2] == ['rev-list', '--count']: + return '2', True + if args[0] == 'merge-base': + return 'abcdef1234567890', True + if args[:2] == ['rev-parse', '--short']: + return 'abcdef1', True + if args[:2] == ['remote', 'get-url']: + return 'https://github.com/nesquena/hermes-webui.git/', True + return '', True + + monkeypatch.setattr(upd, '_run_git', fake_run) + result = upd._check_repo(tmp_path, 'webui') + + assert result['repo_url'] == 'https://github.com/nesquena/hermes-webui' + class TestConflictError: """#813 — conflict error must include flag + recovery command."""