feat: add Git-AI trailer with UUID to commits and authorship notes#622
feat: add Git-AI trailer with UUID to commits and authorship notes#622
Conversation
- Add id field to AuthorshipMetadata for storing per-note UUID - Generate UUID v4 in pre-commit hook, store via RepoStorage - Inject Git-AI trailer in wrapper mode via --trailer flag - Inject Git-AI trailer in hooks mode via git interpret-trailers - Read pending UUID in post_commit and amend flows, set on metadata - Add 8 tests (+ 8 worktree variants) covering trailer/UUID behavior - Update snapshot tests for new id field Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
…for hooks mode, update git-compat whitelist - Insert --trailer before any '--' pathspec separator to avoid git treating it as a file path - Update needs_prepare_commit_msg_handling() to also return true when pending_note_id exists, fixing hooks mode trailer injection - Update git-compat whitelist to include new trailer-induced test failures Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
…s mode The pending_note_id file for linked worktrees is stored at <common_dir>/ai/worktrees/<name>/pending_note_id, but the previous check only looked at <git_dir>/ai/pending_note_id. Extract a pending_note_id_exists helper that checks both the non-worktree and worktree paths. Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
… resolution Replace manual worktree path construction in pending_note_id_exists() with the canonical worktree_storage_ai_dir() helper. Add resolve_git_common_dir() to read git's commondir file for linked worktrees, matching how git itself resolves the common directory. Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
…rebase - Rebase (rewrite_authorship_after_rebase_v2): preserve source commit note id on recompute path - Cherry-pick (rewrite_authorship_after_cherry_pick): preserve source commit note id on recompute path - CI squash/rebase (rewrite_authorship_after_squash_or_rebase): generate new UUID for merge notes - Metadata-only notes (build_metadata_only_authorship_log_from_source_notes): generate new UUID - Add 11 TDD tests covering all three rewrite flows including worktree variants Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
…us commit's id Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
…o prevent stale UUID leak Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
|
Addressed the stale Full code review summary: All 5 Devin Review issues have been addressed:
Additional review findings (no issues):
|
…duplicate Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
feat: add Git-AI trailer with UUID to commits and authorship notes
Summary
Adds a
Git-AI: <uuid>trailer to every commit message and stores the same UUID as a newidfield in the authorship note metadata, creating a link between the two. Also ensures theidis preserved or generated across all rewrite flows (rebase, cherry-pick, CI squash/rebase).How it works (commit flow):
.git/ai/pending_note_idtemp file viaRepoStorage--trailer "Git-AI: <uuid>"into the git commit args forwarded to real git (inserted before any--pathspec separator). A-c trailer.Git-AI.ifExists=replaceglobal arg ensures--amendreplaces the original trailer rather than appending a duplicate.prepare-commit-msg, usegit interpret-trailers --in-place --if-exists replaceto append (or replace) the trailer in the commit message file. Theneeds_prepare_commit_msg_handling()gate was updated to also trigger when apending_note_idfile exists. A cherry-pick guard prevents stale UUIDs from aborted commits leaking into cherry-picked messages.authorship_log.metadata.id, then clean up the temp fileHow it works (rewrite flows):
rewrite_authorship_after_rebase_v2): On the full recompute path, reads the original commit's noteidviaget_reference_as_authorship_log_v3and carries it to the rewritten commit's note. Uses.and_then()chaining to always resetideach loop iteration, preventing stale id leakage when a source note lookup fails. The fast-path remap already preservedidsince it only touchesbase_commit_sha.rewrite_authorship_after_cherry_pick): Same pattern — source commit'sidis read and set on the cherry-picked commit's note during recomputation. Freshauthorship_logper iteration means no cross-iteration leak risk.rewrite_authorship_after_squash_or_rebase): Generates a new UUID v4 for the merge note since there's no single source commit to inherit from.build_metadata_only_authorship_log_from_source_notes): Also generates a new UUID v4 for notes that carry only prompt metadata without file attestations.The
idfield onAuthorshipMetadataisOption<String>with#[serde(default, skip_serializing_if = "Option::is_none")]for backward compatibility with existing notes.Worktree support:
pending_note_id_exists()delegates to the canonicalworktree_storage_ai_dir()helper (madepub(crate)) for path resolution. A newresolve_git_common_dir()reads git'scommondirfile from linked worktree git directories, matching how git itself resolves the common directory — no manual path reconstruction needed.Review & Testing Checklist for Human
--amendproduces exactly oneGit-AItrailer: Rungit commit --amend --no-edit(and repeated amends) and confirm only oneGit-AItrailer is present viagit log --format='%(trailers:key=Git-AI)'. The fix relies ontrailer.Git-AI.ifExists=replace(wrapper) and--if-exists replace(hooks) — verify these work with the project's minimum supported git version (flags added in git 2.15).id). To test the full recompute path, rebase commits that modify AI-touched files with content conflicts, then verify noteidviagit notes --ref=refs/notes/ai show <new-commit>. Pay particular attention to the.and_then()reset pattern atrebase_authorship.rs:729-735— this prevents inter-iterationidleakage when a source note lookup fails.if let(not.and_then()), relying on a freshauthorship_logbeing created each loop iteration. Confirm this assumption holds by reviewing the loop structure atrebase_authorship.rs:947-955.!is_cherry_pick_in_progress(&repo)guard atgit_hook_handlers.rs:2455prevents stalepending_note_idfiles (from aborted commits) from leaking into cherry-picked messages. This relies onCHERRY_PICK_HEADexisting whenprepare-commit-msgfires during cherry-pick — standard git behavior, but worth manual verification. Note: there is no automated test for the abort→cherry-pick stale trailer scenario; only the guard itself was added.idwill NOT match source commits.task testin both wrapper (default) and hooks mode (GIT_AI_TEST_GIT_MODE=hooks). All 27 tests pass locally (16 original trailer tests + 11 new rewrite tests). Manually test with git-ai installed: make commits, amend (multiple times), rebase, cherry-pick, and CI-style merges in both main and linked worktrees. Verify trailers ingit logand noteidmatches viagit notes --ref=refs/notes/ai show <commit>. For rebase/cherry-pick, confirmidpreservation across rewrites. Test the cherry-pick guard by aborting a commit (close editor) then cherry-picking — verify no stale trailer appears.Notes
id: Nonefieldcommit_hooks_comprehensive.rstests updated for the&mutsignature change oncommit_pre_command_hooktest_dry_run_commit_no_trailer(test 8 of 9) was not implemented. Dry-run commits correctly skip trailer injection but lack a dedicated test.commit_pre_command_hooksignature changed to&mut ParsedGitInvocationto allow in-place modification of args for trailer injection--if-existsflag forgit interpret-trailersand-c trailer.<key>.ifExistsconfig were added in git 2.15 (Oct 2017). Verify this aligns with the project's minimum supported git version.Updates since last revision:
idpreservation during rebase rewrites (rewrite_authorship_after_rebase_v2)idpreservation during cherry-pick rewrites (rewrite_authorship_after_cherry_pick)tests/rewrite_trailer_uuid.rscovering all rewrite flows using TDD methodologyidleak bug: Changed from nestedif letto.and_then()chain to ensurecurrent_authorship_log.metadata.idalways resets when source note lookup fails, preventing inter-iteration leakage!is_cherry_pick_in_progress(&repo)guard toprepare-commit-msgtrailer injection to prevent stalepending_note_idfrom aborted commits leaking into cherry-picked messagesGit-AItrailers on--amend: Both wrapper mode (via-c trailer.Git-AI.ifExists=replace) and hooks mode (via--if-exists replace) now replace existing trailers instead of appending duplicates, preventing accumulation of stale trailers on repeated amendsLink to Devin Session: https://app.devin.ai/sessions/ecd79d3689984605abd6bfd6b01280ad
Requested by: @svarlamov