Skip to content

Commit 1a46ec6

Browse files
authored
Merge branch 'develop' into user/ihhethan/SWDEV-561821
2 parents 999ab09 + 067d86d commit 1a46ec6

File tree

609 files changed

+45998
-6574
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

609 files changed

+45998
-6574
lines changed

.github/CODEOWNERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
# rocr-runtime section-specific code owners
4545
/projects/rocr-runtime/libhsakmt @kentrussell @dayatsin-amd @cfreeamd
4646
/projects/rocr-runtime/libhsakmt/tests/kfdtest @kentrussell @xiaogang-chen-amd
47+
/projects/rocr-runtime/libhsakmt/include/impl @dayatsin-amd @cfreeamd @gandryey @chrispaquot
48+
/projects/rocr-runtime/libhsakmt/src/dxg @dayatsin-amd @cfreeamd @gandryey @chrispaquot
4749
/projects/rocr-runtime/rocrtst @shwetagkhatri @cfreeamd
4850
/projects/rocr-runtime/runtime/hsa-runtime/core/runtime/trap_handler/*.s @lancesix @dayatsin-amd @cfreeamd @shwetagkhatri
4951
/projects/rocr-runtime/runtime/hsa-runtime/core/driver/xdna @atgutier @ypapadop-amd @dayatsin-amd @cfreeamd
@@ -52,6 +54,9 @@
5254
/projects/rocr-runtime/runtime/hsa-runtime/image @shwetagkhatri @dayatsin-amd @cfreeamd
5355
/projects/rocr-runtime/runtime/hsa-runtime/pcs @shwetagkhatri @dayatsin-amd @cfreeamd
5456

57+
# rocprof-trace-decoder code owners
58+
/projects/rocprof-trace-decoder/ @ROCm/rocm-devtools-team
59+
5560
# Documentation-specific ownership by project
5661

5762
/projects/clr/**/*.md @ROCm/clr-reviewers @ROCm/lrt-docs-reviewers
@@ -97,3 +102,4 @@
97102
/projects/rocr-runtime/**/*.rst @kentrussell @dayatsin-amd @ROCm/rocm-documentation
98103
/projects/rocr-runtime/**/.readthedocs.yaml @kentrussell @dayatsin-amd @ROCm/rocm-documentation
99104

105+
/projects/rocprof-trace-decoder/**/*.md @ROCm/rocm-devtools-team @ROCm/rocm-documentation

.github/labeler.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@
6666
- changed-files:
6767
- any-glob-to-any-file: 'projects/rocprofiler-systems/**/*'
6868

69+
"project: rocprof-trace-decoder":
70+
- changed-files:
71+
- any-glob-to-any-file: 'projects/rocprof-trace-decoder/**/*'
72+
6973
"project: rocr-runtime":
7074
- changed-files:
7175
- any-glob-to-any-file: 'projects/rocr-runtime/**/*'

