Skip to content

Commit 0e0ca91

Browse files
docs: fix the branch-hygiene push rule — append commits mid-review (NVIDIA#2201)
Signed-off-by: Yuan Chen <yuanchen97@gmail.com> Co-authored-by: Mark Chmarny <mchmarny@users.noreply.github.com>
1 parent 669c3bb commit 0e0ca91

3 files changed

Lines changed: 76 additions & 10 deletions

File tree

.claude/CLAUDE.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -562,10 +562,24 @@ Process and unique findings below; the rule sections above (Error Wrapping, Cont
562562

563563
**Mandatory lint gate for Go changes:** If your PR changes any `.go` files, you MUST run `golangci-lint run -c .golangci.yaml` on each affected package path (e.g., `./pkg/recipe/...`, `./cmd/aicr/...`, `./tests/chainsaw/...`) and confirm zero issues before creating or pushing the PR. For a full module scan, use `./...`. Do not rely on CI to catch lint failures — fix them locally first. This applies even to PRs labeled as "documentation only" if they include Go code changes.
564564

565-
**Branch hygiene:**
566-
- Always rebase onto the target branch before pushing: `git fetch origin main && git rebase origin/main`
567-
- Squash commits into a single commit before push
568-
- Cryptographically sign commits (`git commit -S`)
565+
**Branch hygiene.** The governing idea: the SHA a reviewer is reading should be the SHA you want reviewed.
566+
567+
- **Keep the PR in draft while you are still changing it**; flip it to ready only when you want eyes on it. Draft PRs do not page reviewers, and that is the phase where rewriting history is free.
568+
- **While the PR is a draft, rebase and squash freely.** Do not key this on whether comments exist: CodeRabbit auto-reviews drafts here (`.coderabbit.yaml`, `auto_review.drafts: true`), so a bot comment lands minutes after the first push.
569+
- **Once the PR is not a draft — whether you flipped it or opened it that way — append commits instead of rewriting history.** Inline comments anchor to SHAs, and any rewrite outdates every one of them. Each appended commit dismisses approvals (`dismiss_stale_reviews_on_push` is on); that is the intended cost, since a force-push dismisses them too *and* destroys the anchors. Returning to draft stops paging reviewers and restores the draft phase's freedom to rewrite; if inline comments already exist, say on the PR that you rewrote and name the old and new SHA so reviewers know to restart.
570+
- **Do not hand-squash before merge.** `NVIDIA/aicr` is squash-merge-only, so GitHub composes the commit on `main` from the PR title; by default only the title and trailers reach `main`. Durable wording belongs in the PR title.
571+
- **Once the PR is not a draft, rebase only when the merge gate requires it** — but it does require it: the repo enforces up-to-date branches, so a PR behind `main` cannot merge. `git fetch origin main && git rebase origin/main`, never GitHub's "Update branch" button or a merge commit. A rebase is itself a force-push and can outdate anchors even when the content is unchanged, so treat it as one. To confirm it replayed your work cleanly, compare `git diff origin/main...HEAD` before and after, or use `git range-diff <old-sha>...HEAD`.
572+
- **Once the PR is not a draft, force-push only when you must** — a gate-required rebase, a missing signature or sign-off, a wrong base, a committed secret. Nothing else qualifies at that point; while it is still a draft the bullets above apply. Use exactly one of these two, never both:
573+
- `--force-with-lease=<refname>:<sha-you-observed>` — `<refname>` is the branch name as it exists on the remote, unprefixed, e.g. `git push origin my-branch --force-with-lease=my-branch:abc1234`; or
574+
- `--force-with-lease --force-if-includes` (or `push.useForceIfIncludes=true` once, globally), which adds a reflog check.
575+
576+
`--force-if-includes` is a documented no-op when the lease already names an expected SHA, so pairing them leaves you trusting a guard git has disabled. A bare `--force-with-lease` alone can silently pass, because a background fetch or a concurrent session sharing the clone may have refreshed the remote-tracking ref its lease reads. Never plain `--force`. Say on the PR that you force-pushed, naming the old and new SHA.
577+
- Verify what you are about to push: `git log --oneline origin/main..HEAD` and `git diff --stat origin/main...HEAD`.
578+
- Sign every commit both ways: `git commit -S -s` (see Git Configuration above).
579+
580+
**Responding to review:**
581+
- **Answer feedback on the thread, not only in code.** Reply with what changed and where — or why you disagreed or deferred. Do not make a reviewer diff two SHAs to find out, including when the feedback came via Slack.
582+
- Re-request review when you have finished responding to a round, and after any rewrite that changes the reviewed SHA. Not once per appended commit.
569583

570584
**Documentation updates:** When a PR adds or changes user-visible behavior (new CLI flag, API endpoint, component, recipe field, deployment pattern, environment variable, error code), update the relevant page in `docs/` in the same PR — don't defer to a follow-up. Common targets by kind of change:
571585
- CLI flag / subcommand → `docs/user/cli-reference.md`

AGENTS.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -562,10 +562,24 @@ Process and unique findings below; the rule sections above (Error Wrapping, Cont
562562

563563
**Mandatory lint gate for Go changes:** If your PR changes any `.go` files, you MUST run `golangci-lint run -c .golangci.yaml` on each affected package path (e.g., `./pkg/recipe/...`, `./cmd/aicr/...`, `./tests/chainsaw/...`) and confirm zero issues before creating or pushing the PR. For a full module scan, use `./...`. Do not rely on CI to catch lint failures — fix them locally first. This applies even to PRs labeled as "documentation only" if they include Go code changes.
564564

565-
**Branch hygiene:**
566-
- Always rebase onto the target branch before pushing: `git fetch origin main && git rebase origin/main`
567-
- Squash commits into a single commit before push
568-
- Cryptographically sign commits (`git commit -S`)
565+
**Branch hygiene.** The governing idea: the SHA a reviewer is reading should be the SHA you want reviewed.
566+
567+
- **Keep the PR in draft while you are still changing it**; flip it to ready only when you want eyes on it. Draft PRs do not page reviewers, and that is the phase where rewriting history is free.
568+
- **While the PR is a draft, rebase and squash freely.** Do not key this on whether comments exist: CodeRabbit auto-reviews drafts here (`.coderabbit.yaml`, `auto_review.drafts: true`), so a bot comment lands minutes after the first push.
569+
- **Once the PR is not a draft — whether you flipped it or opened it that way — append commits instead of rewriting history.** Inline comments anchor to SHAs, and any rewrite outdates every one of them. Each appended commit dismisses approvals (`dismiss_stale_reviews_on_push` is on); that is the intended cost, since a force-push dismisses them too *and* destroys the anchors. Returning to draft stops paging reviewers and restores the draft phase's freedom to rewrite; if inline comments already exist, say on the PR that you rewrote and name the old and new SHA so reviewers know to restart.
570+
- **Do not hand-squash before merge.** `NVIDIA/aicr` is squash-merge-only, so GitHub composes the commit on `main` from the PR title; by default only the title and trailers reach `main`. Durable wording belongs in the PR title.
571+
- **Once the PR is not a draft, rebase only when the merge gate requires it** — but it does require it: the repo enforces up-to-date branches, so a PR behind `main` cannot merge. `git fetch origin main && git rebase origin/main`, never GitHub's "Update branch" button or a merge commit. A rebase is itself a force-push and can outdate anchors even when the content is unchanged, so treat it as one. To confirm it replayed your work cleanly, compare `git diff origin/main...HEAD` before and after, or use `git range-diff <old-sha>...HEAD`.
572+
- **Once the PR is not a draft, force-push only when you must** — a gate-required rebase, a missing signature or sign-off, a wrong base, a committed secret. Nothing else qualifies at that point; while it is still a draft the bullets above apply. Use exactly one of these two, never both:
573+
- `--force-with-lease=<refname>:<sha-you-observed>` — `<refname>` is the branch name as it exists on the remote, unprefixed, e.g. `git push origin my-branch --force-with-lease=my-branch:abc1234`; or
574+
- `--force-with-lease --force-if-includes` (or `push.useForceIfIncludes=true` once, globally), which adds a reflog check.
575+
576+
`--force-if-includes` is a documented no-op when the lease already names an expected SHA, so pairing them leaves you trusting a guard git has disabled. A bare `--force-with-lease` alone can silently pass, because a background fetch or a concurrent session sharing the clone may have refreshed the remote-tracking ref its lease reads. Never plain `--force`. Say on the PR that you force-pushed, naming the old and new SHA.
577+
- Verify what you are about to push: `git log --oneline origin/main..HEAD` and `git diff --stat origin/main...HEAD`.
578+
- Sign every commit both ways: `git commit -S -s` (see Git Configuration above).
579+
580+
**Responding to review:**
581+
- **Answer feedback on the thread, not only in code.** Reply with what changed and where — or why you disagreed or deferred. Do not make a reviewer diff two SHAs to find out, including when the feedback came via Slack.
582+
- Re-request review when you have finished responding to a round, and after any rewrite that changes the reviewed SHA. Not once per appended commit.
569583

570584
**Documentation updates:** When a PR adds or changes user-visible behavior (new CLI flag, API endpoint, component, recipe field, deployment pattern, environment variable, error code), update the relevant page in `docs/` in the same PR — don't defer to a follow-up. Common targets by kind of change:
571585
- CLI flag / subcommand → `docs/user/cli-reference.md`

CONTRIBUTING.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,17 @@ Trust is established through evidence, not assertions. Every released artifact c
184184
git push origin your-branch
185185
```
186186

187+
Append commits rather than amending or rebasing while the PR is open for review.
188+
The exceptions are narrow: a catch-up rebase onto `main` that the merge gate
189+
requires when your branch falls behind, a missing signature or sign-off, a wrong
190+
base branch, or a committed secret. Say on the PR whenever you do any of them.
191+
A force-push outdates every inline comment and drops the anchors reviewers left,
192+
which makes "was this addressed?" a manual diff. There is no need to tidy the
193+
history first: pull requests merge by squash, so the branch's commits become one
194+
commit on `main` regardless. Keep the PR in draft while you are still reshaping
195+
it — draft PRs do not page reviewers, and that is the phase where rewriting
196+
history is free.
197+
187198
4. **Merge**: Once approved and CI passes, a maintainer will merge
188199

189200
### AI-Assisted Contributions Policy
@@ -284,16 +295,37 @@ Fix the most recent commit and re-push:
284295

285296
```bash
286297
git commit --amend -s -S --no-edit
287-
git push --force-with-lease origin your-branch
298+
git push --force-with-lease --force-if-includes origin your-branch
288299
```
289300

290301
For an entire branch, re-sign every commit at once:
291302

292303
```bash
293304
git rebase --exec 'git commit --amend -s -S --no-edit' origin/main
294-
git push --force-with-lease origin your-branch
305+
git push --force-with-lease --force-if-includes origin your-branch
306+
```
307+
308+
`--force-if-includes` checks your local **branch reflog**, so in a fresh clone it can
309+
reject the push even when nothing is wrong — the clone's only reflog entry is the
310+
clone itself, which is not a valid rewrite base. It fails safe. Fetching does not
311+
help, because what is missing is a local reflog entry rather than remote data; push
312+
once with a pinned lease instead. Read the remote's current tip first and check it is
313+
the commit you meant to replace, then pass that recorded value explicitly:
314+
315+
```bash
316+
git ls-remote origin your-branch # note the SHA, and confirm it is yours
317+
git push --force-with-lease=your-branch:<that-sha> origin your-branch
295318
```
296319

320+
Do not inline the lookup into the push (`--force-with-lease=your-branch:$(git ls-remote …)`).
321+
That re-reads the remote at push time, so a commit someone else pushed in the meantime
322+
becomes the expected value and is silently overwritten — the same failure the pinned
323+
lease exists to prevent.
324+
325+
If the PR is already under review, say on the PR that you force-pushed and name the
326+
old and new SHA, since re-signing rewrites every commit and outdates the inline
327+
comments.
328+
297329
### What You're Certifying
298330

299331
By signing off, you certify the Developer Certificate of Origin 1.1:
@@ -358,6 +390,12 @@ Explain the problem being solved and why this approach was chosen.
358390
Signed-off-by: Your Name <your@email.com>
359391
```
360392

393+
**Where this text ends up.** Pull requests merge by squash, and the repo composes the
394+
merged commit from the **PR title** with an empty body, so a branch commit's body is
395+
discarded at merge — only the title and trailers reach `main`. Write the body for
396+
your reviewers, and put anything that needs to outlive the PR in the PR title and
397+
description.
398+
361399
### Code Style
362400

363401
- Follow existing patterns in the codebase

0 commit comments

Comments
 (0)