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
|
- committer name, email, and date/time
|
||||||
- full commit message
|
- full commit message
|
||||||
- Keyboard-driven calendar and time picker for author and committer timestamps
|
- 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
|
- Search commits by hash, message, author name, or email
|
||||||
- Bulk replace author names and emails using case-insensitive partial or exact matching
|
- Bulk replace author names and emails using case-insensitive partial or exact matching
|
||||||
- Track edited commits and reset the selected commit
|
- Track edited commits and reset the selected commit
|
||||||
@@ -31,7 +31,7 @@ Repository: https://github.com/andydixon/nexus
|
|||||||
- `r`: reload history
|
- `r`: reload history
|
||||||
- `x`: reset the selected commit
|
- `x`: reset the selected commit
|
||||||
- `a`: apply changes
|
- `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
|
- `f`: toggle force push
|
||||||
- `t`: toggle tag push
|
- `t`: toggle tag push
|
||||||
- `?`: show help
|
- `?`: show help
|
||||||
|
|||||||
@@ -529,21 +529,35 @@ func formatGitDate(value time.Time) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var coAuthorLinePattern = regexp.MustCompile(`(?i)^\s*co-authored(?:-|\s+)by\s*:`)
|
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) {
|
func removeCoAuthorLines(message string) (string, int) {
|
||||||
lines := strings.Split(strings.ReplaceAll(message, "\r\n", "\n"), "\n")
|
lines := strings.Split(strings.ReplaceAll(message, "\r\n", "\n"), "\n")
|
||||||
kept := make([]string, 0, len(lines))
|
kept := make([]string, 0, len(lines))
|
||||||
removed := 0
|
removed := 0
|
||||||
|
inBlock := false
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if coAuthorLinePattern.MatchString(line) {
|
switch {
|
||||||
removed++
|
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
|
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 {
|
func editableFieldsChanged(current CommitRecord, incoming CommitRecord) bool {
|
||||||
return current.AuthorName != incoming.AuthorName ||
|
return current.AuthorName != incoming.AuthorName ||
|
||||||
current.AuthorEmail != incoming.AuthorEmail ||
|
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) {
|
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"
|
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)
|
cleaned, removed := removeCoAuthorLines(message)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Usage:
|
|||||||
|
|
||||||
What it does:
|
What it does:
|
||||||
- Rewrites reachable Git history in the current repository.
|
- 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.
|
- Sets author and committer name/email to the values you provide.
|
||||||
- Moves commit times to either:
|
- Moves commit times to either:
|
||||||
00:00-07:30
|
00:00-07:30
|
||||||
@@ -166,15 +166,21 @@ PY
|
|||||||
|
|
||||||
# Write the message callback.
|
# Write the message callback.
|
||||||
cat >"${MESSAGE_CALLBACK_FILE}" <<'PY'
|
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(
|
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'',
|
b'',
|
||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Trim excessive blank lines from the end of the 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)
|
message = re.sub(br'(?:\r?\n){3,}', b'\n\n', message)
|
||||||
|
|
||||||
# Return the rewritten message bytes.
|
# Return the rewritten message bytes.
|
||||||
return message
|
return message
|
||||||
|
|||||||
Reference in New Issue
Block a user