feat(push): add --no-sign to push unsigned commits

pkgmgr push --no-sign skips the re-sign rebase and the unsigned-range
refusal, so a checkout without a usable signing key can still sync the
fetch URL and every mirror pushurl. Leases and the git cherry guard
still apply. push_branch takes sign as a required keyword; pkgmgr
release and release --retry pass sign=True, so releases stay signed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-01 01:55:31 +01:00
1 parent 944b4cb663
commit f40cc5cfde
6 files changed
+60 -23

No files matched your search

+1 -1
View File
@@ -51,7 +51,7 @@ def retry_release(
tag = max(tags, key=SemVer.parse)
print(f"Re-pushing existing tag: {tag}")
push_branch(".", [], refs=(tag,), preview=preview)
push_branch(".", [], sign=True, refs=(tag,), preview=preview)
try:
if is_highest_version_tag(tag):
+2 -2
View File
@@ -139,7 +139,7 @@ def _release_impl(
commit(commit_msg, all=True, preview=True)
sign_unpushed(".", preview=True)
tag_annotated(new_tag, tag_msg, preview=True)
push_branch(".", [], refs=(new_tag,), preview=True)
push_branch(".", [], sign=True, refs=(new_tag,), preview=True)
if is_highest_version_tag(new_tag):
update_latest_tag(new_tag, preview=True)
@@ -162,7 +162,7 @@ def _release_impl(
sign_unpushed(".", preview=False)
tag_annotated(new_tag, tag_msg, preview=False)
push_branch(".", [], refs=(new_tag,), preview=False)
push_branch(".", [], sign=True, refs=(new_tag,), preview=False)
# Update 'latest' only if this is the highest version tag
try:
+10 -3
View File
@@ -129,6 +129,7 @@ def push_branch(
repo_dir: str,
extra_args: list[str],
*,
sign: bool,
refs: tuple[str, ...] = (),
rewrite: bool = False,
preview: bool = False,
@@ -144,12 +145,14 @@ def push_branch(
Args:
repo_dir: checkout whose current branch is pushed.
extra_args: flags appended to every `git push`.
sign: refuse unsigned commits in the push range; False pushes them as they are.
refs: extra refs (e.g. a release tag) pushed to every URL.
rewrite: replace commits of the old canonical tip; the fetch URL skips
the cherry check, mirrors only refuse commits the old tip never had.
preview: print the pushes instead of running them.
"""
branch, remote = _push_target(repo_dir)
if sign:
_base, signatures = _unpushed_signatures(repo_dir, remote)
if not preview and any(s != "G" for s in signatures):
raise PushRefusedError(
@@ -216,11 +219,12 @@ def push_branch(
def _push_one(
repo_dir: str, extra_args: list[str], rewrite: bool, preview: bool
repo_dir: str, extra_args: list[str], sign: bool, rewrite: bool, preview: bool
) -> tuple[bool, str]:
try:
if sign:
sign_unpushed(repo_dir, preview=preview)
push_branch(repo_dir, extra_args, rewrite=rewrite, preview=preview)
push_branch(repo_dir, extra_args, sign=sign, rewrite=rewrite, preview=preview)
return (True, "")
except (GitRunError, PushRefusedError) as exc:
return (False, str(exc))
@@ -230,6 +234,7 @@ def push_repo_dirs(
repos: list[RepoRef],
extra_args: list[str],
preview: bool,
sign: bool,
jobs: int = 1,
rewrite: bool = False,
) -> None:
@@ -238,7 +243,7 @@ def push_repo_dirs(
"""
run_on_repos(
repos,
lambda rd: _push_one(rd, extra_args, rewrite, preview),
lambda rd: _push_one(rd, extra_args, sign, rewrite, preview),
jobs=jobs,
op_name="push",
)
@@ -250,6 +255,7 @@ def push_in_parallel(
all_repos: list[Repository],
extra_args: list[str],
preview: bool,
sign: bool,
jobs: int = 1,
rewrite: bool = False,
) -> None:
@@ -260,6 +266,7 @@ def push_in_parallel(
resolve_repos(selected_repos, repositories_base_dir, all_repos),
extra_args,
preview,
sign,
jobs=jobs,
rewrite=rewrite,
)
+11
View File
@@ -196,6 +196,15 @@ def register_proxy_commands(
"already has (amend, squash, drop); leases still apply."
),
)
parser.add_argument(
"--no-sign",
action="store_true",
default=False,
help=(
"Push unsigned commits as they are instead of re-signing "
"them; leases still apply."
),
)
if subcommand == "clone":
parser.add_argument(
"--clone-mode",
@@ -230,6 +239,7 @@ def maybe_handle_proxy(args: argparse.Namespace, ctx: CLIContext) -> bool:
[(toplevel, toplevel)],
args.extra_args,
args.preview,
not args.no_sign,
jobs=args.jobs,
rewrite=args.rewrite,
)
@@ -277,6 +287,7 @@ def maybe_handle_proxy(args: argparse.Namespace, ctx: CLIContext) -> bool:
ctx.all_repositories,
args.extra_args,
args.preview,
not args.no_sign,
jobs=args.jobs,
rewrite=args.rewrite,
)
+30 -15
View File
@@ -80,9 +80,27 @@ class TestPushMirrorLeases(unittest.TestCase):
def _push(self, rewrite: bool = False) -> str:
with redirect_stdout(io.StringIO()) as out:
sign_unpushed(self.repo)
push_branch(self.repo, [], rewrite=rewrite)
push_branch(self.repo, [], sign=True, rewrite=rewrite)
return out.getvalue()
def _cli_push(self, cwd: str, *argv: str) -> None:
ctx = CLIContext(
config_merged={},
repositories_base_dir=os.path.join(self.root, "base"),
all_repositories=[],
binaries_dir=os.path.join(self.root, "bin"),
user_config_path=os.path.join(self.root, "config.yaml"),
)
args = create_parser("pkgmgr").parse_args(["push", "-j", "1", *argv])
prev = os.getcwd()
os.chdir(cwd)
self.addCleanup(os.chdir, prev)
with redirect_stdout(io.StringIO()), self.assertRaises(SystemExit) as cm:
maybe_handle_proxy(args, ctx)
self.assertEqual(cm.exception.code, 0)
def _tip(self, bare: str, ref: str = "refs/heads/main") -> str:
return subprocess.run(
["git", "rev-parse", "--verify", "--quiet", ref],
@@ -164,22 +182,9 @@ class TestPushMirrorLeases(unittest.TestCase):
worktree = os.path.join(self.root, "wt")
git(self.repo, "worktree", "add", "-q", "-b", "feature", worktree)
head_before = self._commit("feature", worktree)
ctx = CLIContext(
config_merged={},
repositories_base_dir=os.path.join(self.root, "base"),
all_repositories=[],
binaries_dir=os.path.join(self.root, "bin"),
user_config_path=os.path.join(self.root, "config.yaml"),
)
args = create_parser("pkgmgr").parse_args(["push", "-j", "1"])
cwd = os.getcwd()
os.chdir(worktree)
self.addCleanup(os.chdir, cwd)
with redirect_stdout(io.StringIO()), self.assertRaises(SystemExit) as cm:
maybe_handle_proxy(args, ctx)
self._cli_push(worktree)
self.assertEqual(cm.exception.code, 0)
head = git(worktree, "rev-parse", "HEAD")
self.assertNotEqual(head, head_before)
self.assertEqual(self._tip(self.canonical, "refs/heads/feature"), head)
@@ -188,6 +193,16 @@ class TestPushMirrorLeases(unittest.TestCase):
git(worktree, "rev-parse", "--abbrev-ref", "@{u}"), "origin/feature"
)
def test_no_sign_pushes_unsigned_commits_to_every_url(self) -> None:
unsigned = self._commit("feature")
self._cli_push(self.repo, "--no-sign")
self.assertEqual(git(self.repo, "rev-parse", "HEAD"), unsigned)
self.assertEqual(self._tip(self.canonical), unsigned)
self.assertEqual(self._tip(self.mirror), unsigned)
self.assertEqual(git(self.repo, "log", "-1", "--format=%G?"), "N")
def test_release_retry_refuses_unsigned_range(self) -> None:
self._commit("release")
git(self.repo, "tag", "-a", "v1.0.0", "-m", "v1.0.0")
@@ -83,7 +83,9 @@ class TestRetryRelease(unittest.TestCase):
retry_release(pyproject_path="pyproject.toml", preview=False)
mock_push.assert_called_once_with(".", [], refs=("v1.13.4",), preview=False)
mock_push.assert_called_once_with(
".", [], sign=True, refs=("v1.13.4",), preview=False
)
mock_update_latest.assert_called_once_with("v1.13.4", preview=False)
@patch("pkgmgr.actions.release.retry.update_latest_tag")
@@ -143,7 +145,9 @@ class TestRetryRelease(unittest.TestCase):
retry_release(pyproject_path="pyproject.toml", preview=False)
mock_push.assert_called_once_with(".", [], refs=("v2.0.0",), preview=False)
mock_push.assert_called_once_with(
".", [], sign=True, refs=("v2.0.0",), preview=False
)
if __name__ == "__main__":