Skip to content

feat(source-control): commit history panel in sidebar with undo last commit#924

Open
ceyhuncicek wants to merge 4 commits into
crynta:mainfrom
ceyhuncicek:feat/sidebar-commit-history
Open

feat(source-control): commit history panel in sidebar with undo last commit#924
ceyhuncicek wants to merge 4 commits into
crynta:mainfrom
ceyhuncicek:feat/sidebar-commit-history

Conversation

@ceyhuncicek

@ceyhuncicek ceyhuncicek commented Jul 2, 2026

Copy link
Copy Markdown

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

  • The commit list reuses the existing backend surface: git_log with beforeSha pagination, git_commit_files for expanded rows, and the existing git-commit-file diff tab for file clicks. No new list-side backend.
  • One new Rust command, git_undo_commit, gated through the workspace registry like the other git commands. It verifies HEAD still matches the sha the user clicked before resetting (guards against a racing commit) and refuses to undo the initial commit.
  • The section refetch is keyed on branch/ahead/behind/changed-file count rather than the raw status snapshot, and reloads keep the current rows on screen until the top of history actually changes, so idle status polls do not flicker the list.
  • Split uses the react-resizable-panels group already in the app; height and collapsed state persist in localStorage. The old "Commit Graph" sidebar button moved into the section header as an icon.

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 --noEmit clean
  • Manual smoke-test of the affected feature
  • (If you touched src-tauri/) cargo test --locked and cargo clippy --all-targets --locked -- -D warnings clean
  • (If you changed a #[tauri::command] signature) called out below so the FE caller can be updated in lockstep
  • (If UI) tested in pnpm tauri dev
  • Platforms tested: macOS
  • Shells tested (if relevant): n/a

New 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

  • New Features
    • Added a full Commit History view in source control with infinite scroll, expandable commit details, and a refresh action.
    • Added an “Undo last commit” capability (optional in the UI) that reverts the latest commit and opens commit file diffs from history.
    • Added a preference to enable/disable “Undo last commit” (default: on) and wired it into the Source Control panel.
    • Commit history panel layout now remembers its size and collapsed state.
  • Bug Fixes
    • Undo now validates the expected HEAD SHA and prevents undo when the repo has changed (including malformed or initial commits), keeping history unchanged.
  • Tests
    • Added coverage for undo success, stale/malformed SHA handling, and initial-commit rejection.

@ceyhuncicek ceyhuncicek requested a review from crynta as a code owner July 2, 2026 17:14
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Undo Last Commit

Layer / File(s) Summary
Backend undo operation and tests
src-tauri/src/modules/git/operations.rs, src-tauri/src/modules/git/commands.rs, src-tauri/src/lib.rs, src-tauri/tests/git_operations.rs
Adds undo_last_commit with expected-HEAD validation and atomic HEAD update, registers git_undo_commit, and covers success, stale-sha, and initial-commit cases.
Frontend native bridge
src/modules/ai/lib/native.ts
Adds gitUndoCommit(repoRoot, expectedHeadSha) invoking the backend command with workspace context.
sourceControlUndoCommit preference
src/modules/settings/store.ts, src/settings/sections/GeneralSection.tsx
Adds the preference field, default, persistence, change propagation, and the Settings switch to toggle it.
Commit history panel with undo dialog
src/modules/source-control/CommitHistorySection.tsx
Adds paginated commit history loading, expandable per-commit file lists, scroll-based loading, refresh handling, and undo confirmation tied to gitUndoCommit.
SourceControlPanel resizable integration
src/modules/source-control/SourceControlPanel.tsx, src/app/App.tsx
Wires the history section into a resizable panel with persisted collapsed/height state and passes commit-file and undo props from App.

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
Loading

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)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and clearly summarizes the source-control commit history and undo-last-commit changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/modules/source-control/CommitHistorySection.tsx (1)

205-216: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Defer commit history loading while collapsed.

This effect still runs gitLog for a collapsed section, adding IPC and git work before the user asks for history; keep the repo reset, but call loadInitial() 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 win

Good 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 CommandFailed discriminant) in undo_last_commit_rejects_initial_commit, which would also catch the dead-branch risk flagged in operations.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

📥 Commits

Reviewing files that changed from the base of the PR and between 1c7f3e4 and 7f329e0.

📒 Files selected for processing (10)
  • src-tauri/src/lib.rs
  • src-tauri/src/modules/git/commands.rs
  • src-tauri/src/modules/git/operations.rs
  • src-tauri/tests/git_operations.rs
  • src/app/App.tsx
  • src/modules/ai/lib/native.ts
  • src/modules/settings/store.ts
  • src/modules/source-control/CommitHistorySection.tsx
  • src/modules/source-control/SourceControlPanel.tsx
  • src/settings/sections/GeneralSection.tsx

Comment thread src-tauri/src/modules/git/operations.rs Outdated
Comment thread src/modules/source-control/CommitHistorySection.tsx
Comment thread src/modules/source-control/CommitHistorySection.tsx
@ceyhuncicek

Copy link
Copy Markdown
Author

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.

Source Control sidebar with commit history section

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src-tauri/src/modules/git/operations.rs (1)

487-492: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Non-zero exit is always reported as "HEAD changed".

Any update-ref failure 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f329e0 and c18b856.

📒 Files selected for processing (3)
  • src-tauri/src/modules/git/operations.rs
  • src-tauri/tests/git_operations.rs
  • src/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

Comment on lines +448 to +455
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",
));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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:


🏁 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 -S

Repository: 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.

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.

1 participant