Skip to content

Commit a14881c

Browse files
committed
config: harden git history rewrite guidance
1 parent fe92225 commit a14881c

4 files changed

Lines changed: 88 additions & 8 deletions

File tree

client/plugin/skills/common/git-amend/first-use.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ git commit --amend
112112
git rebase --continue
113113
```
114114

115+
For repository-wide policy fixes (for example, rewriting many historical commit messages or authors), do **not** chain
116+
many ad hoc amend/rebase operations. Use `cat:git-rewrite-history` with explicit concrete refs instead.
117+
115118
## Error Recovery
116119

117120
If amend was wrong, check reflog for original:

client/plugin/skills/common/git-rebase/first-use.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ When a backup_branch was created before the error, its name appears on a `backup
130130
- If this rebase was a retry after a prior failed attempt, also delete the prior attempt's backup branch
131131
(branches matching `backup-before-rebase-*`)
132132
- The backup exists only during verification — leaving it permanently clutters the repository
133+
- If you will run any repo-wide history audit afterward (commit-message policy, author policy, etc.), delete
134+
backup branches before the audit so old pre-rebase commits do not remain reachable.
133135

134136
**On CONFLICT status:** Resolve conflicts using the numbered steps in [Handling Conflicts](#handling-conflicts) below.
135137
After resolution is complete (or error handled): `git branch -D <backup_branch>`

client/plugin/skills/common/git-rewrite-history/first-use.md

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,53 +57,120 @@ rewriting:
5757
git branch backup-before-rewrite-$(date +%Y%m%d-%H%M%S)-$$-$RANDOM
5858
```
5959

60-
### Step 3: Run git filter-repo
60+
### Step 3: Freeze the exact ref scope
61+
62+
**Do not pass namespace shorthands such as `refs/heads` or broad selectors such as `--all`/`--branches`.**
63+
`git-filter-repo --refs` expects concrete ref names and rewrites only that explicit set.
64+
65+
Capture the exact refs you intend to rewrite before running the command:
66+
67+
```bash
68+
git for-each-ref --format='%(refname)' refs/heads refs/tags refs/remotes > refs-to-rewrite.txt
69+
```
70+
71+
Then remove anything you do **not** want rewritten (backup branches, stale remote-tracking refs, temporary refs):
72+
73+
```bash
74+
grep -v '^refs/heads/backup-before-rewrite-' refs-to-rewrite.txt > refs-to-rewrite.filtered
75+
mv refs-to-rewrite.filtered refs-to-rewrite.txt
76+
```
77+
78+
**Important:** `--refs` implies `--partial`. This is the correct mode for targeted history surgery because it:
79+
80+
- rewrites only the listed refs
81+
- keeps remote configuration intact
82+
- avoids remapping unrelated refs automatically
83+
84+
If you omit `--refs` and run a full rewrite, git-filter-repo may intentionally remove the `origin` remote as a safety
85+
measure. When a full rewrite is truly required, record the current remote configuration first:
86+
87+
```bash
88+
git remote -v > remotes.before-rewrite.txt
89+
git config --local --get-regexp '^remote\\.' >> remotes.before-rewrite.txt
90+
```
91+
92+
Expand the ref file as a single `--refs` argument followed by all concrete refs:
93+
94+
```bash
95+
REF_ARGS=$(tr '\n' ' ' < refs-to-rewrite.txt)
96+
```
97+
98+
Do **not** repeat `--refs` once per line. `git-filter-repo` expects one `--refs` followed by many ref arguments.
99+
100+
### Step 4: Run git filter-repo
61101

62102
Use `"$FILTER_REPO"` in place of `git filter-repo` for all operations. The `--force` flag is required
63103
because the working directory is not a fresh clone.
64104

65105
**Remove a file from all history:**
66106

67107
```bash
68-
"$FILTER_REPO" --path secrets.txt --invert-paths --force
108+
"$FILTER_REPO" --refs $REF_ARGS \
109+
--path secrets.txt --invert-paths --force
69110
```
70111

71112
**Remove a directory from all history:**
72113

73114
```bash
74-
"$FILTER_REPO" --path vendor/ --invert-paths --force
115+
"$FILTER_REPO" --refs $REF_ARGS \
116+
--path vendor/ --invert-paths --force
75117
```
76118

77119
**Remove large files:**
78120

79121
```bash
80-
"$FILTER_REPO" --strip-blobs-bigger-than 10M --force
122+
"$FILTER_REPO" --refs $REF_ARGS \
123+
--strip-blobs-bigger-than 10M --force
81124
```
82125

83126
**Replace text patterns (remove secrets by substitution):**
84127

85128
Create `expressions.txt` with lines of the form `old_value==>new_value`, then:
86129

87130
```bash
88-
eval "$FILTER_REPO" --replace-text expressions.txt --force
131+
"$FILTER_REPO" --refs $REF_ARGS \
132+
--replace-text expressions.txt --force
89133
```
90134

135+
**Replace exact commit-message text safely:**
136+
137+
Create `messages.txt` with lines of the form `old text==>new text`, then:
138+
139+
```bash
140+
"$FILTER_REPO" --refs $REF_ARGS \
141+
--replace-message messages.txt --force
142+
```
143+
144+
Use `--replace-message` when you are replacing exact known subjects or phrases. It is safer than writing a callback for
145+
simple message rewrites.
146+
147+
**Callback choice matters:**
148+
149+
- `--message-callback` receives only the message text. Use it for text-only transforms.
150+
- `--commit-callback` receives the full commit object. Use it when the rewrite depends on commit metadata such as the
151+
original commit ID.
152+
153+
Do **not** try to key message rewrites by original commit hash from `--message-callback`; that callback does not expose
154+
the commit object.
155+
91156
**Drop specific commits:**
92157

93158
git filter-repo does not support dropping individual commits by hash directly. Use the `cat:git-rebase`
94159
skill with `--onto <parent-of-commit> <commit> <branch>` arguments to replay commits after the dropped
95160
commit onto its parent. (MANDATORY: always use `cat:git-rebase` instead of running `git rebase`
96161
directly.)
97162

98-
### Step 4: Verify the result
163+
### Step 5: Verify the result
99164

100165
- [ ] Target files/paths no longer appear in history: `git log --all -- <path>`
101166
- [ ] Commit count matches expectations
102167
- [ ] No unexpected files were removed: `git diff --stat <old-commit>..<new-head>`
168+
- [ ] All rewritten refs point at the new history you expected
169+
- [ ] Remote configuration still matches expectations (or has been deliberately restored)
103170
- [ ] Build succeeds
104171
- [ ] Tests pass
105172

106-
### Step 5: Propagate the rewritten history
173+
### Step 6: Propagate the rewritten history
107174

108175
1. **All collaborators must re-clone** or reset their branches — history has been rewritten.
109176
2. **Force push required**: Run `cat:git-rewrite-history` first, then:
@@ -113,14 +180,18 @@ directly.)
113180
3. **Update any CI/CD** that caches the old commits.
114181
4. **GitHub/GitLab**: May need to run server-side garbage collection to purge old objects.
115182

116-
### Step 6: Clean up the backup branch
183+
### Step 7: Clean up the backup branch
117184

118185
Once history is verified correct, delete the backup branch:
119186

120187
```bash
121188
git branch -D backup-before-rewrite-<timestamp>
122189
```
123190

191+
If you are validating repository-wide commit policies after the rewrite, delete the backup branch **before** running the
192+
audit. Keeping the backup branch preserves the pre-rewrite commits as reachable history and can make the audit report
193+
false failures against the old subjects or authors.
194+
124195
---
125196

126197
## Recovery

client/plugin/skills/common/git-squash/first-use.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ accidental file loss.
246246
4. **Verify immediately** — no changes lost or added
247247
5. Cleanup backup only after verification passes
248248

249+
If you plan to run a repository-wide history audit after squashing, delete the backup branch before that audit. A
250+
surviving backup branch keeps the pre-squash commits reachable and can make history-policy validation report the old
251+
commits instead of the squashed result.
252+
249253
**Scope verification before any manual or interactive rewrite:** Before creating a squashed commit by any path other
250254
than the deterministic helper's `OK` result, compare the candidate diff against the intended topic:
251255

0 commit comments

Comments
 (0)