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:
@@ -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
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user