Skip to content

fix: preserve AI attribution through git revert and revert-of-revert (ISSUE-004, 015)#967

Open
svarlamov wants to merge 11 commits intomainfrom
worktree-agent-a97fd8c7
Open

fix: preserve AI attribution through git revert and revert-of-revert (ISSUE-004, 015)#967
svarlamov wants to merge 11 commits intomainfrom
worktree-agent-a97fd8c7

Conversation

@svarlamov
Copy link
Copy Markdown
Member

@svarlamov svarlamov commented Apr 5, 2026

Summary

  • Adds pre_revert_hook and post_revert_hook to intercept git revert operations in wrapper mode
  • Post-revert handler traverses the reverted commit's note chain to restore AI attribution
  • Handles revert-of-revert case: when a revert commit is reverted, attribution is sourced from the original AI commit (one level of chain traversal)
  • Simple reverts of AI commits remain attribution-neutral (ai_additions=0) since they only delete lines
  • Emits RevertMixed event to the rewrite log (was previously defined but never emitted)
  • Daemon mode: adds SemanticEvent::RevertComplete and RevertAbort, registers "revert" in AnalyzerRegistry, implements apply_revert_attribution_side_effect and revert_source_ref_from_command for full daemon-mode parity

Updates since last revision

  • Fixed find_reverted_commit bare keyword check: changed continue/abort/quit/skip to --continue/--abort/--quit/--skip and returns None (control mode, no commit to find), matching how git actually accepts these and consistent with daemon-side revert_source_ref_from_command
  • Added RevertAbort handling in daemon mode: HistoryAnalyzer now checks for --abort before emitting RevertComplete (previously a git revert --abort that changed HEAD would emit a spurious RevertComplete). Added SemanticEvent::RevertAbort variant, wired through reducer and daemon event handler (no-op — abort needs no attribution or rewrite log entry)
  • All Devin Review comments resolved (18/18)

Test plan

  • test_revert_of_revert_restores_ai_attribution passes
  • test_revert_of_ai_commit_is_attribution_neutral passes
  • test_revert_preserves_original_commit_note passes
  • All existing cherry-pick tests pass (49/49)
  • Format, Lint (all platforms), Ubuntu wrapper/daemon/wrapper-daemon tests green
  • cargo clippy with -D warnings clean

Review & Testing Checklist for Human

  • Daemon-mode revert attribution is untested in CI — integration tests use TestRepo (wrapper mode). Manually verify apply_revert_attribution_side_effect works by running a revert with daemon mode enabled and checking git notes --ref=ai show HEAD
  • is_revert_commit heuristic: relies on commit message starting with "Revert ". Will fail if user passes --edit to customize the message or uses a non-English git locale — verify this is acceptable
  • Single-level chain traversal only: revert-of-revert-of-revert (3+ levels deep) won't preserve attribution. Confirm this is acceptable scope
  • revert_source_ref_from_command arg parsing: verify edge cases like git revert -m 1 --gpg-sign abc123 and git revert --strategy=recursive abc123 (equals-sign form) — the daemon-side parser does not handle --flag=value syntax for -m/--strategy/-X, which could cause the flag value to be mistaken for the commit ref
  • RevertMixedEvent fallback: in the daemon event handler, if revert_source_ref_from_command returns None, the event is emitted with an empty reverted_commit string — verify downstream consumers handle this gracefully

Notes

  • CI failures are pre-existing flaky tests unrelated to this PR:
    • utf8_filenames::test_arabic_filename (ubuntu daemon) — daemon completion log timeout
    • daemon_restart_brings_up_new_process (windows wrapper) — PID file race condition
  • license/cla check is pending for the bot commit; expected and not blocking

Link to Devin session: https://app.devin.ai/sessions/64198314ca39403db3aa3ac6f342e34f
Requested by: @svarlamov


Open with Devin

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

svarlamov and others added 6 commits April 5, 2026 20:37
… ISSUE-015)

When a revert commit is itself reverted, the restored content now correctly
inherits the original AI attribution by traversing one level of the revert
chain. Simple reverts of AI commits remain attribution-neutral (ai=0) since
they only delete lines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add RevertComplete semantic event and wire up revert attribution in the
daemon pipeline so revert-of-revert note copying works in daemon and
wrapper-daemon modes, not just wrapper mode.

Changes:
- Add "revert" to trace_command_may_mutate_refs and ORIG_HEAD tracking
- Add RevertComplete variant to SemanticEvent enum
- Emit RevertComplete from the history analyzer for "revert" commands
- Convert RevertComplete to RevertMixed rewrite log events
- Apply revert attribution side effect (note copying) in daemon mode
- Add RevertComplete to reducer event filter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Skip flag value arguments (-m, --strategy, etc.) in find_reverted_commit
  to avoid misinterpreting "1" as a commit ref in `git revert -m 1 abc123`
- Replace ? operator with match/continue in find_reverted_commit to avoid
  premature function return on UTF-8 parse failure
- Guard parent chain traversal with is_revert_commit check (commit message
  starts with "Revert ") to prevent false attribution when reverting a
  normal human commit whose parent happens to be an AI commit
- Apply the same is_revert_commit guard in daemon-mode side effect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply rustfmt to daemon.rs and fix Devin review feedback: -s is
--signoff (boolean) not --strategy (value-taking) in git revert,
so remove it from the skip-next-arg list in find_reverted_commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The trace normalizer's command_may_mutate_refs list was missing "revert",
causing the daemon to skip mutation state capture (pre/post repo context,
ref changes) for revert commands. Without this data, head_change() cannot
detect the HEAD change and no RevertComplete event is emitted, so the
revert-of-revert attribution side effect never fires in daemon mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@svarlamov svarlamov force-pushed the worktree-agent-a97fd8c7 branch from 4466c72 to 6d22a78 Compare April 5, 2026 20:39
svarlamov and others added 2 commits April 5, 2026 20:40
…g log

Addresses Devin review finding: byte-level slice on commit summary
could panic on multi-byte UTF-8 characters (CJK, emoji, etc.).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent fires

Without 'revert' in the HistoryAnalyzer registry, git revert in
daemon mode was routed to the GenericAnalyzer which emits no semantic
events. RevertComplete was never fired, so apply_revert_attribution_side_effect
was never called, and revert-of-revert attribution was silently dropped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

svarlamov and others added 2 commits April 6, 2026 10:29
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ntax error from --gpg-sign removal)

Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ svarlamov
❌ devin-ai-integration[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

devin-ai-integration[bot]

This comment was marked as resolved.

…rted_commit

- HistoryAnalyzer now checks for --abort before emitting RevertComplete,
  matching cherry-pick pattern. Emits RevertAbort instead.
- Added RevertAbort variant to SemanticEvent and wired it through
  reducer and daemon event handler (no-op, abort needs no attribution).
- find_reverted_commit: changed bare keyword check (continue/abort/quit/skip)
  to use --prefix and return None, matching how git actually accepts these
  and consistent with daemon-side revert_source_ref_from_command.

Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants