From 40d9cb1087a467d620804a6aac3c2f280439f5c4 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Fri, 11 Sep 2026 14:54:27 +0100 Subject: [PATCH] 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. --- README.md | 4 ++-- app.go | 22 ++++++++++++++++++---- app_apply_test.go | 11 +++++++++++ edit.sh | 16 +++++++++++----- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5290e97..f457ff3 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app.go b/app.go index 6b23b72..211536b 100644 --- a/app.go +++ b/app.go @@ -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 || diff --git a/app_apply_test.go b/app_apply_test.go index 05841a1..6c9c035 100644 --- a/app_apply_test.go +++ b/app_apply_test.go @@ -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 \nSigned-off-by: Bob \n\nCo-Authored-By: Claude Opus 5 \nClaude-Session: https://example.com/b\nSomething else\n\nCo-authored-by: Codex \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 \n\ntail" { + t.Fatalf("unexpected cleaned message: %q", cleaned) + } +} + func TestRemoveCoAuthorLinesOnlyMatchesWholeTrailerLines(t *testing.T) { message := "subject\r\n\r\nCo-authored-by: Ada \r\n CO-AUTHORED BY: Grace \r\nNote: Co-authored-by: this is prose" cleaned, removed := removeCoAuthorLines(message) diff --git a/edit.sh b/edit.sh index 4e142d2..86cd2cb 100755 --- a/edit.sh +++ b/edit.sh @@ -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