Skip to content

Brownfield re-sign: recommend filter-branch + committer rewrite, and clarify ruleset-blocking/ordering #163

Description

@ptr727

Concrete findings from running the brownfield migration (README "Template - GitHub Setup") on a real repo (ptr727/ESPHome-NonRoot: ~123 commits/branch, 47 merges, 51 unsigned back to root, 82 tags). The procedure works but several things would have saved time / avoided footguns:

1. For merge-heavy history, prefer filter-branch - and it needs a committer rewrite to verify

The procedure lists git rebase --root --rebase-merges --exec 'git commit --amend --no-edit -S' as primary and filter-branch as a deprecated fallback. For a history with many merge commits, the rebase is fragile (complex todo, easy to derail); git filter-branch --commit-filter rewrites every commit object in place, preserving the exact DAG with zero conflicts, and operating on both refs at once keeps shared ancestry consistent (same new SHA on both branches). It was by far the smoother tool here.

Crucial caveat the docs omit: filter-branch's commit-filter preserves the original committer, so signing alone yields committer != signer and GitHub does not mark the commit Verified - which means Require signed commits still rejects it. You must rewrite the committer to the signing identity:

FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch --commit-filter '
  GIT_COMMITTER_NAME="You" GIT_COMMITTER_EMAIL="you@users.noreply.github.com" git commit-tree -S "$@"
' -- develop main

(rebase --amend -S sets committer to the rewriter automatically, which is why it verifies - worth stating explicitly so the filter-branch path isn't a silent trap.)

2. Ordering: re-sign while rulesets are permissive, and the blocker is per-branch

Step 2 says "temporarily set the ruleset Enforcement to Disabled ... since the admin bypass won't permit the force-push." In practice:

  • If you re-sign before importing the strict rulesets (i.e. while the repo still has permissive/no rules), you avoid the disable/re-enable dance entirely. Recommend stating the order: re-sign + force-push first, import strict rulesets after.
  • The push rejection is per-branch and depends on which rule each ruleset has. On a partially-configured repo I hit: develop (deletion-only ruleset) accepted the force-push, but main (had a pull_request rule) rejected it with GH013 ... Changes must be made through a pull request - which doesn't read like a force-push error. Worth noting that either Require a pull request or Block force pushes will reject the direct push, and admin bypass covers neither for git force-push, so the ruleset must be disabled/removed on each affected branch.

3. Tags are orphaned by a root rewrite

A full-history re-sign leaves existing tags (82 release tags here) pointing at the pre-rewrite commits. The Require signed commits rule applies to branches, not tags, so leaving them is fine (the old commits stay reachable via the tags); add --tag-name-filter cat only if you want tags to follow the rewrite. The procedure should call this out so 80+ "dangling" tags don't surprise the maintainer.

4. Verify via the API, not local %G?

Locally git log --pretty=%G? showed G only because allowed_signers happened to be configured; on a fresh machine it can show U (good, untrusted) even though the signature is fine. The authoritative post-push check is:

gh api repos/<owner>/<repo>/commits/<branch> --jq '.commit.verification'   # expect verified: true, reason: valid

5. Post-rewrite cleanup

Open bot PRs/branches based on the old history go stale after the force-push; note to let Dependabot recreate them (or rebase) so reviewers aren't confused.

6. Minor

git filter-branch printed envsubst: not found (git-sh-setup i18n) on a WSL runner - harmless, but startling; a one-line "this warning is benign" note would help.


Surfaced while completing the brownfield migration for ptr727/ESPHome-NonRoot (see ptr727/ESPHome-NonRoot#61).

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions