Implement staging and unstaging hunks (#24606)

- [x] Staging hunks
- [x] Unstaging hunks
- [x] Write a randomized test
- [x] Get test passing
- [x] Fix existing bug in diff_base_byte_range computation
- [x] Remote project support
- [ ] ~~Improve performance of
buffer_range_to_unchanged_diff_base_range~~
- [ ] ~~Bug: project diff editor scrolls to top when staging/unstaging
hunk~~ existing issue
- [ ] ~~UI~~ deferred
- [x] Tricky cases
  - [x] Correctly handle acting on multiple hunks for a single file
- [x] Remove path from index when unstaging the last staged hunk, if
it's absent from HEAD, or staging the only hunk, if it's deleted in the
working copy

Release Notes:

- Add `ToggleStagedSelectedDiffHunks` action for staging and unstaging
individual diff hunks
This commit is contained in:
Cole Miller 2025-02-12 14:46:42 -05:00 committed by GitHub
parent ea8da43c6b
commit eea6b526dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 768 additions and 70 deletions

View file

@ -23,11 +23,11 @@ use util::{maybe, ResultExt};
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
pub struct GitState {
project_id: Option<ProjectId>,
client: Option<AnyProtoClient>,
pub(super) project_id: Option<ProjectId>,
pub(super) client: Option<AnyProtoClient>,
pub update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<anyhow::Result<()>>)>,
repositories: Vec<Entity<Repository>>,
active_index: Option<usize>,
update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<anyhow::Result<()>>)>,
_subscription: Subscription,
}
@ -51,7 +51,7 @@ pub enum GitRepo {
},
}
enum Message {
pub enum Message {
Commit {
git_repo: GitRepo,
message: SharedString,
@ -59,6 +59,7 @@ enum Message {
},
Stage(GitRepo, Vec<RepoPath>),
Unstage(GitRepo, Vec<RepoPath>),
SetIndexText(GitRepo, RepoPath, Option<String>),
}
pub enum GitEvent {
@ -291,11 +292,32 @@ impl GitState {
}
Ok(())
}
Message::SetIndexText(git_repo, path, text) => match git_repo {
GitRepo::Local(repo) => repo.set_index_text(&path, text),
GitRepo::Remote {
project_id,
client,
worktree_id,
work_directory_id,
} => client.send(proto::SetIndexText {
project_id: project_id.0,
worktree_id: worktree_id.to_proto(),
work_directory_id: work_directory_id.to_proto(),
path: path.as_ref().to_proto(),
text,
}),
},
}
}
}
impl GitRepo {}
impl Repository {
pub fn git_state(&self) -> Option<Entity<GitState>> {
self.git_state.upgrade()
}
fn id(&self) -> (WorktreeId, ProjectEntryId) {
(self.worktree_id, self.repository_entry.work_directory_id())
}
@ -522,4 +544,19 @@ impl Repository {
.ok();
result_rx
}
pub fn set_index_text(
&self,
path: &RepoPath,
content: Option<String>,
) -> oneshot::Receiver<anyhow::Result<()>> {
let (result_tx, result_rx) = futures::channel::oneshot::channel();
self.update_sender
.unbounded_send((
Message::SetIndexText(self.git_repo.clone(), path.clone(), content),
result_tx,
))
.ok();
result_rx
}
}