@@ -57,53 +57,120 @@ rewriting:
5757git 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
62102Use ` "$FILTER_REPO" ` in place of ` git filter-repo ` for all operations. The ` --force ` flag is required
63103because 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
85128Create ` 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
93158git filter-repo does not support dropping individual commits by hash directly. Use the ` cat:git-rebase `
94159skill with ` --onto <parent-of-commit> <commit> <branch> ` arguments to replay commits after the dropped
95160commit onto its parent. (MANDATORY: always use ` cat:git-rebase ` instead of running ` git rebase `
96161directly.)
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
1081751 . ** All collaborators must re-clone** or reset their branches — history has been rewritten.
1091762 . ** Force push required** : Run ` cat:git-rewrite-history ` first, then:
@@ -113,14 +180,18 @@ directly.)
1131803 . ** Update any CI/CD** that caches the old commits.
1141814 . ** 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
118185Once history is verified correct, delete the backup branch:
119186
120187``` bash
121188git 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
0 commit comments