Skip to content

Commit 0936bca

Browse files
committed
Fix Observatory stack trace links
1 parent 6a3afad commit 0936bca

3 files changed

Lines changed: 154 additions & 5 deletions

File tree

devtools/observatory/lenses/stack_trace.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ def _get_stack_trace(cls) -> List[Dict[str, Any]]:
3535
continue
3636

3737
github_link = None
38-
if git_info.github_link and repo_root:
38+
link_root = git_info.commit_blob_url or git_info.branch_blob_url or git_info.github_link
39+
if link_root and repo_root:
3940
try:
4041
rel_path = os.path.relpath(frame_info.filename, repo_root)
41-
github_link = f"{git_info.github_link}/{rel_path}#L{frame_info.lineno}"
42+
github_link = f"{link_root}/{rel_path}#L{frame_info.lineno}"
4243
except Exception:
4344
pass
4445

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright (c) Qualcomm Innovation Center, Inc.
2+
# All rights reserved
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
from __future__ import annotations
8+
9+
from types import SimpleNamespace
10+
11+
import pytest
12+
13+
from executorch.devtools.observatory.lenses.stack_trace import StackTraceLens
14+
from executorch.devtools.observatory import utils
15+
16+
17+
@pytest.mark.parametrize(
18+
"remote_url, expected",
19+
[
20+
("git@github.com:pytorch/executorch.git", "https://github.com/pytorch/executorch"),
21+
("git@github.qualcomm.com:MLG/executorch", "https://github.qualcomm.com/MLG/executorch"),
22+
(
23+
"ssh://git@github.enterprise.local:8022/org/repo.git",
24+
"https://github.enterprise.local:8022/org/repo",
25+
),
26+
("https://github.com/pytorch/executorch.git", "https://github.com/pytorch/executorch"),
27+
("file:///tmp/executorch", None),
28+
],
29+
)
30+
def test_normalize_remote_url(remote_url, expected):
31+
assert utils._normalize_remote_url(remote_url) == expected # type: ignore[attr-defined]
32+
33+
34+
def test_stack_trace_uses_commit_links(monkeypatch, tmp_path):
35+
repo_root = tmp_path / "repo"
36+
repo_root.mkdir()
37+
source_file = repo_root / "pkg" / "module.py"
38+
source_file.parent.mkdir(parents=True)
39+
source_file.write_text("line = 1\n")
40+
41+
frame = SimpleNamespace(
42+
filename=str(source_file),
43+
function="test_fn",
44+
lineno=1,
45+
code_context=["line = 1\n"],
46+
)
47+
48+
git_info = SimpleNamespace(
49+
commit_blob_url="https://github.com/org/repo/blob/abc123",
50+
branch_blob_url="https://github.com/org/repo/blob/main",
51+
github_link="https://github.com/org/repo/tree/main",
52+
)
53+
54+
from executorch.devtools.observatory.lenses import stack_trace as stack_trace_mod
55+
56+
monkeypatch.setattr(stack_trace_mod, "inspect", SimpleNamespace(stack=lambda: [frame]))
57+
monkeypatch.setattr(stack_trace_mod, "get_repo_root", lambda: str(repo_root))
58+
monkeypatch.setattr(stack_trace_mod, "get_git_info", lambda: git_info)
59+
monkeypatch.setattr(stack_trace_mod, "is_in_repo", lambda path: str(path).startswith(str(repo_root)))
60+
61+
frames = StackTraceLens._get_stack_trace()
62+
assert len(frames) == 1
63+
assert frames[0]["link"] == "https://github.com/org/repo/blob/abc123/pkg/module.py#L1"
64+
65+
66+
def test_stack_trace_falls_back_to_branch_links(monkeypatch, tmp_path):
67+
repo_root = tmp_path / "repo"
68+
repo_root.mkdir()
69+
source_file = repo_root / "pkg" / "fallback.py"
70+
source_file.parent.mkdir(parents=True)
71+
source_file.write_text("line = 2\n")
72+
73+
frame = SimpleNamespace(
74+
filename=str(source_file),
75+
function="fallback_fn",
76+
lineno=2,
77+
code_context=["line = 2\n"],
78+
)
79+
80+
git_info = SimpleNamespace(
81+
commit_blob_url=None,
82+
branch_blob_url="https://github.com/org/repo/blob/main",
83+
github_link="https://github.com/org/repo/tree/main",
84+
)
85+
86+
from executorch.devtools.observatory.lenses import stack_trace as stack_trace_mod
87+
88+
monkeypatch.setattr(stack_trace_mod, "inspect", SimpleNamespace(stack=lambda: [frame]))
89+
monkeypatch.setattr(stack_trace_mod, "get_repo_root", lambda: str(repo_root))
90+
monkeypatch.setattr(stack_trace_mod, "get_git_info", lambda: git_info)
91+
monkeypatch.setattr(stack_trace_mod, "is_in_repo", lambda path: str(path).startswith(str(repo_root)))
92+
93+
frames = StackTraceLens._get_stack_trace()
94+
assert len(frames) == 1
95+
assert frames[0]["link"] == "https://github.com/org/repo/blob/main/pkg/fallback.py#L2"

