feat(source-control): commit history panel in sidebar with undo last commit#924
feat(source-control): commit history panel in sidebar with undo last commit#924ceyhuncicek wants to merge 4 commits into
Conversation
📝 WalkthroughWalkthroughAdds end-to-end undo-last-commit support: backend validation and atomic ref update, frontend command bridge, a new commit history panel with undo confirmation, resizable source control layout wiring, and a settings preference toggle. ChangesUndo Last Commit
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant CommitHistorySection
participant native
participant git_undo_commit
participant undo_last_commit
participant GitCLI
User->>CommitHistorySection: click undo, confirm dialog
CommitHistorySection->>native: gitUndoCommit(repoRoot, expectedHeadSha)
native->>git_undo_commit: invoke("git_undo_commit", ...)
git_undo_commit->>undo_last_commit: undo_last_commit(repo_root, expected_head_sha)
undo_last_commit->>GitCLI: rev-parse <expected>^
undo_last_commit->>GitCLI: git update-ref HEAD <parent> <expected>
GitCLI-->>undo_last_commit: result
undo_last_commit-->>git_undo_commit: Ok/Err
git_undo_commit-->>native: Result<(), String>
native-->>CommitHistorySection: success/error
CommitHistorySection->>CommitHistorySection: reload history or toast error
Related issues: None specified. Related PRs: None specified. Suggested labels: feature, frontend, backend, settings Suggested reviewers: Not enough information to infer ownership from the diff. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/modules/source-control/CommitHistorySection.tsx (1)
205-216: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winDefer commit history loading while collapsed.
This effect still runs
gitLogfor a collapsed section, adding IPC and git work before the user asks for history; keep the repo reset, but callloadInitial()only when expanded or explicitly refreshed. As per coding guidelines, "Unused features consume zero resources." As per path instructions, watch for "extra IPC round-trips" and "eager work that should be lazy."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/modules/source-control/CommitHistorySection.tsx` around lines 205 - 216, The CommitHistorySection useEffect is still triggering loadInitial() even when the history panel is collapsed, causing eager gitLog/IPC work. Update the effect in CommitHistorySection so the repo reset/cache clearing logic still runs on repoRoot changes, but gate the loadInitial() call on the section being expanded or on an explicit refresh via refreshKey. Use the existing symbols loadInitial, refreshKey, repoRoot, and lastRepoRef to keep the lazy-loading behavior aligned with the collapsed state.Sources: Coding guidelines, Path instructions
src-tauri/tests/git_operations.rs (1)
567-651: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGood coverage of the invariant.
Success, stale-sha rejection, and initial-commit rejection are all exercised, matching the "core subsystem needs a test that locks the invariant" bar. As per coding guidelines, "A change to a core subsystem (terminal/shell spawn, workspace auth, git, fs, IPC or AI tool surface) needs a test that locks the invariant." Consider asserting on the error message/context (not just the
CommandFaileddiscriminant) inundo_last_commit_rejects_initial_commit, which would also catch the dead-branch risk flagged inoperations.rs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/tests/git_operations.rs` around lines 567 - 651, The undo-last-commit tests already cover the main invariant, but `undo_last_commit_rejects_initial_commit` only checks the `GitError::CommandFailed` variant and could miss the dead-branch behavior in `operations::undo_last_commit`. Update that test to assert on the returned error’s message/context as well, using the `undo_last_commit` and `GitError` symbols to verify the initial-commit path is actually the one being rejected.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/src/modules/git/operations.rs`:
- Around line 445-479: The current HEAD validation in operations::undo_commit is
a TOCTOU check, because git reset --soft HEAD~1 re-reads HEAD after the guard
and can discard a different commit if the ref changes in between. Replace the
separate HEAD check plus reset flow with an atomic compare-and-swap using git
update-ref in undo_commit, verifying expected_head_sha and moving HEAD directly
to the parent commit in one step. Keep the existing initial-commit guard and
success/error handling, but make the ref move itself the single source of truth
so the operation only succeeds when HEAD still matches the confirmed commit.
In `@src/modules/source-control/CommitHistorySection.tsx`:
- Around line 176-200: The loadMore pagination flow in CommitHistorySection is
vulnerable to stale results after a repo switch and can get stuck in a hidden
error state. Add the same request/repo guard used by loadInitial around the
native.gitLog await so old responses are ignored when repoRoot changes, and
update the catch path so pagination failures do not leave loadStatus permanently
at "error" without visible recovery; either reset loadStatus back to "idle" with
retry handling or surface an inline load-more error near the existing commit
list UI.
- Around line 513-521: The undo control in CommitHistorySection’s action button
is only exposed via group-hover, which makes it unreachable for keyboard and
touch users. Update the undo button in the commit row to remain focusable and
discoverable without hover, using an always-present visibility pattern such as
opacity/visibility with focus-visible styles, or by moving it to a persistent
row action. Keep the existing onRequestUndo(commit) behavior and aria-label
while adjusting the button’s classes/styles so it can be tabbed to and activated
without mouse hover.
---
Nitpick comments:
In `@src-tauri/tests/git_operations.rs`:
- Around line 567-651: The undo-last-commit tests already cover the main
invariant, but `undo_last_commit_rejects_initial_commit` only checks the
`GitError::CommandFailed` variant and could miss the dead-branch behavior in
`operations::undo_last_commit`. Update that test to assert on the returned
error’s message/context as well, using the `undo_last_commit` and `GitError`
symbols to verify the initial-commit path is actually the one being rejected.
In `@src/modules/source-control/CommitHistorySection.tsx`:
- Around line 205-216: The CommitHistorySection useEffect is still triggering
loadInitial() even when the history panel is collapsed, causing eager gitLog/IPC
work. Update the effect in CommitHistorySection so the repo reset/cache clearing
logic still runs on repoRoot changes, but gate the loadInitial() call on the
section being expanded or on an explicit refresh via refreshKey. Use the
existing symbols loadInitial, refreshKey, repoRoot, and lastRepoRef to keep the
lazy-loading behavior aligned with the collapsed state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c8ba8fd1-0c40-4344-9959-363ed7c7c35f
📒 Files selected for processing (10)
src-tauri/src/lib.rssrc-tauri/src/modules/git/commands.rssrc-tauri/src/modules/git/operations.rssrc-tauri/tests/git_operations.rssrc/app/App.tsxsrc/modules/ai/lib/native.tssrc/modules/settings/store.tssrc/modules/source-control/CommitHistorySection.tsxsrc/modules/source-control/SourceControlPanel.tsxsrc/settings/sections/GeneralSection.tsx
|
Screenshot as promised: the Commits section at the bottom of the Source Control sidebar, with a commit expanded to its changed files (clicking a file opens the per-file diff tab). The icons in the section header are refresh and a jump to the full-page commit graph. The undo action appears on hover over the newest commit and asks for confirmation before running; it can be turned off in Settings under General with the "Undo last commit" toggle. |
- undo uses git update-ref with old-value check so the ref move is an atomic compare-and-swap instead of check-then-reset - loadMore drops stale responses after a repo switch or refresh and surfaces pagination failures via toast instead of a hidden error state - undo action is keyboard reachable via focus-visible instead of hover-only display
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src-tauri/src/modules/git/operations.rs (1)
487-492: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueNon-zero exit is always reported as "HEAD changed".
Any
update-reffailure that isn't a CAS mismatch (locked ref, worktree lock, disk/permission error) surfaces the same "refresh and try again" message, and stderr is dropped. Users chasing a real failure get a misleading hint and no diagnostics. Consider threading the git stderr tail into the error detail so genuine failures are distinguishable from the expected stale-sha case.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/modules/git/operations.rs` around lines 487 - 492, The git update-ref error handling in operations.rs is treating every non-zero exit from the update-ref path as a stale HEAD mismatch, which hides real failures. Update the logic around the update-ref call in the git operations code to distinguish the expected CAS mismatch case from other errors, and include the stderr tail from the command in the error detail for genuine failures. Use the existing GitError::command path and the update-ref handling block to preserve the refresh-and-try-again message only when the ref really changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/src/modules/git/operations.rs`:
- Around line 448-455: The SHA validation in undo_last_commit is too strict and
only accepts 40-character hex IDs, which breaks SHA-256 repositories. Update the
expected_head_sha check in operations.rs to accept both full 40-char and 64-char
hexadecimal object IDs, or replace the inline validation with the existing SHA
helper used elsewhere in git operations. Keep the same error path in
GitError::command, but ensure valid full object IDs of either length pass
through to the git update-ref flow.
---
Nitpick comments:
In `@src-tauri/src/modules/git/operations.rs`:
- Around line 487-492: The git update-ref error handling in operations.rs is
treating every non-zero exit from the update-ref path as a stale HEAD mismatch,
which hides real failures. Update the logic around the update-ref call in the
git operations code to distinguish the expected CAS mismatch case from other
errors, and include the stderr tail from the command in the error detail for
genuine failures. Use the existing GitError::command path and the update-ref
handling block to preserve the refresh-and-try-again message only when the ref
really changed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0cd52e60-6b01-4d35-85b7-87f1a5dc638f
📒 Files selected for processing (3)
src-tauri/src/modules/git/operations.rssrc-tauri/tests/git_operations.rssrc/modules/source-control/CommitHistorySection.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src-tauri/tests/git_operations.rs
- src/modules/source-control/CommitHistorySection.tsx
| if expected_head_sha.len() != 40 | ||
| || !expected_head_sha.bytes().all(|b| b.is_ascii_hexdigit()) | ||
| { | ||
| return Err(GitError::command( | ||
| "git update-ref", | ||
| "expected a full commit sha", | ||
| )); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
git SHA-256 object format commit hash length hex characters
💡 Result:
In Git, when a repository is configured to use the SHA-256 hash function (enabled via a repository format extension), object identifiers (commit hashes and other object names) are represented as 64 hexadecimal characters [1][2]. This is an increase from the 40 hexadecimal characters used for SHA-1 object identifiers [1][2]. Key technical details regarding this format include: Hash Representation: Objects are identified by their SHA-256 hash, which results in a 64-character hexadecimal string [1][2]. Object Content and References: In a SHA-256 repository, not only are the object names (hashes) different, but the internal content of objects (such as commit, tree, and tag objects) is also updated to reference other objects by their SHA-256 names instead of SHA-1 names [2][3]. Compatibility: While SHA-256 repositories use 64-character hashes, Git provides mechanisms to facilitate interoperability. For instance, repositories can maintain a bidirectional mapping between SHA-1 and SHA-256 object names, allowing users to reference objects using either format interchangeably in some contexts, and enabling communication with SHA-1 based Git servers [3][4]. Format Identification: Git internals, such as packfiles and multi-pack-index files, use a specific hash function identifier (e.g., 2 for SHA-256, 1 for SHA-1) to determine the length and type of object IDs being processed [5].
Citations:
- 1: https://git-scm.com/docs/hash-function-transition
- 2: https://github.com/git/git/blob/0ed8d8da374f648764758f13038ca93af87ab800/Documentation/technical/hash-function-transition.txt
- 3: https://git-scm.com/docs/hash-function-transition/2.52.0
- 4: https://github.com/git/git/blob/v2.25.0/Documentation/technical/hash-function-transition.txt
- 5: https://git-scm.com/docs/pack-format
🏁 Script executed:
sed -n '400,520p' src-tauri/src/modules/git/operations.rs
rg -n "object-format|sha256|expected_head_sha|is_ascii_hexdigit|len\(\) != 40|64" src-tauri/src/modules/git -SRepository: crynta/terax-ai
Length of output: 6308
Accept 64-char object IDs here too
undo_last_commit rejects any full SHA unless it is 40 hex chars, which breaks SHA-256 repos where git rev-parse and git update-ref use 64-char object IDs. This guard should accept both lengths, or reuse the existing SHA helper here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src-tauri/src/modules/git/operations.rs` around lines 448 - 455, The SHA
validation in undo_last_commit is too strict and only accepts 40-character hex
IDs, which breaks SHA-256 repositories. Update the expected_head_sha check in
operations.rs to accept both full 40-char and 64-char hexadecimal object IDs, or
replace the inline validation with the existing SHA helper used elsewhere in git
operations. Keep the same error path in GitError::command, but ensure valid full
object IDs of either length pass through to the git update-ref flow.

