From 8e9681cf7d433fc555055b9c14facdf13a9f40fd Mon Sep 17 00:00:00 2001 From: emil Date: Wed, 24 Jun 2026 10:54:58 +0000 Subject: [PATCH] DEVX-31: fix: prefer branch name for task ID extraction + strip heads/ prefix in release --- src/devx/ci/auto_merge.py | 16 +++++++++------- src/devx/ci/release.py | 2 ++ tests/unit/test_auto_merge.py | 9 ++++++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/devx/ci/auto_merge.py b/src/devx/ci/auto_merge.py index 45d051e..6e505f2 100644 --- a/src/devx/ci/auto_merge.py +++ b/src/devx/ci/auto_merge.py @@ -63,20 +63,22 @@ def run_cmd(args: list[str], check: bool = True) -> subprocess.CompletedProcess[ def read_taskid(branch: str) -> str: - """Read task ID from .taskid file, falling back to branch name extraction. + """Read task ID from branch name, falling back to .taskid file. - The .taskid file is a simple text file containing just the task ID - (e.g., ``DEVX-60``). If the file doesn't exist, extract from the - branch name as a backwards-compatibility fallback. + The branch name is the primary source of truth for the task ID + (e.g., ``DEVX-31-fix-foo`` → ``DEVX-31``). The ``.taskid`` file + is a legacy fallback for branches without a task ID prefix. """ + branch_task_id = extract_task_id(branch) + if branch_task_id: + return branch_task_id + # Fallback: read from .taskid file path = Path(TASKID_FILE) if path.exists(): task_id = path.read_text(encoding="utf-8").strip() if task_id: return task_id - # Fallback: extract from branch name - match = TASK_ID_RE.search(branch) - return match.group(0) if match else "" + return "" def extract_task_id(branch: str) -> str: diff --git a/src/devx/ci/release.py b/src/devx/ci/release.py index 9227957..1146941 100644 --- a/src/devx/ci/release.py +++ b/src/devx/ci/release.py @@ -547,6 +547,8 @@ def main(dry_run: bool, skip_tests: bool, verify: bool) -> None: # Ensure we're on master (skip this check in dry-run mode for PR validation) branch = run_cmd(["git", "rev-parse", "--abbrev-ref", "HEAD"]).stdout.strip() + # Some git versions return "heads/master" instead of "master" + branch = branch.removeprefix("heads/") if branch != "master" and not dry_run: raise click.ClickException(_("Release must be run on master, currently on '{branch}'.", branch=branch)) if branch != "master" and dry_run: diff --git a/tests/unit/test_auto_merge.py b/tests/unit/test_auto_merge.py index 1a8fdd9..726aca4 100644 --- a/tests/unit/test_auto_merge.py +++ b/tests/unit/test_auto_merge.py @@ -21,9 +21,16 @@ from devx.exceptions import APIError class TestReadTaskid: - def test_reads_from_file(self, tmp_path, monkeypatch) -> None: # type: ignore[no-untyped-def] + def test_prefers_branch_name_over_file(self, tmp_path, monkeypatch) -> None: # type: ignore[no-untyped-def] monkeypatch.chdir(tmp_path) (tmp_path / ".taskid").write_text("DEVX-60\n") + # Branch name takes priority over .taskid file + assert read_taskid("DEVX-19-fix-bug") == "DEVX-19" + + def test_falls_back_to_file_when_no_branch_match(self, tmp_path, monkeypatch) -> None: # type: ignore[no-untyped-def] + monkeypatch.chdir(tmp_path) + (tmp_path / ".taskid").write_text("DEVX-60\n") + # No task ID in branch name → fall back to .taskid assert read_taskid("some-branch") == "DEVX-60" def test_falls_back_to_branch_name(self, tmp_path, monkeypatch) -> None: # type: ignore[no-untyped-def]