devtools/observatory/utils.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,73 @@
1111
import subprocess
1212
from dataclasses import dataclass
1313
from typing import Optional
14+
from urllib.parse import urlparse
1415

1516

1617
@dataclass
1718
class GitInfo:
1819
"""Git repository information for source links."""
1920

2021
remote_url: Optional[str] = None
22+
remote_https_url: Optional[str] = None
2123
branch: Optional[str] = None
2224
commit_hash: Optional[str] = None
2325
is_dirty: bool = False
2426
github_link: Optional[str] = None
27+
branch_blob_url: Optional[str] = None
28+
commit_blob_url: Optional[str] = None
2529

2630

2731
_cached_git_info: Optional[GitInfo] = None
2832
_cached_repo_root: Optional[str] = None
2933

3034

35+
def _strip_git_suffix(value: str) -> str:
36+
return value[:-4] if value.endswith(".git") else value
37+
38+
39+
def _normalize_remote_url(remote_url: str) -> Optional[str]:
40+
"""Return an https URL for browser links regardless of git remote scheme."""
41+
42+
if not remote_url:
43+
return None
44+
45+
remote_url = remote_url.strip()
46+
if not remote_url:
47+
return None
48+
49+
if remote_url.startswith("git@"):
50+
try:
51+
user_host, path = remote_url.split(":", 1)
52+
except ValueError:
53+
return None
54+
host = user_host.split("@", 1)[1]
55+
cleaned_path = _strip_git_suffix(path.lstrip("/").rstrip("/"))
56+
return f"https://{host}/{cleaned_path}" if cleaned_path else f"https://{host}"
57+
58+
if remote_url.startswith("ssh://"):
59+
parsed = urlparse(remote_url)
60+
host = parsed.hostname
61+
if not host:
62+
return None
63+
port_suffix = f":{parsed.port}" if parsed.port and parsed.port not in (22,) else ""
64+
cleaned_path = _strip_git_suffix(parsed.path.lstrip("/").rstrip("/"))
65+
return f"https://{host}{port_suffix}/{cleaned_path}" if cleaned_path else f"https://{host}{port_suffix}"
66+
67+
parsed = urlparse(remote_url)
68+
if parsed.scheme in {"http", "https"}:
69+
host = parsed.hostname
70+
if not host:
71+
return None
72+
port_suffix = ""
73+
if parsed.port and not (parsed.scheme == "http" and parsed.port == 80) and not (parsed.scheme == "https" and parsed.port == 443):
74+
port_suffix = f":{parsed.port}"
75+
cleaned_path = _strip_git_suffix(parsed.path.lstrip("/").rstrip("/"))
76+
return f"https://{host}{port_suffix}/{cleaned_path}" if cleaned_path else f"https://{host}{port_suffix}"
77+
78+
return None
79+
80+
3181
def get_repo_root() -> Optional[str]:
3282
"""Return repository root, or None when unavailable."""
3383

@@ -94,6 +144,7 @@ def get_git_info() -> GitInfo:
94144
)
95145
if remote_url.returncode == 0:
96146
info.remote_url = remote_url.stdout.strip()
147+
info.remote_https_url = _normalize_remote_url(info.remote_url)
97148

98149
commit = subprocess.run(
99150
["git", "rev-parse", "HEAD"],
@@ -113,9 +164,11 @@ def get_git_info() -> GitInfo:
113164
if dirty.returncode == 0:
114165
info.is_dirty = bool(dirty.stdout.strip())
115166

116-
if info.remote_url and info.branch:
117-
base = info.remote_url[:-4] if info.remote_url.endswith(".git") else info.remote_url
118-
info.github_link = f"{base}/tree/{info.branch}"
167+
if info.remote_https_url and info.branch:
168+
info.github_link = f"{info.remote_https_url}/tree/{info.branch}"
169+
info.branch_blob_url = f"{info.remote_https_url}/blob/{info.branch}"
170+
if info.remote_https_url and info.commit_hash:
171+
info.commit_blob_url = f"{info.remote_https_url}/blob/{info.commit_hash}"
119172
except Exception as exc:
120173
logging.debug("[Observatory] Failed to query git info: %s", exc)
121174

0 commit comments

Comments
 (0)