What
Adds a resizable, collapsible Commits section to the bottom of the Source Control sidebar: recent commits with expandable changed-file lists that open per-file diffs, infinite scroll pagination, and an undo action on the newest commit (git reset --soft HEAD~1) behind a confirm dialog. A settings toggle (General > Undo last commit) can hide the undo action.
Why
Commit history currently only exists as the full-page Commit Graph tab. The common loop of "what did I just commit, let me undo and fix it" requires leaving the current tab. This brings a GitLens-style history view into the sidebar next to the changes list.
How
Testing
Exercised manually in pnpm tauri dev on a real repo: commit from the panel and watch it appear at the top of the section, expand commits and open per-file diffs, scroll to page in older commits, drag the divider and restart to confirm persisted height, collapse/expand the section, undo the last commit and confirm its changes return to the staged list, confirm the undo icon is absent on the initial commit and hidden when the new setting is off, and confirm a non-repo workspace shows the empty state.
pnpm exec tsc --noEmitcleansrc-tauri/)cargo test --lockedandcargo clippy --all-targets --locked -- -D warningsclean#[tauri::command]signature) called out below so the FE caller can be updated in locksteppnpm tauri devNew command:
git_undo_commit(repo_root, expected_head_sha); its frontend caller (native.gitUndoCommit) lands in the same PR. Three new integration tests in tests/git_operations.rs cover the undo path, the stale-HEAD rejection, and the initial-commit rejection; 254 frontend tests and pnpm lint pass with no new warnings.Screenshots / GIFs
Screenshots to follow in a comment.
Notes for reviewer
Undo is soft: the commit's changes return to the index, nothing is lost. If the newest commit appears pushed (upstream exists and ahead is 0), the confirm dialog adds an explicit history-rewrite warning instead of blocking. The undo action defaults to visible; the new sourceControlUndoCommit preference hides it for people who consider reset too sharp a tool for a sidebar.
Summary by CodeRabbit