Start on a Git-based review flow (#27103)

Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2025-03-19 20:00:21 +01:00 committed by GitHub
parent 68262fe7e4
commit 33faa66e35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 396 additions and 131 deletions

View file

@ -1,5 +1,5 @@
use crate::status::GitStatus;
use crate::SHORT_SHA_LENGTH;
use crate::{Oid, SHORT_SHA_LENGTH};
use anyhow::{anyhow, Context as _, Result};
use collections::HashMap;
use futures::future::BoxFuture;
@ -22,6 +22,7 @@ use std::{
use sum_tree::MapSeekTarget;
use util::command::new_smol_command;
use util::ResultExt;
use uuid::Uuid;
pub use askpass::{AskPassResult, AskPassSession};
@ -287,6 +288,12 @@ pub trait GitRepository: Send + Sync {
/// Run git diff
fn diff(&self, diff: DiffType, cx: AsyncApp) -> BoxFuture<Result<String>>;
/// Creates a checkpoint for the repository.
fn checkpoint(&self, cx: AsyncApp) -> BoxFuture<Result<Oid>>;
/// Resets to a previously-created checkpoint.
fn restore_checkpoint(&self, oid: Oid, cx: AsyncApp) -> BoxFuture<Result<()>>;
}
pub enum DiffType {
@ -1025,6 +1032,89 @@ impl GitRepository for RealGitRepository {
})
.boxed()
}
fn checkpoint(&self, cx: AsyncApp) -> BoxFuture<Result<Oid>> {
let working_directory = self.working_directory();
let git_binary_path = self.git_binary_path.clone();
let executor = cx.background_executor().clone();
cx.background_spawn(async move {
let working_directory = working_directory?;
let index_file_path = working_directory.join(".git/index.tmp");
let delete_temp_index = util::defer({
let index_file_path = index_file_path.clone();
|| {
executor
.spawn(async move {
smol::fs::remove_file(index_file_path).await.log_err();
})
.detach();
}
});
let run_git_command = async |args: &[&str]| {
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.env("GIT_INDEX_FILE", &index_file_path)
.env("GIT_AUTHOR_NAME", "Zed")
.env("GIT_AUTHOR_EMAIL", "hi@zed.dev")
.env("GIT_COMMITTER_NAME", "Zed")
.env("GIT_COMMITTER_EMAIL", "hi@zed.dev")
.args(args)
.output()
.await?;
if output.status.success() {
anyhow::Ok(String::from_utf8(output.stdout)?.trim_end().to_string())
} else {
let error = String::from_utf8_lossy(&output.stderr);
Err(anyhow!("Git command failed: {:?}", error))
}
};
run_git_command(&["add", "--all"]).await?;
let tree = run_git_command(&["write-tree"]).await?;
let commit_sha = run_git_command(&["commit-tree", &tree, "-m", "Checkpoint"]).await?;
let ref_name = Uuid::new_v4().to_string();
run_git_command(&["update-ref", &format!("refs/heads/{ref_name}"), &commit_sha])
.await?;
smol::fs::remove_file(index_file_path).await.ok();
delete_temp_index.abort();
commit_sha.parse()
})
.boxed()
}
fn restore_checkpoint(&self, oid: Oid, cx: AsyncApp) -> BoxFuture<Result<()>> {
let working_directory = self.working_directory();
let git_binary_path = self.git_binary_path.clone();
cx.background_spawn(async move {
let working_directory = working_directory?;
let index_file_path = working_directory.join(".git/index.tmp");
let run_git_command = async |args: &[&str]| {
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.env("GIT_INDEX_FILE", &index_file_path)
.args(args)
.output()
.await?;
if output.status.success() {
anyhow::Ok(String::from_utf8(output.stdout)?.trim_end().to_string())
} else {
let error = String::from_utf8_lossy(&output.stderr);
Err(anyhow!("Git command failed: {:?}", error))
}
};
run_git_command(&["restore", "--source", &oid.to_string(), "--worktree", "."]).await?;
run_git_command(&["read-tree", &oid.to_string()]).await?;
run_git_command(&["clean", "-d", "--force"]).await?;
Ok(())
})
.boxed()
}
}
async fn run_remote_command(
@ -1260,29 +1350,72 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
}
}
#[test]
fn test_branches_parsing() {
// suppress "help: octal escapes are not supported, `\0` is always null"
#[allow(clippy::octal_escapes)]
let input = "*\0060964da10574cd9bf06463a53bf6e0769c5c45e\0\0refs/heads/zed-patches\0refs/remotes/origin/zed-patches\0\01733187470\0generated protobuf\n";
assert_eq!(
parse_branch_input(&input).unwrap(),
vec![Branch {
is_head: true,
name: "zed-patches".into(),
upstream: Some(Upstream {
ref_name: "refs/remotes/origin/zed-patches".into(),
tracking: UpstreamTracking::Tracked(UpstreamTrackingStatus {
ahead: 0,
behind: 0
#[cfg(test)]
mod tests {
use gpui::TestAppContext;
use super::*;
#[gpui::test]
async fn test_checkpoint(cx: &mut TestAppContext) {
cx.executor().allow_parking();
let repo_dir = tempfile::tempdir().unwrap();
git2::Repository::init(repo_dir.path()).unwrap();
let repo = RealGitRepository::new(&repo_dir.path().join(".git"), None).unwrap();
smol::fs::write(repo_dir.path().join("foo"), "foo")
.await
.unwrap();
let checkpoint_sha = repo.checkpoint(cx.to_async()).await.unwrap();
smol::fs::write(repo_dir.path().join("foo"), "bar")
.await
.unwrap();
smol::fs::write(repo_dir.path().join("baz"), "qux")
.await
.unwrap();
repo.restore_checkpoint(checkpoint_sha, cx.to_async())
.await
.unwrap();
assert_eq!(
smol::fs::read_to_string(repo_dir.path().join("foo"))
.await
.unwrap(),
"foo"
);
assert_eq!(
smol::fs::read_to_string(repo_dir.path().join("baz"))
.await
.ok(),
None
);
}
#[test]
fn test_branches_parsing() {
// suppress "help: octal escapes are not supported, `\0` is always null"
#[allow(clippy::octal_escapes)]
let input = "*\0060964da10574cd9bf06463a53bf6e0769c5c45e\0\0refs/heads/zed-patches\0refs/remotes/origin/zed-patches\0\01733187470\0generated protobuf\n";
assert_eq!(
parse_branch_input(&input).unwrap(),
vec![Branch {
is_head: true,
name: "zed-patches".into(),
upstream: Some(Upstream {
ref_name: "refs/remotes/origin/zed-patches".into(),
tracking: UpstreamTracking::Tracked(UpstreamTrackingStatus {
ahead: 0,
behind: 0
})
}),
most_recent_commit: Some(CommitSummary {
sha: "060964da10574cd9bf06463a53bf6e0769c5c45e".into(),
subject: "generated protobuf".into(),
commit_timestamp: 1733187470,
has_parent: false,
})
}),
most_recent_commit: Some(CommitSummary {
sha: "060964da10574cd9bf06463a53bf6e0769c5c45e".into(),
subject: "generated protobuf".into(),
commit_timestamp: 1733187470,
has_parent: false,
})
}]
)
}]
)
}
}