.github/scripts/pr_merge_sync_patches.py

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import shutil
3333
import subprocess
3434
import tempfile
35+
from email.header import decode_header
3536
from typing import Optional, List
3637
from pathlib import Path
3738
from github_cli_client import GitHubCLIClient
@@ -103,6 +104,34 @@ def get_subtree_info(config: List[RepoEntry], subtrees: List[str]) -> List[RepoE
103104
return matched
104105

105106

107+
def _get_patch_range(merge_sha: str) -> tuple[str, str]:
108+
"""Derive the commit range for patch-back from the merge commit.
109+
110+
- Squash merge (1 parent): range = parent..merge (single squashed commit).
111+
- Rebase and merge (1 parent): range = parent..merge; only the tip commit is
112+
included (earlier rebased commits are not patched back; limitation).
113+
- Merge commit (2 parents): range = first_parent..second_parent (PR branch only).
114+
115+
Returns: (base_sha, range_end) so the range is base_sha..range_end.
116+
"""
117+
out = _run_git(["rev-list", "--parents", "-n", "1", merge_sha])
118+
parts = out.split()
119+
if len(parts) < 2:
120+
raise RuntimeError(f"Could not read parents of merge commit {merge_sha}")
121+
merge_full = parts[0]
122+
parents = parts[1:]
123+
if len(parents) == 2:
124+
# Merge commit: first parent = base at merge time, second = PR branch tip
125+
return parents[0], parents[1]
126+
if len(parents) == 1:
127+
# One parent: squash (1 commit) or rebase-and-merge (N commits). The range is
128+
# parent..merge (exclusive of parent), which is exactly the PR's commits.
129+
return parents[0], merge_full
130+
raise RuntimeError(
131+
f"Merge commit {merge_sha} has {len(parents)} parents; expected 1 or 2."
132+
)
133+
134+
106135
def _run_git(args: List[str], cwd: Optional[Path] = None) -> str:
107136
"""Run a git command and return stdout."""
108137
cmd = ["git"] + args
@@ -167,16 +196,32 @@ def _stage_changes(repo_path: Path) -> None:
167196
logger.debug(f"Staged all changes in {repo_path}")
168197

169198

199+
def _decode_rfc2047_header(value: str) -> str:
200+
"""Decode RFC 2047 encoded-word (e.g. =?UTF-8?q?foo=20bar?=) to plain text."""
201+
if "=?" not in value:
202+
return value
203+
parts = decode_header(value)
204+
result = []
205+
for part, charset in parts:
206+
if isinstance(part, bytes):
207+
result.append(part.decode(charset or "utf-8", errors="replace"))
208+
else:
209+
result.append(part or "")
210+
return "".join(result)
211+
212+
170213
def _extract_commit_message_from_patch(patch_path: Path) -> str:
171214
"""Extract and clean the original commit message from the patch file,
172-
removing '[PATCH]' and trailing PR references like (#NN) from the title."""
215+
removing '[PATCH]' and trailing PR references like (#NN) from the title.
216+
Decodes RFC 2047 (MIME) encoded subjects so subrepo commits show plain text."""
173217
with open(patch_path, "r", encoding="utf-8") as f:
174218
lines = f.readlines()
175219
commit_msg_lines = []
176220
in_msg = False
177221
for line in lines:
178222
if line.startswith("Subject: "):
179223
subject = line[len("Subject: ") :].strip()
224+
subject = _decode_rfc2047_header(subject)
180225
# Remove leading "[PATCH]" if present
181226
if subject.startswith("[PATCH]"):
182227
subject = subject[len("[PATCH]") :].strip()
@@ -261,34 +306,34 @@ def _patch_touched_paths(patch_path: Path) -> List[str]:
261306

262307
def generate_patch(
263308
prefix: str,
264-
merge_sha: str,
265309
patch_path: Path,
266310
base_sha: str,
311+
range_end: str,
267312
debug: bool = False,
268313
) -> Optional[List[Path]]:
269-
"""Generate patch file(s) for a given subtree prefix from a merge commit.
314+
"""Generate patch file(s) for a given subtree prefix from a commit range.
270315
271316
Only commits that actually touch files under the prefix are included, so every
272317
generated patch has at least one diff hunk and can be applied to the subrepo.
273318
274319
Args:
275320
prefix: The subtree prefix (e.g., "projects/rocBLAS/")
276-
merge_sha: The merge commit SHA
277321
patch_path: Path where patch file(s) should be written
278-
base_sha: Base commit SHA. Required to properly handle both squash and rebase merges.
322+
base_sha: Start of range (exclusive)
323+
range_end: End of range (inclusive). For squash: merge SHA; for merge commit: merge^2 (second parent).
279324
debug: If True, log which commits touch the prefix and each patch's paths.
280325
281326
Returns:
282327
List[Path]: List of patch file paths (one per commit that touches the subtree)
283328
None: If there are no commits that touch the prefix
284329
"""
285330
path_arg = prefix.rstrip("/")
286-
total_commits = int(_run_git(["rev-list", "--count", f"{base_sha}..{merge_sha}"]))
287-
commits = _commits_touching_prefix(base_sha, merge_sha, prefix)
331+
total_commits = int(_run_git(["rev-list", "--count", f"{base_sha}..{range_end}"]))
332+
commits = _commits_touching_prefix(base_sha, range_end, prefix)
288333

289334
if debug:
290335
logger.debug(
291-
f"Commit range {base_sha}..{merge_sha}: {total_commits} total commit(s), "
336+
f"Commit range {base_sha}..{range_end}: {total_commits} total commit(s), "
292337
f"{len(commits)} commit(s) touch subtree '{path_arg}'"
293338
)
294339
for i, sha in enumerate(commits, 1):
@@ -297,7 +342,7 @@ def generate_patch(
297342

298343
if not commits:
299344
logger.debug(
300-
f"No commits in {base_sha}..{merge_sha} touch prefix '{prefix}', skipping"
345+
f"No commits in {base_sha}..{range_end} touch prefix '{prefix}', skipping"
301346
)
302347
return None
303348

@@ -320,7 +365,7 @@ def generate_patch(
320365
patch_files = sorted(patch_dir.glob("*.patch"))
321366
if not patch_files:
322367
raise RuntimeError(
323-
f"No patch files generated for range {base_sha}..{merge_sha} with prefix '{prefix}' "
368+
f"No patch files generated for range {base_sha}..{range_end} with prefix '{prefix}' "
324369
f"(expected patches for {len(commits)} commit(s) touching subtree)."
325370
)
326371

@@ -462,15 +507,10 @@ def main(argv: Optional[List[str]] = None) -> None:
462507
return
463508
logger.debug(f"Merge commit for PR #{args.pr} in {args.repo}: {merge_sha}")
464509

465-
# Get base commit to detect if this is a rebase merge with multiple commits
466-
base_sha = client.get_pr_base_commit(args.repo, args.pr)
467-
if not base_sha:
468-
logger.error(
469-
f"Could not get base commit for PR #{args.pr} in {args.repo}. "
470-
f"Base commit is required to properly handle both squash and rebase merges."
471-
)
472-
return
473-
logger.debug(f"Base commit for PR #{args.pr} in {args.repo}: {base_sha}")
510+
# Use merge commit's parents so the range is exactly "commits from this PR", not API base..merge
511+
# (squash: merge^1..merge; merge commit: merge^1..merge^2)
512+
base_sha, range_end = _get_patch_range(merge_sha)
513+
logger.debug(f"Patch range for PR #{args.pr}: {base_sha}..{range_end}")
474514

475515
if args.keep_clone_dir:
476516
args.no_push = True
@@ -496,7 +536,7 @@ def main(argv: Optional[List[str]] = None) -> None:
496536

497537
try:
498538
patch_result = generate_patch(
499-
prefix, merge_sha, patch_dir_anchor, base_sha, debug=args.debug
539+
prefix, patch_dir_anchor, base_sha, range_end, debug=args.debug
500540
)
501541
if patch_result is None:
502542
logger.debug(f"No patches to apply for subtree {prefix}, skipping")

.github/scripts/tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Pytest configuration: ensure pr_merge_sync_patches and siblings are importable."""
2+
import sys
3+
from pathlib import Path
4+
5+
scripts_dir = Path(__file__).resolve().parent.parent
6+
if str(scripts_dir) not in sys.path:
7+
sys.path.insert(0, str(scripts_dir))
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""
2+
Unit tests for pr_merge_sync_patches: patch range derivation and patch generation.
3+
4+
- PR 3277: rebase-and-merge example (1 parent → range = parent..merge).
5+
- PR 2514: squash-merge example (1 parent → range = parent..merge).
6+
"""
7+
8+
import shutil
9+
import subprocess
10+
import tempfile
11+
from pathlib import Path
12+
from unittest.mock import patch
13+
14+
import pytest
15+
16+
# Merge SHAs from GitHub API (ROCm/rocm-systems)
17+
PR_3277_MERGE_SHA = "c6d7e08fd4b06070e37b3e9aca500224fcc8404c" # rebase-and-merge
18+
PR_2514_MERGE_SHA = "bf6c504f4f3db50457a04340d9f49f2dfc6c94c8" # squash-merge
19+
20+
21+
class TestGetPatchRangeWithMocks:
22+
"""Test _get_patch_range with mocked git (no repo required)."""
23+
24+
def test_squash_merge_one_parent_returns_parent_and_merge(self):
25+
# One parent: squash or rebase-and-merge → range = parent..merge
26+
from pr_merge_sync_patches import _get_patch_range
27+
28+
merge_sha = "cccc000000000000000000000000000000000000"
29+
parent_sha = "aaaa000000000000000000000000000000000000"
30+
rev_list_out = f"{merge_sha} {parent_sha}"
31+
32+
with patch("pr_merge_sync_patches._run_git", return_value=rev_list_out):
33+
base, range_end = _get_patch_range(merge_sha)
34+
assert base == parent_sha
35+
assert range_end == merge_sha
36+
37+
def test_merge_commit_two_parents_returns_first_and_second_parent(self):
38+
from pr_merge_sync_patches import _get_patch_range
39+
40+
merge_sha = "cccc000000000000000000000000000000000000"
41+
first_parent = "aaaa000000000000000000000000000000000000"
42+
second_parent = "bbbb000000000000000000000000000000000000"
43+
rev_list_out = f"{merge_sha} {first_parent} {second_parent}"
44+
45+
with patch("pr_merge_sync_patches._run_git", return_value=rev_list_out):
46+
base, range_end = _get_patch_range(merge_sha)
47+
assert base == first_parent
48+
assert range_end == second_parent
49+
50+
51+
def _is_rocm_systems_repo_and_has_commits():
52+
"""True if cwd looks like rocm-systems and the test merge SHAs exist."""
53+
try:
54+
root = Path(__file__).resolve().parents[2] # .github/scripts/tests -> repo root
55+
r = subprocess.run(
56+
["git", "rev-parse", "--is-inside-work-tree"],
57+
cwd=root,
58+
capture_output=True,
59+
text=True,
60+
)
61+
if r.returncode != 0:
62+
return False
63+
for sha in (PR_3277_MERGE_SHA, PR_2514_MERGE_SHA):
64+
r2 = subprocess.run(
65+
["git", "cat-file", "-t", sha],
66+
cwd=root,
67+
capture_output=True,
68+
text=True,
69+
)
70+
if r2.returncode != 0 or "commit" not in (r2.stdout or ""):
71+
return False
72+
return True
73+
except Exception:
74+
return False
75+
76+
77+
@pytest.mark.skipif(
78+
not _is_rocm_systems_repo_and_has_commits(),
79+
reason="Not in rocm-systems repo or merge SHAs (3277/2514) not available",
80+
)
81+
class TestGetPatchRangeRealRepo:
82+
"""Integration tests: real git in rocm-systems repo with known PR merge SHAs."""
83+
84+
def test_pr_3277_rebase_merge_range(self):
85+
"""PR 3277 (rebase-and-merge): range = parent..merge, 1 commit in range for rocr-debug-agent."""
86+
from pr_merge_sync_patches import _get_patch_range
87+
88+
base, range_end = _get_patch_range(PR_3277_MERGE_SHA)
89+
# Merge commit c6d7e08 has one parent b3e0321
90+
assert base.startswith("b3e0321")
91+
assert range_end.startswith("c6d7e08")
92+
93+
def test_pr_2514_squash_merge_range(self):
94+
"""PR 2514 (squash-merge): range = parent..merge, 1 commit in range."""
95+
from pr_merge_sync_patches import _get_patch_range
96+
97+
base, range_end = _get_patch_range(PR_2514_MERGE_SHA)
98+
# Merge bf6c504 has one parent a5b467d
99+
assert base.startswith("a5b467d")
100+
assert range_end.startswith("bf6c504")
101+
102+
103+
def _patch_test_dir():
104+
"""Temp dir under repo for patch-generation tests (avoids pytest tmp_path permissions)."""
105+
root = Path(__file__).resolve().parents[2]
106+
return Path(tempfile.mkdtemp(prefix="patch_test_", dir=str(root)))
107+
108+
109+
@pytest.mark.skipif(
110+
not _is_rocm_systems_repo_and_has_commits(),
111+
reason="Not in rocm-systems repo or merge SHAs not available",
112+
)
113+
class TestPatchGenerationRealRepo:
114+
"""Integration tests: generate_patch for known PRs (no apply)."""
115+
116+
def test_pr_3277_generates_patches_for_rocr_debug_agent(self):
117+
"""PR 3277 touches projects/rocr-debug-agent: at least one patch, all touch subtree."""
118+
from pr_merge_sync_patches import _get_patch_range, generate_patch
119+
120+
tmp = _patch_test_dir()
121+
try:
122+
base, range_end = _get_patch_range(PR_3277_MERGE_SHA)
123+
prefix = "projects/rocr-debug-agent/"
124+
anchor = tmp / "rocr-debug-agent.patch"
125+
patch_files = generate_patch(prefix, anchor, base, range_end, debug=False)
126+
assert patch_files is not None
127+
assert len(patch_files) >= 1
128+
for p in patch_files:
129+
assert p.exists()
130+
content = p.read_text(encoding="utf-8")
131+
assert "rocr-debug-agent" in content or "debug_agent" in content
132+
finally:
133+
shutil.rmtree(tmp, ignore_errors=True)
134+
135+
def test_pr_2514_generates_patches_for_clr(self):
136+
"""PR 2514 (squash) touches projects/clr: exactly one patch."""
137+
from pr_merge_sync_patches import _get_patch_range, generate_patch
138+
139+
tmp = _patch_test_dir()
140+
try:
141+
base, range_end = _get_patch_range(PR_2514_MERGE_SHA)
142+
prefix = "projects/clr/"
143+
anchor = tmp / "clr.patch"
144+
patch_files = generate_patch(prefix, anchor, base, range_end, debug=False)
145+
assert patch_files is not None
146+
assert len(patch_files) == 1
147+
assert patch_files[0].exists()
148+
finally:
149+
shutil.rmtree(tmp, ignore_errors=True)

0 commit comments

Comments
 (0)