Introduce primitives in GitStore
to support reviewing assistant diffs (#27576)
Release Notes: - N/A
This commit is contained in:
parent
cd6b1d32d0
commit
82a06f0ca9
7 changed files with 666 additions and 63 deletions
|
@ -317,7 +317,7 @@ impl Render for MessageEditor {
|
||||||
|
|
||||||
let project = self.thread.read(cx).project();
|
let project = self.thread.read(cx).project();
|
||||||
let changed_files = if let Some(repository) = project.read(cx).active_repository(cx) {
|
let changed_files = if let Some(repository) = project.read(cx).active_repository(cx) {
|
||||||
repository.read(cx).status().count()
|
repository.read(cx).cached_status().count()
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,8 @@ use futures::future::{self, BoxFuture};
|
||||||
use git::{
|
use git::{
|
||||||
blame::Blame,
|
blame::Blame,
|
||||||
repository::{
|
repository::{
|
||||||
AskPassSession, Branch, CommitDetails, GitRepository, GitRepositoryCheckpoint, PushOptions,
|
AskPassSession, Branch, CommitDetails, GitIndex, GitRepository, GitRepositoryCheckpoint,
|
||||||
Remote, RepoPath, ResetMode,
|
PushOptions, Remote, RepoPath, ResetMode,
|
||||||
},
|
},
|
||||||
status::{FileStatus, GitStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
status::{FileStatus, GitStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
||||||
};
|
};
|
||||||
|
@ -81,7 +81,15 @@ impl FakeGitRepository {
|
||||||
impl GitRepository for FakeGitRepository {
|
impl GitRepository for FakeGitRepository {
|
||||||
fn reload_index(&self) {}
|
fn reload_index(&self) {}
|
||||||
|
|
||||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
|
fn load_index_text(
|
||||||
|
&self,
|
||||||
|
index: Option<GitIndex>,
|
||||||
|
path: RepoPath,
|
||||||
|
) -> BoxFuture<Option<String>> {
|
||||||
|
if index.is_some() {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
async {
|
async {
|
||||||
self.with_state_async(false, move |state| {
|
self.with_state_async(false, move |state| {
|
||||||
state
|
state
|
||||||
|
@ -171,7 +179,15 @@ impl GitRepository for FakeGitRepository {
|
||||||
self.path()
|
self.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result<GitStatus>> {
|
fn status(
|
||||||
|
&self,
|
||||||
|
index: Option<GitIndex>,
|
||||||
|
path_prefixes: &[RepoPath],
|
||||||
|
) -> BoxFuture<'static, Result<GitStatus>> {
|
||||||
|
if index.is_some() {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
let status = self.status_blocking(path_prefixes);
|
let status = self.status_blocking(path_prefixes);
|
||||||
async move { status }.boxed()
|
async move { status }.boxed()
|
||||||
}
|
}
|
||||||
|
@ -414,7 +430,7 @@ impl GitRepository for FakeGitRepository {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checkpoint(&self) -> BoxFuture<Result<GitRepositoryCheckpoint>> {
|
fn checkpoint(&self) -> BoxFuture<'static, Result<GitRepositoryCheckpoint>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,4 +449,20 @@ impl GitRepository for FakeGitRepository {
|
||||||
fn delete_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>> {
|
fn delete_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diff_checkpoints(
|
||||||
|
&self,
|
||||||
|
_base_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
_target_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
) -> BoxFuture<Result<String>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_index(&self) -> BoxFuture<Result<GitIndex>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_diff(&self, _index: GitIndex, _diff: String) -> BoxFuture<Result<()>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::borrow::{Borrow, Cow};
|
use std::borrow::{Borrow, Cow};
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::future;
|
|
||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
use std::process::{ExitStatus, Stdio};
|
use std::process::{ExitStatus, Stdio};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
@ -21,6 +20,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use std::{future, mem};
|
||||||
use sum_tree::MapSeekTarget;
|
use sum_tree::MapSeekTarget;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use util::command::{new_smol_command, new_std_command};
|
use util::command::{new_smol_command, new_std_command};
|
||||||
|
@ -161,7 +161,8 @@ pub trait GitRepository: Send + Sync {
|
||||||
/// Returns the contents of an entry in the repository's index, or None if there is no entry for the given path.
|
/// Returns the contents of an entry in the repository's index, or None if there is no entry for the given path.
|
||||||
///
|
///
|
||||||
/// Also returns `None` for symlinks.
|
/// Also returns `None` for symlinks.
|
||||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>>;
|
fn load_index_text(&self, index: Option<GitIndex>, path: RepoPath)
|
||||||
|
-> BoxFuture<Option<String>>;
|
||||||
|
|
||||||
/// Returns the contents of an entry in the repository's HEAD, or None if HEAD does not exist or has no entry for the given path.
|
/// Returns the contents of an entry in the repository's HEAD, or None if HEAD does not exist or has no entry for the given path.
|
||||||
///
|
///
|
||||||
|
@ -183,7 +184,11 @@ pub trait GitRepository: Send + Sync {
|
||||||
|
|
||||||
fn merge_head_shas(&self) -> Vec<String>;
|
fn merge_head_shas(&self) -> Vec<String>;
|
||||||
|
|
||||||
fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result<GitStatus>>;
|
fn status(
|
||||||
|
&self,
|
||||||
|
index: Option<GitIndex>,
|
||||||
|
path_prefixes: &[RepoPath],
|
||||||
|
) -> BoxFuture<'static, Result<GitStatus>>;
|
||||||
fn status_blocking(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
|
fn status_blocking(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
|
||||||
|
|
||||||
fn branches(&self) -> BoxFuture<Result<Vec<Branch>>>;
|
fn branches(&self) -> BoxFuture<Result<Vec<Branch>>>;
|
||||||
|
@ -286,7 +291,7 @@ pub trait GitRepository: Send + Sync {
|
||||||
fn diff(&self, diff: DiffType) -> BoxFuture<Result<String>>;
|
fn diff(&self, diff: DiffType) -> BoxFuture<Result<String>>;
|
||||||
|
|
||||||
/// Creates a checkpoint for the repository.
|
/// Creates a checkpoint for the repository.
|
||||||
fn checkpoint(&self) -> BoxFuture<Result<GitRepositoryCheckpoint>>;
|
fn checkpoint(&self) -> BoxFuture<'static, Result<GitRepositoryCheckpoint>>;
|
||||||
|
|
||||||
/// Resets to a previously-created checkpoint.
|
/// Resets to a previously-created checkpoint.
|
||||||
fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>>;
|
fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>>;
|
||||||
|
@ -300,6 +305,19 @@ pub trait GitRepository: Send + Sync {
|
||||||
|
|
||||||
/// Deletes a previously-created checkpoint.
|
/// Deletes a previously-created checkpoint.
|
||||||
fn delete_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>>;
|
fn delete_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>>;
|
||||||
|
|
||||||
|
/// Computes a diff between two checkpoints.
|
||||||
|
fn diff_checkpoints(
|
||||||
|
&self,
|
||||||
|
base_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
target_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
) -> BoxFuture<Result<String>>;
|
||||||
|
|
||||||
|
/// Creates a new index for the repository.
|
||||||
|
fn create_index(&self) -> BoxFuture<Result<GitIndex>>;
|
||||||
|
|
||||||
|
/// Applies a diff to the repository's index.
|
||||||
|
fn apply_diff(&self, index: GitIndex, diff: String) -> BoxFuture<Result<()>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum DiffType {
|
pub enum DiffType {
|
||||||
|
@ -356,8 +374,10 @@ pub struct GitRepositoryCheckpoint {
|
||||||
commit_sha: Oid,
|
commit_sha: Oid,
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
|
#[derive(Copy, Clone, Debug)]
|
||||||
const GIT_MODE_SYMLINK: u32 = 0o120000;
|
pub struct GitIndex {
|
||||||
|
id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
impl GitRepository for RealGitRepository {
|
impl GitRepository for RealGitRepository {
|
||||||
fn reload_index(&self) {
|
fn reload_index(&self) {
|
||||||
|
@ -464,31 +484,82 @@ impl GitRepository for RealGitRepository {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
|
fn load_index_text(
|
||||||
let repo = self.repository.clone();
|
&self,
|
||||||
|
index: Option<GitIndex>,
|
||||||
|
path: RepoPath,
|
||||||
|
) -> BoxFuture<Option<String>> {
|
||||||
|
let working_directory = self.working_directory();
|
||||||
|
let git_binary_path = self.git_binary_path.clone();
|
||||||
|
let executor = self.executor.clone();
|
||||||
self.executor
|
self.executor
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
|
match check_path_to_repo_path_errors(&path) {
|
||||||
// This check is required because index.get_path() unwraps internally :(
|
Ok(_) => {}
|
||||||
check_path_to_repo_path_errors(path)?;
|
Err(err) => {
|
||||||
|
log::error!("Error with repo path: {:?}", err);
|
||||||
let mut index = repo.index()?;
|
return None;
|
||||||
index.read(false)?;
|
}
|
||||||
|
|
||||||
const STAGE_NORMAL: i32 = 0;
|
|
||||||
let oid = match index.get_path(path, STAGE_NORMAL) {
|
|
||||||
Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id,
|
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = repo.find_blob(oid)?.content().to_owned();
|
|
||||||
Ok(Some(String::from_utf8(content)?))
|
|
||||||
}
|
}
|
||||||
match logic(&repo.lock(), &path) {
|
|
||||||
Ok(value) => return value,
|
let working_directory = match working_directory {
|
||||||
Err(err) => log::error!("Error loading index text: {:?}", err),
|
Ok(dir) => dir,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Error getting working directory: {:?}", err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut git = GitBinary::new(git_binary_path, working_directory, executor);
|
||||||
|
let text = git
|
||||||
|
.with_option_index(index, async |git| {
|
||||||
|
// First check if the file is a symlink using ls-files
|
||||||
|
let ls_files_output = git
|
||||||
|
.run(&[
|
||||||
|
OsStr::new("ls-files"),
|
||||||
|
OsStr::new("--stage"),
|
||||||
|
path.to_unix_style().as_ref(),
|
||||||
|
])
|
||||||
|
.await
|
||||||
|
.context("error running ls-files")?;
|
||||||
|
|
||||||
|
// Parse ls-files output to check if it's a symlink
|
||||||
|
// Format is: "100644 <sha> 0 <filename>" where 100644 is the mode
|
||||||
|
if ls_files_output.is_empty() {
|
||||||
|
return Ok(None); // File not in index
|
||||||
|
}
|
||||||
|
|
||||||
|
let parts: Vec<&str> = ls_files_output.split_whitespace().collect();
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"unexpected ls-files output format: {}",
|
||||||
|
ls_files_output
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a symlink (120000 mode)
|
||||||
|
if parts[0] == "120000" {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sha = parts[1];
|
||||||
|
|
||||||
|
// Now get the content
|
||||||
|
Ok(Some(
|
||||||
|
git.run_raw(&["cat-file", "blob", sha])
|
||||||
|
.await
|
||||||
|
.context("error getting blob content")?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match text {
|
||||||
|
Ok(text) => text,
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("Error getting text: {}", error);
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
@ -607,16 +678,36 @@ impl GitRepository for RealGitRepository {
|
||||||
shas
|
shas
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result<GitStatus>> {
|
fn status(
|
||||||
|
&self,
|
||||||
|
index: Option<GitIndex>,
|
||||||
|
path_prefixes: &[RepoPath],
|
||||||
|
) -> BoxFuture<'static, Result<GitStatus>> {
|
||||||
let working_directory = self.working_directory();
|
let working_directory = self.working_directory();
|
||||||
let git_binary_path = self.git_binary_path.clone();
|
let git_binary_path = self.git_binary_path.clone();
|
||||||
let executor = self.executor.clone();
|
let executor = self.executor.clone();
|
||||||
let args = git_status_args(path_prefixes);
|
let mut args = vec![
|
||||||
|
OsString::from("--no-optional-locks"),
|
||||||
|
OsString::from("status"),
|
||||||
|
OsString::from("--porcelain=v1"),
|
||||||
|
OsString::from("--untracked-files=all"),
|
||||||
|
OsString::from("--no-renames"),
|
||||||
|
OsString::from("-z"),
|
||||||
|
];
|
||||||
|
args.extend(path_prefixes.iter().map(|path_prefix| {
|
||||||
|
if path_prefix.0.as_ref() == Path::new("") {
|
||||||
|
Path::new(".").into()
|
||||||
|
} else {
|
||||||
|
path_prefix.as_os_str().into()
|
||||||
|
}
|
||||||
|
}));
|
||||||
self.executor
|
self.executor
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let working_directory = working_directory?;
|
let working_directory = working_directory?;
|
||||||
let git = GitBinary::new(git_binary_path, working_directory, executor);
|
let mut git = GitBinary::new(git_binary_path, working_directory, executor);
|
||||||
git.run(&args).await?.parse()
|
git.with_option_index(index, async |git| git.run(&args).await)
|
||||||
|
.await?
|
||||||
|
.parse()
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
@ -1071,7 +1162,7 @@ impl GitRepository for RealGitRepository {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checkpoint(&self) -> BoxFuture<Result<GitRepositoryCheckpoint>> {
|
fn checkpoint(&self) -> BoxFuture<'static, Result<GitRepositoryCheckpoint>> {
|
||||||
let working_directory = self.working_directory();
|
let working_directory = self.working_directory();
|
||||||
let git_binary_path = self.git_binary_path.clone();
|
let git_binary_path = self.git_binary_path.clone();
|
||||||
let executor = self.executor.clone();
|
let executor = self.executor.clone();
|
||||||
|
@ -1203,6 +1294,66 @@ impl GitRepository for RealGitRepository {
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diff_checkpoints(
|
||||||
|
&self,
|
||||||
|
base_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
target_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
) -> BoxFuture<Result<String>> {
|
||||||
|
let working_directory = self.working_directory();
|
||||||
|
let git_binary_path = self.git_binary_path.clone();
|
||||||
|
|
||||||
|
let executor = self.executor.clone();
|
||||||
|
self.executor
|
||||||
|
.spawn(async move {
|
||||||
|
let working_directory = working_directory?;
|
||||||
|
let git = GitBinary::new(git_binary_path, working_directory, executor);
|
||||||
|
git.run(&[
|
||||||
|
"diff",
|
||||||
|
"--find-renames",
|
||||||
|
"--patch",
|
||||||
|
&base_checkpoint.ref_name,
|
||||||
|
&target_checkpoint.ref_name,
|
||||||
|
])
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_index(&self) -> BoxFuture<Result<GitIndex>> {
|
||||||
|
let working_directory = self.working_directory();
|
||||||
|
let git_binary_path = self.git_binary_path.clone();
|
||||||
|
|
||||||
|
let executor = self.executor.clone();
|
||||||
|
self.executor
|
||||||
|
.spawn(async move {
|
||||||
|
let working_directory = working_directory?;
|
||||||
|
let mut git = GitBinary::new(git_binary_path, working_directory, executor);
|
||||||
|
let index = GitIndex { id: Uuid::new_v4() };
|
||||||
|
git.with_index(index, async move |git| git.run(&["add", "--all"]).await)
|
||||||
|
.await?;
|
||||||
|
Ok(index)
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_diff(&self, index: GitIndex, diff: String) -> BoxFuture<Result<()>> {
|
||||||
|
let working_directory = self.working_directory();
|
||||||
|
let git_binary_path = self.git_binary_path.clone();
|
||||||
|
|
||||||
|
let executor = self.executor.clone();
|
||||||
|
self.executor
|
||||||
|
.spawn(async move {
|
||||||
|
let working_directory = working_directory?;
|
||||||
|
let mut git = GitBinary::new(git_binary_path, working_directory, executor);
|
||||||
|
git.with_index(index, async move |git| {
|
||||||
|
git.run_with_stdin(&["apply", "--cached", "-"], diff).await
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn git_status_args(path_prefixes: &[RepoPath]) -> Vec<OsString> {
|
fn git_status_args(path_prefixes: &[RepoPath]) -> Vec<OsString> {
|
||||||
|
@ -1256,7 +1407,7 @@ impl GitBinary {
|
||||||
&mut self,
|
&mut self,
|
||||||
f: impl AsyncFnOnce(&Self) -> Result<R>,
|
f: impl AsyncFnOnce(&Self) -> Result<R>,
|
||||||
) -> Result<R> {
|
) -> Result<R> {
|
||||||
let index_file_path = self.working_directory.join(".git/index.tmp");
|
let index_file_path = self.path_for_index(GitIndex { id: Uuid::new_v4() });
|
||||||
|
|
||||||
let delete_temp_index = util::defer({
|
let delete_temp_index = util::defer({
|
||||||
let index_file_path = index_file_path.clone();
|
let index_file_path = index_file_path.clone();
|
||||||
|
@ -1281,7 +1432,81 @@ impl GitBinary {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn with_index<R>(
|
||||||
|
&mut self,
|
||||||
|
index: GitIndex,
|
||||||
|
f: impl AsyncFnOnce(&Self) -> Result<R>,
|
||||||
|
) -> Result<R> {
|
||||||
|
self.with_option_index(Some(index), f).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_option_index<R>(
|
||||||
|
&mut self,
|
||||||
|
index: Option<GitIndex>,
|
||||||
|
f: impl AsyncFnOnce(&Self) -> Result<R>,
|
||||||
|
) -> Result<R> {
|
||||||
|
let new_index_path = index.map(|index| self.path_for_index(index));
|
||||||
|
let old_index_path = mem::replace(&mut self.index_file_path, new_index_path);
|
||||||
|
let result = f(self).await;
|
||||||
|
self.index_file_path = old_index_path;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_for_index(&self, index: GitIndex) -> PathBuf {
|
||||||
|
self.working_directory
|
||||||
|
.join(".git")
|
||||||
|
.join(format!("index-{}.tmp", index.id))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn run<S>(&self, args: impl IntoIterator<Item = S>) -> Result<String>
|
pub async fn run<S>(&self, args: impl IntoIterator<Item = S>) -> Result<String>
|
||||||
|
where
|
||||||
|
S: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
let mut stdout = self.run_raw(args).await?;
|
||||||
|
if stdout.chars().last() == Some('\n') {
|
||||||
|
stdout.pop();
|
||||||
|
}
|
||||||
|
Ok(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the result of the command without trimming the trailing newline.
|
||||||
|
pub async fn run_raw<S>(&self, args: impl IntoIterator<Item = S>) -> Result<String>
|
||||||
|
where
|
||||||
|
S: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
let mut command = self.build_command(args);
|
||||||
|
let output = command.output().await?;
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(String::from_utf8(output.stdout)?)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(GitBinaryCommandError {
|
||||||
|
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
||||||
|
status: output.status,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_with_stdin(&self, args: &[&str], stdin: String) -> Result<String> {
|
||||||
|
let mut command = self.build_command(args);
|
||||||
|
command.stdin(Stdio::piped());
|
||||||
|
let mut child = command.spawn()?;
|
||||||
|
|
||||||
|
let mut child_stdin = child.stdin.take().context("failed to write to stdin")?;
|
||||||
|
child_stdin.write_all(stdin.as_bytes()).await?;
|
||||||
|
drop(child_stdin);
|
||||||
|
|
||||||
|
let output = child.output().await?;
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(String::from_utf8(output.stdout)?.trim_end().to_string())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(GitBinaryCommandError {
|
||||||
|
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
||||||
|
status: output.status,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_command<S>(&self, args: impl IntoIterator<Item = S>) -> smol::process::Command
|
||||||
where
|
where
|
||||||
S: AsRef<OsStr>,
|
S: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
|
@ -1292,15 +1517,7 @@ impl GitBinary {
|
||||||
command.env("GIT_INDEX_FILE", index_file_path);
|
command.env("GIT_INDEX_FILE", index_file_path);
|
||||||
}
|
}
|
||||||
command.envs(&self.envs);
|
command.envs(&self.envs);
|
||||||
let output = command.output().await?;
|
command
|
||||||
if output.status.success() {
|
|
||||||
anyhow::Ok(String::from_utf8(output.stdout)?.trim_end().to_string())
|
|
||||||
} else {
|
|
||||||
Err(anyhow!(GitBinaryCommandError {
|
|
||||||
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
|
||||||
status: output.status,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1570,8 +1787,9 @@ fn checkpoint_author_envs() -> HashMap<String, String> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::status::FileStatus;
|
use crate::status::{FileStatus, StatusCode, TrackedStatus};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
|
use unindent::Unindent;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_checkpoint_basic(cx: &mut TestAppContext) {
|
async fn test_checkpoint_basic(cx: &mut TestAppContext) {
|
||||||
|
@ -1751,7 +1969,7 @@ mod tests {
|
||||||
"content2"
|
"content2"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
repo.status(&[]).await.unwrap().entries.as_ref(),
|
repo.status(None, &[]).await.unwrap().entries.as_ref(),
|
||||||
&[
|
&[
|
||||||
(RepoPath::from_str("new_file1"), FileStatus::Untracked),
|
(RepoPath::from_str("new_file1"), FileStatus::Untracked),
|
||||||
(RepoPath::from_str("new_file2"), FileStatus::Untracked)
|
(RepoPath::from_str("new_file2"), FileStatus::Untracked)
|
||||||
|
@ -1790,6 +2008,90 @@ mod tests {
|
||||||
.unwrap());
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_secondary_indices(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, cx.executor()).unwrap();
|
||||||
|
let index = repo.create_index().await.unwrap();
|
||||||
|
smol::fs::write(repo_dir.path().join("file1"), "file1\n")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
smol::fs::write(repo_dir.path().join("file2"), "file2\n")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let diff = r#"
|
||||||
|
diff --git a/file2 b/file2
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..cbc4e2e
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/file2
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+file2
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
repo.apply_diff(index, diff.to_string()).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
repo.status(Some(index), &[])
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.entries
|
||||||
|
.as_ref(),
|
||||||
|
vec![
|
||||||
|
(RepoPath::from_str("file1"), FileStatus::Untracked),
|
||||||
|
(
|
||||||
|
RepoPath::from_str("file2"),
|
||||||
|
FileStatus::index(StatusCode::Added)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repo.load_index_text(Some(index), RepoPath::from_str("file1"))
|
||||||
|
.await,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repo.load_index_text(Some(index), RepoPath::from_str("file2"))
|
||||||
|
.await,
|
||||||
|
Some("file2\n".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
smol::fs::write(repo_dir.path().join("file2"), "file2-changed\n")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
repo.status(Some(index), &[])
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.entries
|
||||||
|
.as_ref(),
|
||||||
|
vec![
|
||||||
|
(RepoPath::from_str("file1"), FileStatus::Untracked),
|
||||||
|
(
|
||||||
|
RepoPath::from_str("file2"),
|
||||||
|
FileStatus::Tracked(TrackedStatus {
|
||||||
|
worktree_status: StatusCode::Modified,
|
||||||
|
index_status: StatusCode::Added,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repo.load_index_text(Some(index), RepoPath::from_str("file1"))
|
||||||
|
.await,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repo.load_index_text(Some(index), RepoPath::from_str("file2"))
|
||||||
|
.await,
|
||||||
|
Some("file2\n".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_branches_parsing() {
|
fn test_branches_parsing() {
|
||||||
// suppress "help: octal escapes are not supported, `\0` is always null"
|
// suppress "help: octal escapes are not supported, `\0` is always null"
|
||||||
|
|
|
@ -2259,7 +2259,7 @@ impl GitPanel {
|
||||||
|
|
||||||
let repo = repo.read(cx);
|
let repo = repo.read(cx);
|
||||||
|
|
||||||
for entry in repo.status() {
|
for entry in repo.cached_status() {
|
||||||
let is_conflict = repo.has_conflict(&entry.repo_path);
|
let is_conflict = repo.has_conflict(&entry.repo_path);
|
||||||
let is_new = entry.status.is_created();
|
let is_new = entry.status.is_created();
|
||||||
let staging = entry.status.staging();
|
let staging = entry.status.staging();
|
||||||
|
|
|
@ -339,7 +339,7 @@ impl ProjectDiff {
|
||||||
|
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
repo.update(cx, |repo, cx| {
|
repo.update(cx, |repo, cx| {
|
||||||
for entry in repo.status() {
|
for entry in repo.cached_status() {
|
||||||
if !entry.status.has_changes() {
|
if !entry.status.has_changes() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,10 @@ use git::{
|
||||||
blame::Blame,
|
blame::Blame,
|
||||||
parse_git_remote_url,
|
parse_git_remote_url,
|
||||||
repository::{
|
repository::{
|
||||||
Branch, CommitDetails, DiffType, GitRepository, GitRepositoryCheckpoint, PushOptions,
|
Branch, CommitDetails, DiffType, GitIndex, GitRepository, GitRepositoryCheckpoint,
|
||||||
Remote, RemoteCommandOutput, RepoPath, ResetMode,
|
PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,
|
||||||
},
|
},
|
||||||
status::FileStatus,
|
status::{FileStatus, GitStatus},
|
||||||
BuildPermalinkParams, GitHostingProviderRegistry,
|
BuildPermalinkParams, GitHostingProviderRegistry,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -146,6 +146,22 @@ pub struct GitStoreCheckpoint {
|
||||||
checkpoints_by_work_dir_abs_path: HashMap<PathBuf, GitRepositoryCheckpoint>,
|
checkpoints_by_work_dir_abs_path: HashMap<PathBuf, GitRepositoryCheckpoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GitStoreDiff {
|
||||||
|
diffs_by_work_dir_abs_path: HashMap<PathBuf, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GitStoreIndex {
|
||||||
|
indices_by_work_dir_abs_path: HashMap<PathBuf, GitIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GitStoreStatus {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
statuses_by_work_dir_abs_path: HashMap<PathBuf, GitStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Repository {
|
pub struct Repository {
|
||||||
pub repository_entry: RepositoryEntry,
|
pub repository_entry: RepositoryEntry,
|
||||||
pub merge_message: Option<String>,
|
pub merge_message: Option<String>,
|
||||||
|
@ -651,8 +667,8 @@ impl GitStore {
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
for (dot_git_abs_path, checkpoint) in checkpoint.checkpoints_by_work_dir_abs_path {
|
for (work_dir_abs_path, checkpoint) in checkpoint.checkpoints_by_work_dir_abs_path {
|
||||||
if let Some(repository) = repositories_by_work_dir_abs_path.get(&dot_git_abs_path) {
|
if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path) {
|
||||||
let restore = repository.read(cx).restore_checkpoint(checkpoint);
|
let restore = repository.read(cx).restore_checkpoint(checkpoint);
|
||||||
tasks.push(async move { restore.await? });
|
tasks.push(async move { restore.await? });
|
||||||
}
|
}
|
||||||
|
@ -685,12 +701,13 @@ impl GitStore {
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
for (dot_git_abs_path, left_checkpoint) in left.checkpoints_by_work_dir_abs_path {
|
for (work_dir_abs_path, left_checkpoint) in left.checkpoints_by_work_dir_abs_path {
|
||||||
if let Some(right_checkpoint) = right
|
if let Some(right_checkpoint) = right
|
||||||
.checkpoints_by_work_dir_abs_path
|
.checkpoints_by_work_dir_abs_path
|
||||||
.remove(&dot_git_abs_path)
|
.remove(&work_dir_abs_path)
|
||||||
{
|
{
|
||||||
if let Some(repository) = repositories_by_work_dir_abs_path.get(&dot_git_abs_path) {
|
if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path)
|
||||||
|
{
|
||||||
let compare = repository
|
let compare = repository
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.compare_checkpoints(left_checkpoint, right_checkpoint);
|
.compare_checkpoints(left_checkpoint, right_checkpoint);
|
||||||
|
@ -738,6 +755,113 @@ impl GitStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn diff_checkpoints(
|
||||||
|
&self,
|
||||||
|
base_checkpoint: GitStoreCheckpoint,
|
||||||
|
target_checkpoint: GitStoreCheckpoint,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<Result<GitStoreDiff>> {
|
||||||
|
let repositories_by_work_dir_abs_path = self
|
||||||
|
.repositories
|
||||||
|
.values()
|
||||||
|
.map(|repo| {
|
||||||
|
(
|
||||||
|
repo.read(cx)
|
||||||
|
.repository_entry
|
||||||
|
.work_directory_abs_path
|
||||||
|
.clone(),
|
||||||
|
repo,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
for (work_dir_abs_path, base_checkpoint) in base_checkpoint.checkpoints_by_work_dir_abs_path
|
||||||
|
{
|
||||||
|
if let Some(target_checkpoint) = target_checkpoint
|
||||||
|
.checkpoints_by_work_dir_abs_path
|
||||||
|
.get(&work_dir_abs_path)
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path)
|
||||||
|
{
|
||||||
|
let diff = repository
|
||||||
|
.read(cx)
|
||||||
|
.diff_checkpoints(base_checkpoint, target_checkpoint);
|
||||||
|
tasks.push(async move {
|
||||||
|
let diff = diff.await??;
|
||||||
|
anyhow::Ok((work_dir_abs_path, diff))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let diffs_by_path = future::try_join_all(tasks).await?;
|
||||||
|
Ok(GitStoreDiff {
|
||||||
|
diffs_by_work_dir_abs_path: diffs_by_path.into_iter().collect(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_index(&self, cx: &App) -> Task<Result<GitStoreIndex>> {
|
||||||
|
let mut indices = Vec::new();
|
||||||
|
for repository in self.repositories.values() {
|
||||||
|
let repository = repository.read(cx);
|
||||||
|
let work_dir_abs_path = repository.repository_entry.work_directory_abs_path.clone();
|
||||||
|
let index = repository.create_index().map(|index| index?);
|
||||||
|
indices.push(async move {
|
||||||
|
let index = index.await?;
|
||||||
|
anyhow::Ok((work_dir_abs_path, index))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.background_executor().spawn(async move {
|
||||||
|
let indices = future::try_join_all(indices).await?;
|
||||||
|
Ok(GitStoreIndex {
|
||||||
|
indices_by_work_dir_abs_path: indices.into_iter().collect(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_diff(
|
||||||
|
&self,
|
||||||
|
mut index: GitStoreIndex,
|
||||||
|
diff: GitStoreDiff,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
let repositories_by_work_dir_abs_path = self
|
||||||
|
.repositories
|
||||||
|
.values()
|
||||||
|
.map(|repo| {
|
||||||
|
(
|
||||||
|
repo.read(cx)
|
||||||
|
.repository_entry
|
||||||
|
.work_directory_abs_path
|
||||||
|
.clone(),
|
||||||
|
repo,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
for (work_dir_abs_path, diff) in diff.diffs_by_work_dir_abs_path {
|
||||||
|
if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path) {
|
||||||
|
if let Some(branch) = index
|
||||||
|
.indices_by_work_dir_abs_path
|
||||||
|
.remove(&work_dir_abs_path)
|
||||||
|
{
|
||||||
|
let apply = repository.read(cx).apply_diff(branch, diff);
|
||||||
|
tasks.push(async move { apply.await? });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
future::try_join_all(tasks).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Blames a buffer.
|
/// Blames a buffer.
|
||||||
pub fn blame_buffer(
|
pub fn blame_buffer(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1282,7 +1406,7 @@ impl GitStore {
|
||||||
let index_text = if current_index_text.is_some() {
|
let index_text = if current_index_text.is_some() {
|
||||||
local_repo
|
local_repo
|
||||||
.repo()
|
.repo()
|
||||||
.load_index_text(relative_path.clone())
|
.load_index_text(None, relative_path.clone())
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -1397,6 +1521,87 @@ impl GitStore {
|
||||||
Some(status.status)
|
Some(status.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn status(&self, index: Option<GitStoreIndex>, cx: &App) -> Task<Result<GitStoreStatus>> {
|
||||||
|
let repositories_by_work_dir_abs_path = self
|
||||||
|
.repositories
|
||||||
|
.values()
|
||||||
|
.map(|repo| {
|
||||||
|
(
|
||||||
|
repo.read(cx)
|
||||||
|
.repository_entry
|
||||||
|
.work_directory_abs_path
|
||||||
|
.clone(),
|
||||||
|
repo,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
|
if let Some(index) = index {
|
||||||
|
// When we have an index, just check the repositories that are part of it
|
||||||
|
for (work_dir_abs_path, git_index) in index.indices_by_work_dir_abs_path {
|
||||||
|
if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path)
|
||||||
|
{
|
||||||
|
let status = repository.read(cx).status(Some(git_index));
|
||||||
|
tasks.push(
|
||||||
|
async move {
|
||||||
|
let status = status.await??;
|
||||||
|
anyhow::Ok((work_dir_abs_path, status))
|
||||||
|
}
|
||||||
|
.boxed(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, check all repositories
|
||||||
|
for repository in self.repositories.values() {
|
||||||
|
let repository = repository.read(cx);
|
||||||
|
let work_dir_abs_path = repository.repository_entry.work_directory_abs_path.clone();
|
||||||
|
let status = repository.status(None);
|
||||||
|
tasks.push(
|
||||||
|
async move {
|
||||||
|
let status = status.await??;
|
||||||
|
anyhow::Ok((work_dir_abs_path, status))
|
||||||
|
}
|
||||||
|
.boxed(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.background_executor().spawn(async move {
|
||||||
|
let statuses = future::try_join_all(tasks).await?;
|
||||||
|
Ok(GitStoreStatus {
|
||||||
|
statuses_by_work_dir_abs_path: statuses.into_iter().collect(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_index_text(
|
||||||
|
&self,
|
||||||
|
index: Option<GitStoreIndex>,
|
||||||
|
buffer: &Entity<Buffer>,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<Option<String>> {
|
||||||
|
let Some((repository, path)) =
|
||||||
|
self.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
|
||||||
|
else {
|
||||||
|
return Task::ready(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let git_index = index.and_then(|index| {
|
||||||
|
index
|
||||||
|
.indices_by_work_dir_abs_path
|
||||||
|
.get(&repository.read(cx).repository_entry.work_directory_abs_path)
|
||||||
|
.copied()
|
||||||
|
});
|
||||||
|
let text = repository.read(cx).load_index_text(git_index, path);
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let text = text.await;
|
||||||
|
text.ok().flatten()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn repository_and_path_for_buffer_id(
|
pub fn repository_and_path_for_buffer_id(
|
||||||
&self,
|
&self,
|
||||||
buffer_id: BufferId,
|
buffer_id: BufferId,
|
||||||
|
@ -2642,10 +2847,34 @@ impl Repository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
|
pub fn cached_status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
|
||||||
self.repository_entry.status()
|
self.repository_entry.status()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn status(&self, index: Option<GitIndex>) -> oneshot::Receiver<Result<GitStatus>> {
|
||||||
|
self.send_job(move |repo, _cx| async move {
|
||||||
|
match repo {
|
||||||
|
RepositoryState::Local(git_repository) => git_repository.status(index, &[]).await,
|
||||||
|
RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_index_text(
|
||||||
|
&self,
|
||||||
|
index: Option<GitIndex>,
|
||||||
|
path: RepoPath,
|
||||||
|
) -> oneshot::Receiver<Option<String>> {
|
||||||
|
self.send_job(move |repo, _cx| async move {
|
||||||
|
match repo {
|
||||||
|
RepositoryState::Local(git_repository) => {
|
||||||
|
git_repository.load_index_text(index, path).await
|
||||||
|
}
|
||||||
|
RepositoryState::Remote { .. } => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_conflict(&self, path: &RepoPath) -> bool {
|
pub fn has_conflict(&self, path: &RepoPath) -> bool {
|
||||||
self.repository_entry
|
self.repository_entry
|
||||||
.current_merge_conflicts
|
.current_merge_conflicts
|
||||||
|
@ -3533,6 +3762,43 @@ impl Repository {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn diff_checkpoints(
|
||||||
|
&self,
|
||||||
|
base_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
target_checkpoint: GitRepositoryCheckpoint,
|
||||||
|
) -> oneshot::Receiver<Result<String>> {
|
||||||
|
self.send_job(move |repo, _cx| async move {
|
||||||
|
match repo {
|
||||||
|
RepositoryState::Local(git_repository) => {
|
||||||
|
git_repository
|
||||||
|
.diff_checkpoints(base_checkpoint, target_checkpoint)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_index(&self) -> oneshot::Receiver<Result<GitIndex>> {
|
||||||
|
self.send_job(move |repo, _cx| async move {
|
||||||
|
match repo {
|
||||||
|
RepositoryState::Local(git_repository) => git_repository.create_index().await,
|
||||||
|
RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_diff(&self, index: GitIndex, diff: String) -> oneshot::Receiver<Result<()>> {
|
||||||
|
self.send_job(move |repo, _cx| async move {
|
||||||
|
match repo {
|
||||||
|
RepositoryState::Local(git_repository) => {
|
||||||
|
git_repository.apply_diff(index, diff).await
|
||||||
|
}
|
||||||
|
RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_permalink_in_rust_registry_src(
|
fn get_permalink_in_rust_registry_src(
|
||||||
|
|
|
@ -1041,7 +1041,10 @@ impl Worktree {
|
||||||
if let Some(git_repo) =
|
if let Some(git_repo) =
|
||||||
snapshot.git_repositories.get(&repo.work_directory_id)
|
snapshot.git_repositories.get(&repo.work_directory_id)
|
||||||
{
|
{
|
||||||
return Ok(git_repo.repo_ptr.load_index_text(repo_path).await);
|
return Ok(git_repo
|
||||||
|
.repo_ptr
|
||||||
|
.load_index_text(None, repo_path)
|
||||||
|
.await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue