From 5f726d0814eb8ff30fb535432f5ef95765d4f58c Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Tue, 23 Jun 2026 10:16:01 -0400 Subject: [PATCH] Fix push-to-main SHA resolving to bare HEAD instead of main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a push to main the tip SHA is listed by ls-remote as both the symbolic HEAD and refs/heads/main. With equal specificity, min() picked the first (HEAD), yielding tag=HEAD with no head flag — so nothing built and the test looked for dist/HEAD/cli.sh. Rank real refs (pull > merge-queue > branch > tag) above the bare HEAD so refs/heads/main wins, giving tag=main, head=true. Branch is ranked above tag so a commit-under-test that is also a release tag still gets a source build. --- otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py | 16 ++++++++++++-- otdf-sdk-mgr/tests/test_resolve.py | 28 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py b/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py index 02f35f375..9c50d2ce1 100644 --- a/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py +++ b/otdf-sdk-mgr/src/otdf_sdk_mgr/resolve.py @@ -54,12 +54,24 @@ def is_resolve_success(val: ResolveResult) -> TypeGuard[ResolveSuccess]: def _ref_specificity(ref: str) -> int: - """Sort key for refs that share a SHA: PR head, then merge-queue, then rest.""" + """Sort key for refs that share a SHA, lowest wins. + + PR head, then merge-queue, then a branch, then a tag, then anything else + (e.g. the symbolic ``HEAD`` that ``ls-remote`` lists alongside the branch + it points at — never a useful dist tag on its own). A branch is preferred + over a tag because the SHA path resolves a commit-under-test: the branch + case flags it as a ``head`` so it is built from source, whereas a tag that + happens to point at the same commit would not be. + """ if ref.startswith("refs/pull/"): return 0 if re.match(MERGE_QUEUE_REGEX, ref): return 1 - return 2 + if ref.startswith("refs/heads/"): + return 2 + if ref.startswith("refs/tags/"): + return 3 + return 4 def _classify_sha_match( diff --git a/otdf-sdk-mgr/tests/test_resolve.py b/otdf-sdk-mgr/tests/test_resolve.py index dd665f184..a7c30b057 100644 --- a/otdf-sdk-mgr/tests/test_resolve.py +++ b/otdf-sdk-mgr/tests/test_resolve.py @@ -182,6 +182,34 @@ def test_single_match_branch_flagged_head(self): assert result.get("head") is True assert result["tag"] == "feature--my-branch" + def test_head_and_branch_prefers_branch(self): + # On a push to main the tip SHA is listed by ls-remote as both the + # symbolic HEAD and refs/heads/main. Prefer the branch so the dist tag + # is "main" (built as a head), not the useless "HEAD". + ls = make_ls_remote( + (SHA40, "HEAD"), + (SHA40, "refs/heads/main"), + ) + with patch_git(ls): + result = resolve("go", SHA40, None) + assert is_resolve_success(result) + assert result["tag"] == "main" + assert result.get("head") is True + + def test_branch_preferred_over_tag(self): + # A commit-under-test that is both a branch tip and a release tag must + # resolve to the branch so it gets head=True (and a source build), + # rather than a tag that would not be built. + ls = make_ls_remote( + (SHA40, "refs/tags/otdfctl/v0.33.0"), + (SHA40, "refs/heads/main"), + ) + with patch_git(ls): + result = resolve("go", SHA40, None) + assert is_resolve_success(result) + assert result["tag"] == "main" + assert result.get("head") is True + # --------------------------------------------------------------------------- # resolve() — refs/pull/NNN