Repository navigation
feat(staged): add right-click Revert for timeline commits #1073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
matt2e
wants to merge
6
commits into
main
Choose a base branch
from
right-click-revert-commit
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d31ce22
feat: add commit revert action
matt2e aaa1483
fix(staged): launch revert pipelines like other run-now pipelines
matt2e 8aa39f6
fix(staged): make commit revert race-safe and finish its frontend wiring
matt2e 659bb84
fix(staged): close revert-commit gaps around resets, orphans and HEAD…
matt2e 226913c
fix(staged): block Revert while a commit delete is in flight
matt2e 4e70866
refactor(staged): simplify no-AI pipeline resolution and tighten reve…
matt2e File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| //! Revert: a run-now pipeline that creates a signed-off revert commit for a | ||
| //! completed commit on the branch. | ||
| //! | ||
| //! Unlike rebase and squash it never queues: it is refused while the branch | ||
| //! has work in flight, and runs `git revert --signoff --no-edit <sha>` with an | ||
| //! AI handoff only if git stops on a conflict. A pending-commit stub is linked | ||
| //! to the session so completion can record the revert commit's SHA. | ||
|
|
||
| use std::sync::Arc; | ||
|
|
||
| use crate::session_runner; | ||
| use crate::store::{FailureStrategy, PipelineExecution, PipelineStep, Store}; | ||
|
|
||
| use super::commit::insert_running_pending_commit_session; | ||
| use super::context::{resolve_branch_pipeline_context, BranchPipelineContext}; | ||
| use super::pipeline::{ | ||
| branch_has_work_in_flight, launch_running_pipeline_session, RunningPipelineSession, | ||
| }; | ||
|
|
||
| pub(super) fn validate_commit_sha(commit_sha: &str) -> Result<String, String> { | ||
| let sha = commit_sha.trim(); | ||
| if (7..=64).contains(&sha.len()) && sha.chars().all(|c| c.is_ascii_hexdigit()) { | ||
| Ok(sha.to_string()) | ||
| } else { | ||
| Err("Commit SHA must be 7-64 hexadecimal characters".to_string()) | ||
| } | ||
| } | ||
|
|
||
| fn revert_handoff_prompt(commit_sha: &str) -> String { | ||
| format!( | ||
| r#"<action> | ||
| `git revert --signoff --no-edit {commit_sha}` failed. | ||
|
|
||
| Inspect the git state and command output, then complete the revert safely. | ||
|
|
||
| Requirements: | ||
| - Resolve conflicts if present, preserving unrelated local changes. | ||
| - If git is in a revert sequence and the resolution is correct, run `git revert --continue`. | ||
| - Use the user's global git identity for author and committer. | ||
| - Ensure the resulting revert commit is signed off. | ||
| - Do not push the branch. | ||
| - If the revert should not continue, explain why and leave the repository in a clear state. | ||
|
|
||
| Context from prior steps: | ||
|
|
||
| {{step_outputs}} | ||
| </action>"# | ||
| ) | ||
| } | ||
|
|
||
| pub(super) fn build_revert_pipeline_steps(commit_sha: &str) -> Result<Vec<PipelineStep>, String> { | ||
| let commit_sha = validate_commit_sha(commit_sha)?; | ||
| Ok(vec![PipelineStep::Command { | ||
| label: "Revert commit".to_string(), | ||
| command: format!("git revert --signoff --no-edit {commit_sha}"), | ||
| on_failure: FailureStrategy::HandoffToAi { | ||
| prompt_template: revert_handoff_prompt(&commit_sha), | ||
| }, | ||
| }]) | ||
| } | ||
|
|
||
| pub(super) const REVERT_BRANCH_BUSY: &str = | ||
| "Revert is unavailable while this branch has queued or running work"; | ||
| pub(super) const REVERT_WORKTREE_BUSY: &str = | ||
| "Revert is unavailable while this branch is being reset or its changes discarded"; | ||
|
|
||
| /// Insert the rows for a revert that runs now, or refuse it on a busy branch. | ||
| /// | ||
| /// A revert is never queued: a queued row would have to carry the target SHA | ||
| /// for the drain to rebuild its step from. So where a rebase or squash would | ||
| /// queue behind work in flight, this errors — under the branch launch lock, | ||
| /// with the insert in the same critical section, so two rapid clicks (or a | ||
| /// click racing a session start) cannot both observe an idle branch. This | ||
| /// stays synchronous on purpose: the lock must not be held across an await. | ||
| /// | ||
| /// A reset-to-origin or discard has no session row for that check to find, | ||
| /// so the marker they set is read here as well, the way the forced start reads | ||
| /// it: a revert started under one would race its `git reset --hard`, and if it | ||
| /// lost on `index.lock` the AI handoff would be asked to finish reverting a | ||
| /// commit the reset may have just removed from the branch. | ||
| /// | ||
| /// HEAD is not read here. The runner reads it before the revert step (see | ||
| /// [`session_runner::PipelineConfig::capture_pre_head`]), by which point this | ||
| /// row has made the branch busy, so nothing that goes through the branch queue | ||
| /// can move HEAD in between. Reset-to-origin, discard and delete-commit run | ||
| /// outside the queue: the marker above only stops a revert starting under a | ||
| /// reset or discard, only the frontend's busy-branch gates keep those two from | ||
| /// starting beside a running revert, and nothing gates a delete-commit then. | ||
| pub(super) fn insert_running_revert_pipeline_session_if_idle( | ||
| store: &Arc<Store>, | ||
| registry: &Arc<session_runner::SessionRegistry>, | ||
| ctx: &BranchPipelineContext, | ||
| steps: &[PipelineStep], | ||
| commit_sha: &str, | ||
| provider: Option<&str>, | ||
| ) -> Result<RunningPipelineSession, String> { | ||
| let launch_lock = crate::session_commands::branch_session_launch_lock_for(&ctx.branch.id); | ||
| let _guard = launch_lock.lock().unwrap(); | ||
| if crate::session_commands::branch_worktree_operation_in_flight(&ctx.branch.id) { | ||
| return Err(REVERT_WORKTREE_BUSY.to_string()); | ||
| } | ||
| if branch_has_work_in_flight(store, &ctx.branch.id)? { | ||
| return Err(REVERT_BRANCH_BUSY.to_string()); | ||
| } | ||
|
|
||
| let prompt = format!("Revert commit {}", &commit_sha[..7.min(commit_sha.len())]); | ||
| insert_running_pending_commit_session( | ||
| store, | ||
| registry, | ||
| ctx, | ||
| PipelineExecution::from_steps(steps), | ||
| prompt, | ||
| provider, | ||
| true, | ||
| ) | ||
| } | ||
|
|
||
| /// Revert a completed commit by creating a new signed-off revert commit. | ||
| /// | ||
| /// Runs `git revert --signoff --no-edit <sha>` directly. If git needs conflict | ||
| /// resolution, the pipeline hands off to AI with the pending commit row still | ||
| /// linked so completion can record the final HEAD. | ||
| /// | ||
| /// Shared by the Tauri command and the web dispatch arm so both gates behave | ||
| /// identically. | ||
| pub(crate) fn revert_commit_for_branch( | ||
| store: Arc<Store>, | ||
| registry: Arc<session_runner::SessionRegistry>, | ||
| app_handle: tauri::AppHandle, | ||
| branch_id: String, | ||
| commit_sha: String, | ||
| provider: Option<String>, | ||
| ) -> Result<String, String> { | ||
| let commit_sha = validate_commit_sha(&commit_sha)?; | ||
| let steps = build_revert_pipeline_steps(&commit_sha)?; | ||
| let ctx = resolve_branch_pipeline_context(&store, &branch_id)?; | ||
|
|
||
| let running = insert_running_revert_pipeline_session_if_idle( | ||
| &store, | ||
| ®istry, | ||
| &ctx, | ||
| &steps, | ||
| &commit_sha, | ||
| provider.as_deref(), | ||
| )?; | ||
| launch_running_pipeline_session( | ||
| ctx, | ||
| running, | ||
| steps, | ||
| "commit", | ||
| provider, | ||
| store, | ||
| &app_handle, | ||
| ®istry, | ||
| ) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a repository has a local Git identity different from the user's global identity, a conflict-free revert is authored, committed, and signed off using that local identity. The deterministic runner does not apply
git_identity_env_from_global_config(); that override is only applied during AI handoff. Consequently, the same Revert action uses different identities depending on whether it encounters conflicts, and the successful path bypasses the global-identity requirement explicitly included inrevert_handoff_prompt. As confirmed bygit revert -h,--signoffonly means “add a Signed-off-by trailer”; it does not select the identity. Apply the global author/committer environment to the deterministic command as well.Useful? React with 👍 / 👎.