fix(sign-push): force rebase so -S actually re-signs the tip
`git rebase <base>` is a no-op when HEAD is already a descendant of <base>, which is the normal shape for a local branch built on top of origin/main. Without `--force-rebase`, rebase short-circuits, `-S` never runs, and the unsigned commit gets pushed and rejected by required_signatures branch rules. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
## [1.1.1] - 2026-04-24
|
||||
|
||||
* `git-sign-push`: pass `--force-rebase` to the signing rebase so the tip commit actually gets re-signed when HEAD is already a descendant of the base (otherwise `git rebase <base>` is a no-op and the unsigned commit gets pushed).
|
||||
|
||||
## [1.1.0] - 2026-04-24
|
||||
|
||||
* `git-setup-remotes` now pins `branch.main.pushRemote` to `origin` so direct pushes on the canonical branch never target the personal fork.
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "git-maintainer-tools"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
description = "Small CLIs (git-setup-remotes, git-sign-push) for fork-based OSS maintainer workflows."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -77,12 +77,18 @@ def resign(base: str) -> None:
|
||||
`GIT_SEQUENCE_EDITOR=:` turns the interactive rebase TODO-list editor
|
||||
into a no-op so the rebase just replays the existing commits without
|
||||
reordering or squashing. `-S` signs each replayed commit.
|
||||
|
||||
`--force-rebase` is required: without it, `git rebase <base>` is a no-op
|
||||
when HEAD is already a descendant of `base` (the normal shape for a
|
||||
local branch built on top of `origin/main`). The rebase then never
|
||||
replays any commits, `-S` signs nothing, and the still-unsigned tip
|
||||
gets pushed and rejected by `required_signatures` branch rules.
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
env["GIT_SEQUENCE_EDITOR"] = ":"
|
||||
import subprocess
|
||||
proc = subprocess.run(
|
||||
["git", "rebase", "--rebase-merges", "-S", base],
|
||||
["git", "rebase", "--rebase-merges", "--force-rebase", "-S", base],
|
||||
env=env,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
|
||||
28
tests/test_resign.py
Normal file
28
tests/test_resign.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""Guard the rebase invocation used to re-sign pending commits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from git_maintainer_tools import sign_push
|
||||
|
||||
|
||||
def test_resign_passes_force_rebase(monkeypatch):
|
||||
"""`git rebase <base>` is a no-op when HEAD already descends from base.
|
||||
|
||||
Without `--force-rebase`, `-S` would never replay any commit and the
|
||||
tip would stay unsigned, so push-time `required_signatures` rules
|
||||
reject it. This test pins the flag in place.
|
||||
"""
|
||||
fake_run = MagicMock(return_value=MagicMock(returncode=0))
|
||||
monkeypatch.setattr(subprocess, "run", fake_run)
|
||||
|
||||
sign_push.resign("abc123")
|
||||
|
||||
called_cmd = fake_run.call_args.args[0]
|
||||
assert called_cmd[0] == "git"
|
||||
assert "rebase" in called_cmd
|
||||
assert "--force-rebase" in called_cmd
|
||||
assert "-S" in called_cmd
|
||||
assert called_cmd[-1] == "abc123"
|
||||
Reference in New Issue
Block a user