diff --git a/hermes_cli/update_cmd.py b/hermes_cli/update_cmd.py index 9481de8a97..6445a5e1d2 100644 --- a/hermes_cli/update_cmd.py +++ b/hermes_cli/update_cmd.py @@ -1384,11 +1384,8 @@ def _cmd_update_impl(args, gateway_mode: bool): if release_sha: fetch_result = _git_run(git_cmd, ["fetch", "--no-tags", "origin", target_ref], network=True) else: - # Explicit refspec so the tracking ref lands even on narrow clones whose only - # configured refspec is a tag (#125112): a branch-name fetch would leave the - # target unresolvable and the checkout/compare below would fail. fetch_result = _git_run( - git_cmd, ["fetch", "origin", f"+refs/heads/{branch}:refs/remotes/origin/{branch}"], network=True) + git_cmd, ["fetch", "origin", _check.tracking_refspec("origin", branch)], network=True) if fetch_result.returncode != 0: _print_fetch_failure(fetch_result.stderr) _m()._resume_windows_gateways_after_update(_windows_gateway_resume) diff --git a/hermes_cli/update_cmd_check.py b/hermes_cli/update_cmd_check.py index 0ee7fe8335..c541e03f32 100644 --- a/hermes_cli/update_cmd_check.py +++ b/hermes_cli/update_cmd_check.py @@ -71,13 +71,21 @@ def is_shallow_repository(git_cmd: list[str], root: Path) -> bool: return _git(git_cmd, root, ["rev-parse", "--is-shallow-repository"]).stdout.strip() == "true" +def tracking_refspec(remote: str, branch: str) -> str: + """Refspec that always writes ``/``, whatever ``remote..fetch`` says. + + Narrow clones (tag-pinned ``--single-branch``, #125112) map only the tag, so a by-name fetch + writes FETCH_HEAD and never the tracking ref the updater resolves. The ``+`` is load-bearing: + on a depth-1 clone the new tip is not a descendant of the old one. + """ + return f"+refs/heads/{branch}:refs/remotes/{remote}/{branch}" + + def _fetch(git_cmd: list[str], root: Path, depth_args: list[str], remote: str, branch: str): print(f"→ Fetching from {remote}...") - # Fetch by explicit refspec: on narrow clones (tag-pinned --single-branch, #125112) the - # configured remote..fetch maps only the tag, so a branch-name fetch writes - # FETCH_HEAD and never creates the tracking ref the compare step resolves. - refspec = f"+refs/heads/{branch}:refs/remotes/{remote}/{branch}" - return _git(git_cmd, root, ["fetch", *depth_args, remote, refspec], **_uc()._no_prompt_git_kwargs()) + return _git( + git_cmd, root, ["fetch", *depth_args, remote, tracking_refspec(remote, branch)], + **_uc()._no_prompt_git_kwargs()) def fetch_compare_branch(git_cmd: list[str], root: Path, branch: str, depth_args: list[str]): diff --git a/hermes_cli/update_cmd_git.py b/hermes_cli/update_cmd_git.py index 59dac5e9c4..eddb579c7c 100644 --- a/hermes_cli/update_cmd_git.py +++ b/hermes_cli/update_cmd_git.py @@ -335,15 +335,14 @@ def _sync_with_upstream_if_needed(git_cmd: list[str], cwd: Path, *, assume_yes: See #97052. """ from hermes_cli.update_cmd import _count_commits_between, _has_upstream_remote, _no_prompt_git_kwargs, _should_skip_upstream_prompt + from hermes_cli.update_cmd_check import tracking_refspec if not _has_upstream_remote(git_cmd, cwd) and ( _should_skip_upstream_prompt() or not _offer_upstream_remote(git_cmd, cwd, assume_yes=assume_yes, input_fn=input_fn) ): return False print("\n→ Fetching upstream...") try: - # Explicit refspec (#125112): tag-pinned narrow clones never build upstream/main - # from a branch-name fetch, and the comparison below resolves it. - subprocess.run(git_cmd + ["fetch", "upstream", "+refs/heads/main:refs/remotes/upstream/main", "--quiet"], cwd=cwd, capture_output=True, check=True, **_no_prompt_git_kwargs()) + subprocess.run(git_cmd + ["fetch", "upstream", tracking_refspec("upstream", "main"), "--quiet"], cwd=cwd, capture_output=True, check=True, **_no_prompt_git_kwargs()) except subprocess.CalledProcessError: print(" āœ— Failed to fetch upstream. Skipping upstream sync.") return False