Remove Claude/Codex attribution blocks and Claude-Session lines

Co-author cleanup now removes a Claude or Codex Co-authored-by line
together with the non-blank lines that follow it, removes any
Claude-Session trailer line, and collapses the blank lines left behind.
Other Co-authored-by lines are still removed individually.
This commit is contained in:
Andy Dixon
2026-09-11 14:55:30 +01:00
parent dfe23bd404
commit 40d9cb1087
4 changed files with 42 additions and 11 deletions
+2 -2
View File
@@ -13,7 +13,7 @@ Repository: https://github.com/andydixon/nexus
- committer name, email, and date/time
- full commit message
- Keyboard-driven calendar and time picker for author and committer timestamps
- Opt-in bulk removal of `Co-authored-by:` trailer lines across commit messages
- Opt-in bulk removal of `Co-authored-by:` and `Claude-Session:` lines across commit messages; Claude and Codex attribution blocks are removed whole
- Search commits by hash, message, author name, or email
- Bulk replace author names and emails using case-insensitive partial or exact matching
- Track edited commits and reset the selected commit
@@ -31,7 +31,7 @@ Repository: https://github.com/andydixon/nexus
- `r`: reload history
- `x`: reset the selected commit
- `a`: apply changes
- `c`: toggle bulk removal of `Co-authored-by:` lines
- `c`: toggle bulk removal of `Co-authored-by:` trailers and `Claude-Session:` lines
- `f`: toggle force push
- `t`: toggle tag push
- `?`: show help
+18 -4
View File
@@ -529,21 +529,35 @@ func formatGitDate(value time.Time) string {
}
var coAuthorLinePattern = regexp.MustCompile(`(?i)^\s*co-authored(?:-|\s+)by\s*:`)
var aiAttributionPattern = regexp.MustCompile(`(?i)^\s*co-authored(?:-|\s+)by\s*:.*\b(claude|codex)\b`)
// removeCoAuthorLines drops Co-authored-by trailer lines and Claude-Session:
// lines. A Claude or Codex co-author line also takes the contiguous non-blank
// lines that follow it (its attribution block). Returns the number of lines removed.
func removeCoAuthorLines(message string) (string, int) {
lines := strings.Split(strings.ReplaceAll(message, "\r\n", "\n"), "\n")
kept := make([]string, 0, len(lines))
removed := 0
inBlock := false
for _, line := range lines {
if coAuthorLinePattern.MatchString(line) {
removed++
switch {
case aiAttributionPattern.MatchString(line):
inBlock = true
case inBlock && strings.TrimSpace(line) != "":
case coAuthorLinePattern.MatchString(line), strings.Contains(strings.ToLower(line), "claude-session:"):
inBlock = false
default:
inBlock = false
kept = append(kept, line)
continue
}
kept = append(kept, line)
removed++
}
return normalizeMessage(strings.Join(kept, "\n")), removed
return normalizeMessage(blankRunPattern.ReplaceAllString(strings.Join(kept, "\n"), "\n\n")), removed
}
var blankRunPattern = regexp.MustCompile(`\n(?:[ \t]*\n){2,}`)
func editableFieldsChanged(current CommitRecord, incoming CommitRecord) bool {
return current.AuthorName != incoming.AuthorName ||
current.AuthorEmail != incoming.AuthorEmail ||
+11
View File
@@ -178,6 +178,17 @@ func TestApplyChangesUpdatesDatesAndRemovesCoAuthorTrailers(t *testing.T) {
}
}
func TestRemoveCoAuthorLinesDropsAIAttributionBlocks(t *testing.T) {
message := "subject\n\nClaude-Session: https://example.com/a\nbody\n\nCo-authored-by: Ada <ada@example.com>\nSigned-off-by: Bob <bob@example.com>\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://example.com/b\nSomething else\n\nCo-authored-by: Codex <codex@openai.com>\nGenerated by Codex\n\ntail"
cleaned, removed := removeCoAuthorLines(message)
if removed != 7 {
t.Fatalf("expected seven removed lines, got %d", removed)
}
if cleaned != "subject\n\nbody\n\nSigned-off-by: Bob <bob@example.com>\n\ntail" {
t.Fatalf("unexpected cleaned message: %q", cleaned)
}
}
func TestRemoveCoAuthorLinesOnlyMatchesWholeTrailerLines(t *testing.T) {
message := "subject\r\n\r\nCo-authored-by: Ada <ada@example.com>\r\n CO-AUTHORED BY: Grace <grace@example.com>\r\nNote: Co-authored-by: this is prose"
cleaned, removed := removeCoAuthorLines(message)
+11 -5
View File
@@ -10,7 +10,7 @@ Usage:
What it does:
- Rewrites reachable Git history in the current repository.
- Removes lines starting with "Co-Authored" from commit messages.
- Removes "Co-Authored" and "Claude-Session:" lines; Claude/Codex co-author blocks are removed whole.
- Sets author and committer name/email to the values you provide.
- Moves commit times to either:
00:00-07:30
@@ -166,15 +166,21 @@ PY
# Write the message callback.
cat >"${MESSAGE_CALLBACK_FILE}" <<'PY'
# Remove any line that starts with "Co-Authored", case-insensitively.
# Remove Claude/Codex co-author lines together with the non-blank lines that
# follow them, then any other "Co-Authored" or "Claude-Session:" lines.
message = re.sub(
br'(?im)^[ \t]*Co-Authored[^\r\n]*(?:\r?\n|$)',
br'(?im)^[ \t]*Co-Authored[^\r\n]*\b(?:claude|codex)\b[^\r\n]*(?:\r?\n[^\r\n]+)*(?:\r?\n|$)',
b'',
message,
)
message = re.sub(
br'(?im)^(?:[ \t]*Co-Authored|[^\r\n]*Claude-Session:)[^\r\n]*(?:\r?\n|$)',
b'',
message,
)
# Trim excessive blank lines from the end of the message.
message = re.sub(br'(?:\r?\n){3,}$', b'\n\n', message)
# Collapse runs of blank lines left behind and trim the end of the message.
message = re.sub(br'(?:\r?\n){3,}', b'\n\n', message)
# Return the rewritten message bytes.
return message