Git Signoff
Requirement
Every commit produced or rewritten by the task must include a Signed-off-by trailer that matches the current Git committer identity for that repository.
Compute the expected trailer from Git before committing or validating:
signoff_identity="$(git var GIT_COMMITTER_IDENT | sed -E 's/ [0-9]+ [-+][0-9]+$//')"
signoff_trailer="Signed-off-by: $signoff_identity"
Do not assume a personal name or email. Always derive the identity from the repository's active Git configuration.
This applies to ordinary commits and to history-changing operations such as amend, rebase, cherry-pick, squash/fixup, merge commits, revert commits, and conflict-resolution commits.
Before pushing a branch, audit every outgoing commit in the upstream range. Do not rely only on commits created during the current turn; a local branch may already be ahead with older unsigned commits.
Workflow
- Before changing history, inspect the current branch and worktree:
git status --short
git branch --show-current
git rev-parse --abbrev-ref --symbolic-full-name @{u}
If there is no upstream, choose an explicit base only when it is clear from context. Otherwise ask before rebasing or validating a commit range.
- Prefer signoff-aware commands when creating commits:
git commit -s -m "Subject"
git commit --amend -s --no-edit
git cherry-pick -s <commit>
git revert -s <commit>
git merge --signoff <branch>
git rebase --signoff <base>
If the local Git identity might not produce the required exact trailer, amend with git interpret-trailers and verify.
- After any operation that creates or rewrites commits, validate every affected commit, not just
HEAD.
For a single commit:
signoff_identity="$(git var GIT_COMMITTER_IDENT | sed -E 's/ [0-9]+ [-+][0-9]+$//')"
signoff_trailer="Signed-off-by: $signoff_identity"
git log -1 --format=%B | rg -Fx "$signoff_trailer"
For a branch range:
base="$(git merge-base HEAD @{u})"
signoff_identity="$(git var GIT_COMMITTER_IDENT | sed -E 's/ [0-9]+ [-+][0-9]+$//')"
signoff_trailer="Signed-off-by: $signoff_identity"
git rev-list --reverse "$base"..HEAD |
while read -r commit; do
git log -1 --format=%B "$commit" |
rg -Fxq "$signoff_trailer" ||
echo "$commit"
done
The range check prints commits missing the required trailer. No output means the range is signed off.
- Before any
git push, always inspect the branch state and validate the full outgoing range when an upstream exists:
git status --short --branch
upstream="$(git rev-parse --abbrev-ref --symbolic-full-name @{u})"
signoff_identity="$(git var GIT_COMMITTER_IDENT | sed -E 's/ [0-9]+ [-+][0-9]+$//')"
signoff_trailer="Signed-off-by: $signoff_identity"
git rev-list --reverse "$upstream"..HEAD |
while read -r commit; do
git log -1 --format=%B "$commit" |
rg -Fxq "$signoff_trailer" ||
git log -1 --format='%H %s' "$commit"
done
The pre-push check must print no commits before pushing. If it prints any commit, repair the outgoing range first, then re-run the check. If the branch has no upstream, choose an explicit remote/base only when it is obvious from the user request; otherwise ask before validating or pushing.
When a history rewrite was needed to add missing trailers to commits already on the remote, push with --force-with-lease, not plain --force.
Repair
For only HEAD, add the trailer without changing the patch:
tmp_msg="$(mktemp)"
signoff_identity="$(git var GIT_COMMITTER_IDENT | sed -E 's/ [0-9]+ [-+][0-9]+$//')"
git log -1 --format=%B |
git interpret-trailers --if-exists addIfDifferent \
--trailer "Signed-off-by: $signoff_identity" > "$tmp_msg"
git commit --amend -F "$tmp_msg"
rm -f "$tmp_msg"
For multiple local commits, use an interactive rebase with an exec step that amends each picked commit:
base="$(git merge-base HEAD @{u})"
GIT_SEQUENCE_EDITOR=: git rebase -i --exec 'tmp_msg="$(mktemp)" && signoff_identity="$(git var GIT_COMMITTER_IDENT | sed -E "s/ [0-9]+ [-+][0-9]+$//")" && git log -1 --format=%B | git interpret-trailers --if-exists addIfDifferent --trailer "Signed-off-by: $signoff_identity" > "$tmp_msg" && git commit --amend -F "$tmp_msg" && rm -f "$tmp_msg"' "$base"
After repair, rerun the range validation.
If the repair changed commits that were already pushed, rerun the pre-push outgoing range check against the upstream. The branch will usually show both ahead and behind until the rewritten history is pushed with --force-with-lease.
Notes
- Do not use
--no-verifyto bypass signoff checks unless the user explicitly asks and understands the policy impact. - Do not rewrite commits that are not part of the current task unless the user asks.
- If a rebase, cherry-pick, or merge stops for conflicts, resolve conflicts first, continue the operation, then rerun signoff validation for the affected range.