use crate::commit::parse_git_diff_name_status;use crate::stash::GitStash;use crate::status::{DiffTreeType, GitStatus, StatusCode, TreeDiff};use crate::{Oid, SHORT_SHA_LENGTH};use anyhow::{Context as _, Result, anyhow, bail};use collections::HashMap;use futures::future::BoxFuture;use futures::io::BufWriter;use futures::{AsyncWriteExt, FutureExt as _, select_biased};use git2::BranchType;use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Task};use parking_lot::Mutex;use rope::Rope;use schemars::JsonSchema;use serde::{Deserialize, Serialize};use smol::io::{AsyncBufReadExt, AsyncReadExt, BufReader};use std::ffi::{OsStr, OsString};use std::process::{ExitStatus, Stdio};use std::{ cmp::Ordering, future, path::{Path, PathBuf}, sync::Arc,};use sum_tree::MapSeekTarget;use thiserror::Error;use util::command::new_smol_command;use util::paths::PathStyle;use util::rel_path::RelPath;use util::{ResultExt, paths};use uuid::Uuid; pub use askpass::{AskPassDelegate, AskPassResult, AskPassSession}; pub const REMOTE_CANCELLED_BY_USER: &str = "Operation cancelled by user"; #[derive(Clone, Debug, Hash, PartialEq, Eq)]pub struct Branch { pub is_head: bool, pub ref_name: SharedString, pub upstream: Option<Upstream>, pub most_recent_commit: Option<CommitSummary>,} impl Branch { pub fn name(&self) -> &str { self.ref_name .as_ref() .strip_prefix("refs/heads/") .or_else(|| self.ref_name.as_ref().strip_prefix("refs/remotes/")) .unwrap_or(self.ref_name.as_ref()) } pub fn is_remote(&self) -> bool { self.ref_name.starts_with("refs/remotes/") } pub fn tracking_status(&self) -> Option<UpstreamTrackingStatus> { self.upstream .as_ref() .and_then(|upstream| upstream.tracking.status()) } pub fn priority_key(&self) -> (bool, Option<i64>) { ( self.is_head, self.most_recent_commit .as_ref() .map(|commit| commit.commit_timestamp), ) }} #[derive(Clone, Debug, Hash, PartialEq, Eq)]pub struct Worktree { pub path: PathBuf, pub ref_name: SharedString, pub sha: SharedString,} impl Worktree { pub fn branch(&self) -> &str { self.ref_name .as_ref() .strip_prefix("refs/heads/") .or_else(|| self.ref_name.as_ref().strip_prefix("refs/remotes/")) .unwrap_or(self.ref_name.as_ref()) }} pub fn parse_worktrees_from_str<T: AsRef<str>>(raw_worktrees: T) -> Vec<Worktree> { let mut worktrees = Vec::new(); let entries = raw_worktrees.as_ref().split("\n\n"); for entry in entries { let mut parts = entry.splitn(3, '\n'); let path = parts .next() .and_then(|p| p.split_once(' ').map(|(_, path)| path.to_string())); let sha = parts .next() .and_then(|p| p.split_once(' ').map(|(_, sha)| sha.to_string())); let ref_name = parts .next() .and_then(|p| p.split_once(' ').map(|(_, ref_name)| ref_name.to_string())); if let (Some(path), Some(sha), Some(ref_name)) = (path, sha, ref_name) { worktrees.push(Worktree { path: PathBuf::from(path), ref_name: ref_name.into(), sha: sha.into(), }) } } worktrees} #[derive(Clone, Debug, Hash, PartialEq, Eq)]pub struct Upstream { pub ref_name: SharedString, pub tracking: UpstreamTracking,} impl Upstream { pub fn is_remote(&self) -> bool { self.remote_name().is_some() } pub fn remote_name(&self) -> Option<&str> { self.ref_name .strip_prefix("refs/remotes/") .and_then(|stripped| stripped.split("/").next()) } pub fn stripped_ref_name(&self) -> Option<&str> { self.ref_name.strip_prefix("refs/remotes/") }} #[derive(Clone, Copy, Debug, Default)]pub enum CommitMessageCleanup { #[default] Strip, Whitespace,} #[derive(Clone, Copy, Default)]pub struct CommitOptions { pub amend: bool, pub signoff: bool, pub cleanup: CommitMessageCleanup,} #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]pub enum UpstreamTracking { /// Remote ref not present in local repository. Gone, /// Remote ref present in local repository (fetched from remote). Tracked(UpstreamTrackingStatus),} impl From<UpstreamTrackingStatus> for UpstreamTracking { fn from(status: UpstreamTrackingStatus) -> Self { UpstreamTracking::Tracked(status) }} impl UpstreamTracking { pub fn is_gone(&self) -> bool { matches!(self, UpstreamTracking::Gone) } pub fn status(&self) -> Option<UpstreamTrackingStatus> { match self { UpstreamTracking::Gone => None, UpstreamTracking::Tracked(status) => Some(*status), } }} #[derive(Debug, Clone)]pub struct RemoteCommandOutput { pub stdout: String, pub stderr: String,} impl RemoteCommandOutput { pub fn is_empty(&self) -> bool { self.stdout.is_empty() && self.stderr.is_empty() }} #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]pub struct UpstreamTrackingStatus { pub ahead: u32, pub behind: u32,} #[derive(Clone, Debug, Hash, PartialEq, Eq)]pub struct CommitSummary { pub sha: SharedString, pub subject: SharedString, /// This is a unix timestamp pub commit_timestamp: i64, pub author_name: SharedString, pub has_parent: bool,} #[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]pub struct CommitDetails { pub sha: SharedString, pub message: SharedString, pub commit_timestamp: i64, pub author_email: SharedString, pub author_name: SharedString,} #[derive(Debug)]pub struct CommitDiff { pub files: Vec<CommitFile>,} #[derive(Debug)]pub struct CommitFile { pub path: RepoPath, pub old_text: Option<String>, pub new_text: Option<String>,} impl CommitDetails { pub fn short_sha(&self) -> SharedString { self.sha[..SHORT_SHA_LENGTH].to_string().into() }} #[derive(Debug, Clone, Hash, PartialEq, Eq)]pub struct Remote { pub name: SharedString,} pub enum ResetMode { /// Reset the branch pointer, leave index and worktree unchanged (this will make it look like things that were /// committed are now staged). Soft, /// Reset the branch pointer and index, leave worktree unchanged (this makes it look as though things that were /// committed are now unstaged). Mixed,} #[derive(Debug, Clone, Hash, PartialEq, Eq)]pub enum FetchOptions { All, Remote(Remote),} impl FetchOptions { pub fn to_proto(&self) -> Option<String> { match self { FetchOptions::All => None, FetchOptions::Remote(remote) => Some(remote.clone().name.into()), } } pub fn from_proto(remote_name: Option<String>) -> Self { match remote_name { Some(name) => FetchOptions::Remote(Remote { name: name.into() }), None => FetchOptions::All, } } pub fn name(&self) -> SharedString { match self { Self::All => "Fetch all remotes".into(), Self::Remote(remote) => remote.name.clone(), } }} impl std::fmt::Display for FetchOptions { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FetchOptions::All => write!(f, "--all"), FetchOptions::Remote(remote) => write!(f, "{}", remote.name), } }} /// Modifies .git/info/exclude temporarilypub struct GitExcludeOverride { git_exclude_path: PathBuf, original_excludes: Option<String>, added_excludes: Option<String>,} impl GitExcludeOverride { const START_BLOCK_MARKER: &str = "\n\n# ====== Auto-added by Zed: =======\n"; const END_BLOCK_MARKER: &str = "\n# ====== End of auto-added by Zed =======\n"; pub async fn new(git_exclude_path: PathBuf) -> Result<Self> { let original_excludes = smol::fs::read_to_string(&git_exclude_path) .await .ok() .map(|content| { // Auto-generated lines are normally cleaned up in // `restore_original()` or `drop()`, but may stuck in rare cases. // Make sure to remove them. Self::remove_auto_generated_block(&content) }); Ok(GitExcludeOverride { git_exclude_path, original_excludes, added_excludes: None, }) } pub async fn add_excludes(&mut self, excludes: &str) -> Result<()> { self.added_excludes = Some(if let Some(ref already_added) = self.added_excludes { format!("{already_added}\n{excludes}") } else { excludes.to_string() }); let mut content = self.original_excludes.clone().unwrap_or_default(); content.push_str(Self::START_BLOCK_MARKER); content.push_str(self.added_excludes.as_ref().unwrap()); content.push_str(Self::END_BLOCK_MARKER); smol::fs::write(&self.git_exclude_path, content).await?; Ok(()) } pub async fn restore_original(&mut self) -> Result<()> { if let Some(ref original) = self.original_excludes { smol::fs::write(&self.git_exclude_path, original).await?; } else if self.git_exclude_path.exists() { smol::fs::remove_file(&self.git_exclude_path).await?; } self.added_excludes = None; Ok(()) } fn remove_auto_generated_block(content: &str) -> String { let start_marker = Self::START_BLOCK_MARKER; let end_marker = Self::END_BLOCK_MARKER; let mut content = content.to_string(); let start_index = content.find(start_marker); let end_index = content.rfind(end_marker); if let (Some(start), Some(end)) = (start_index, end_index) { if end > start { content.replace_range(start..end + end_marker.len(), ""); } } // Older versions of Zed didn't have end-of-block markers, // so it's impossible to determine auto-generated lines. // Conservatively remove the standard list of excludes let standard_excludes = format!( "{}{}", Self::START_BLOCK_MARKER, include_str!("./checkpoint.gitignore") ); content = content.replace(&standard_excludes, ""); content }} impl Drop for GitExcludeOverride { fn drop(&mut self) { if self.added_excludes.is_some() { let git_exclude_path = self.git_exclude_path.clone(); let original_excludes = self.original_excludes.clone(); smol::spawn(async move { if let Some(original) = original_excludes { smol::fs::write(&git_exclude_path, original).await } else { smol::fs::remove_file(&git_exclude_path).await } }) .detach(); } }} pub trait GitRepository: Send + Sync { fn reload_index(&self); /// 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. fn load_index_text(&self, 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. /// /// Also returns `None` for symlinks. fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>>; fn load_blob_content(&self, oid: Oid) -> BoxFuture<'_, Result<String>>; fn set_index_text( &self, path: RepoPath, content: Option<String>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, anyhow::Result<()>>; /// Returns the URL of the remote with the given name. fn remote_url(&self, name: &str) -> Option<String>; /// Resolve a list of refs to SHAs. fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<'_, Result<Vec<Option<String>>>>; fn head_sha(&self) -> BoxFuture<'_, Option<String>> { async move { self.revparse_batch(vec!["HEAD".into()]) .await .unwrap_or_default() .into_iter() .next() .flatten() } .boxed() } fn merge_message(&self) -> BoxFuture<'_, Option<String>>; fn status(&self, path_prefixes: &[RepoPath]) -> Task<Result<GitStatus>>; fn diff_tree(&self, request: DiffTreeType) -> BoxFuture<'_, Result<TreeDiff>>; fn stash_entries(&self) -> BoxFuture<'_, Result<GitStash>>; fn branches(&self) -> BoxFuture<'_, Result<Vec<Branch>>>; fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>>; fn create_branch(&self, name: String, base_branch: Option<String>) -> BoxFuture<'_, Result<()>>; fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>>; fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>>; fn create_worktree( &self, name: String, directory: PathBuf, from_commit: Option<String>, ) -> BoxFuture<'_, Result<()>>; fn reset( &self, commit: String, mode: ResetMode, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; fn checkout_files( &self, commit: String, paths: Vec<RepoPath>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; fn show(&self, commit: String) -> BoxFuture<'_, Result<CommitDetails>>; fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>>; fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>>; /// Returns the absolute path to the repository. For worktrees, this will be the path to the /// worktree's gitdir within the main repository (typically `.git/worktrees/<name>`). fn path(&self) -> PathBuf; fn main_repository_path(&self) -> PathBuf; /// Updates the index to match the worktree at the given paths. /// /// If any of the paths have been deleted from the worktree, they will be removed from the index if found there. fn stage_paths( &self, paths: Vec<RepoPath>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; /// Updates the index to match HEAD at the given paths. /// /// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index. fn unstage_paths( &self, paths: Vec<RepoPath>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; fn commit( &self, message: SharedString, name_and_email: Option<(SharedString, SharedString)>, options: CommitOptions,Lines 1–500 of 2829. Load more
use crate::commit::parse_git_diff_name_status; use crate::stash::GitStash; use crate::status::{DiffTreeType, GitStatus, StatusCode, TreeDiff}; use crate::{Oid, SHORT_SHA_LENGTH}; use anyhow::{Context as _, Result, anyhow, bail}; use collections::HashMap; use futures::future::BoxFuture; use futures::io::BufWriter; use futures::{AsyncWriteExt, FutureExt as _, select_biased}; use git2::BranchType; use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Task}; use parking_lot::Mutex; use rope::Rope; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use smol::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use std::ffi::{OsStr, OsString}; use std::process::{ExitStatus, Stdio}; use std::{ cmp::Ordering, future, path::{Path, PathBuf}, sync::Arc, }; use sum_tree::MapSeekTarget; use thiserror::Error; use util::command::new_smol_command; use util::paths::PathStyle; use util::rel_path::RelPath; use util::{ResultExt, paths}; use uuid::Uuid; pub use askpass::{AskPassDelegate, AskPassResult, AskPassSession}; pub const REMOTE_CANCELLED_BY_USER: &str = "Operation cancelled by user"; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Branch { pub is_head: bool, pub ref_name: SharedString, pub upstream: Option<Upstream>, pub most_recent_commit: Option<CommitSummary>, } impl Branch { pub fn name(&self) -> &str { self.ref_name .as_ref() .strip_prefix("refs/heads/") .or_else(|| self.ref_name.as_ref().strip_prefix("refs/remotes/")) .unwrap_or(self.ref_name.as_ref()) } pub fn is_remote(&self) -> bool { self.ref_name.starts_with("refs/remotes/") } pub fn tracking_status(&self) -> Option<UpstreamTrackingStatus> { self.upstream .as_ref() .and_then(|upstream| upstream.tracking.status()) } pub fn priority_key(&self) -> (bool, Option<i64>) { ( self.is_head, self.most_recent_commit .as_ref() .map(|commit| commit.commit_timestamp), ) } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Worktree { pub path: PathBuf, pub ref_name: SharedString, pub sha: SharedString, } impl Worktree { pub fn branch(&self) -> &str { self.ref_name .as_ref() .strip_prefix("refs/heads/") .or_else(|| self.ref_name.as_ref().strip_prefix("refs/remotes/")) .unwrap_or(self.ref_name.as_ref()) } } pub fn parse_worktrees_from_str<T: AsRef<str>>(raw_worktrees: T) -> Vec<Worktree> { let mut worktrees = Vec::new(); let entries = raw_worktrees.as_ref().split("\n\n"); for entry in entries { let mut parts = entry.splitn(3, '\n'); let path = parts .next() .and_then(|p| p.split_once(' ').map(|(_, path)| path.to_string())); let sha = parts .next() .and_then(|p| p.split_once(' ').map(|(_, sha)| sha.to_string())); let ref_name = parts .next() .and_then(|p| p.split_once(' ').map(|(_, ref_name)| ref_name.to_string())); if let (Some(path), Some(sha), Some(ref_name)) = (path, sha, ref_name) { worktrees.push(Worktree { path: PathBuf::from(path), ref_name: ref_name.into(), sha: sha.into(), }) } } worktrees } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Upstream { pub ref_name: SharedString, pub tracking: UpstreamTracking, } impl Upstream { pub fn is_remote(&self) -> bool { self.remote_name().is_some() } pub fn remote_name(&self) -> Option<&str> { self.ref_name .strip_prefix("refs/remotes/") .and_then(|stripped| stripped.split("/").next()) } pub fn stripped_ref_name(&self) -> Option<&str> { self.ref_name.strip_prefix("refs/remotes/") } } #[derive(Clone, Copy, Debug, Default)] pub enum CommitMessageCleanup { #[default] Strip, Whitespace, } #[derive(Clone, Copy, Default)] pub struct CommitOptions { pub amend: bool, pub signoff: bool, pub cleanup: CommitMessageCleanup, } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum UpstreamTracking { /// Remote ref not present in local repository. Gone, /// Remote ref present in local repository (fetched from remote). Tracked(UpstreamTrackingStatus), } impl From<UpstreamTrackingStatus> for UpstreamTracking { fn from(status: UpstreamTrackingStatus) -> Self { UpstreamTracking::Tracked(status) } } impl UpstreamTracking { pub fn is_gone(&self) -> bool { matches!(self, UpstreamTracking::Gone) } pub fn status(&self) -> Option<UpstreamTrackingStatus> { match self { UpstreamTracking::Gone => None, UpstreamTracking::Tracked(status) => Some(*status), } } } #[derive(Debug, Clone)] pub struct RemoteCommandOutput { pub stdout: String, pub stderr: String, } impl RemoteCommandOutput { pub fn is_empty(&self) -> bool { self.stdout.is_empty() && self.stderr.is_empty() } } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct UpstreamTrackingStatus { pub ahead: u32, pub behind: u32, } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CommitSummary { pub sha: SharedString, pub subject: SharedString, /// This is a unix timestamp pub commit_timestamp: i64, pub author_name: SharedString, pub has_parent: bool, } #[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] pub struct CommitDetails { pub sha: SharedString, pub message: SharedString, pub commit_timestamp: i64, pub author_email: SharedString, pub author_name: SharedString, } #[derive(Debug)] pub struct CommitDiff { pub files: Vec<CommitFile>, } #[derive(Debug)] pub struct CommitFile { pub path: RepoPath, pub old_text: Option<String>, pub new_text: Option<String>, } impl CommitDetails { pub fn short_sha(&self) -> SharedString { self.sha[..SHORT_SHA_LENGTH].to_string().into() } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Remote { pub name: SharedString, } pub enum ResetMode { /// Reset the branch pointer, leave index and worktree unchanged (this will make it look like things that were /// committed are now staged). Soft, /// Reset the branch pointer and index, leave worktree unchanged (this makes it look as though things that were /// committed are now unstaged). Mixed, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum FetchOptions { All, Remote(Remote), } impl FetchOptions { pub fn to_proto(&self) -> Option<String> { match self { FetchOptions::All => None, FetchOptions::Remote(remote) => Some(remote.clone().name.into()), } } pub fn from_proto(remote_name: Option<String>) -> Self { match remote_name { Some(name) => FetchOptions::Remote(Remote { name: name.into() }), None => FetchOptions::All, } } pub fn name(&self) -> SharedString { match self { Self::All => "Fetch all remotes".into(), Self::Remote(remote) => remote.name.clone(), } } } impl std::fmt::Display for FetchOptions { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FetchOptions::All => write!(f, "--all"), FetchOptions::Remote(remote) => write!(f, "{}", remote.name), } } } /// Modifies .git/info/exclude temporarily pub struct GitExcludeOverride { git_exclude_path: PathBuf, original_excludes: Option<String>, added_excludes: Option<String>, } impl GitExcludeOverride { const START_BLOCK_MARKER: &str = "\n\n# ====== Auto-added by Zed: =======\n"; const END_BLOCK_MARKER: &str = "\n# ====== End of auto-added by Zed =======\n"; pub async fn new(git_exclude_path: PathBuf) -> Result<Self> { let original_excludes = smol::fs::read_to_string(&git_exclude_path) .await .ok() .map(|content| { // Auto-generated lines are normally cleaned up in // `restore_original()` or `drop()`, but may stuck in rare cases. // Make sure to remove them. Self::remove_auto_generated_block(&content) }); Ok(GitExcludeOverride { git_exclude_path, original_excludes, added_excludes: None, }) } pub async fn add_excludes(&mut self, excludes: &str) -> Result<()> { self.added_excludes = Some(if let Some(ref already_added) = self.added_excludes { format!("{already_added}\n{excludes}") } else { excludes.to_string() }); let mut content = self.original_excludes.clone().unwrap_or_default(); content.push_str(Self::START_BLOCK_MARKER); content.push_str(self.added_excludes.as_ref().unwrap()); content.push_str(Self::END_BLOCK_MARKER); smol::fs::write(&self.git_exclude_path, content).await?; Ok(()) } pub async fn restore_original(&mut self) -> Result<()> { if let Some(ref original) = self.original_excludes { smol::fs::write(&self.git_exclude_path, original).await?; } else if self.git_exclude_path.exists() { smol::fs::remove_file(&self.git_exclude_path).await?; } self.added_excludes = None; Ok(()) } fn remove_auto_generated_block(content: &str) -> String { let start_marker = Self::START_BLOCK_MARKER; let end_marker = Self::END_BLOCK_MARKER; let mut content = content.to_string(); let start_index = content.find(start_marker); let end_index = content.rfind(end_marker); if let (Some(start), Some(end)) = (start_index, end_index) { if end > start { content.replace_range(start..end + end_marker.len(), ""); } } // Older versions of Zed didn't have end-of-block markers, // so it's impossible to determine auto-generated lines. // Conservatively remove the standard list of excludes let standard_excludes = format!( "{}{}", Self::START_BLOCK_MARKER, include_str!("./checkpoint.gitignore") ); content = content.replace(&standard_excludes, ""); content } } impl Drop for GitExcludeOverride { fn drop(&mut self) { if self.added_excludes.is_some() { let git_exclude_path = self.git_exclude_path.clone(); let original_excludes = self.original_excludes.clone(); smol::spawn(async move { if let Some(original) = original_excludes { smol::fs::write(&git_exclude_path, original).await } else { smol::fs::remove_file(&git_exclude_path).await } }) .detach(); } } } pub trait GitRepository: Send + Sync { fn reload_index(&self); /// 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. fn load_index_text(&self, 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. /// /// Also returns `None` for symlinks. fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>>; fn load_blob_content(&self, oid: Oid) -> BoxFuture<'_, Result<String>>; fn set_index_text( &self, path: RepoPath, content: Option<String>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, anyhow::Result<()>>; /// Returns the URL of the remote with the given name. fn remote_url(&self, name: &str) -> Option<String>; /// Resolve a list of refs to SHAs. fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<'_, Result<Vec<Option<String>>>>; fn head_sha(&self) -> BoxFuture<'_, Option<String>> { async move { self.revparse_batch(vec!["HEAD".into()]) .await .unwrap_or_default() .into_iter() .next() .flatten() } .boxed() } fn merge_message(&self) -> BoxFuture<'_, Option<String>>; fn status(&self, path_prefixes: &[RepoPath]) -> Task<Result<GitStatus>>; fn diff_tree(&self, request: DiffTreeType) -> BoxFuture<'_, Result<TreeDiff>>; fn stash_entries(&self) -> BoxFuture<'_, Result<GitStash>>; fn branches(&self) -> BoxFuture<'_, Result<Vec<Branch>>>; fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>>; fn create_branch(&self, name: String, base_branch: Option<String>) -> BoxFuture<'_, Result<()>>; fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>>; fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>>; fn create_worktree( &self, name: String, directory: PathBuf, from_commit: Option<String>, ) -> BoxFuture<'_, Result<()>>; fn reset( &self, commit: String, mode: ResetMode, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; fn checkout_files( &self, commit: String, paths: Vec<RepoPath>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; fn show(&self, commit: String) -> BoxFuture<'_, Result<CommitDetails>>; fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>>; fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>>; /// Returns the absolute path to the repository. For worktrees, this will be the path to the /// worktree's gitdir within the main repository (typically `.git/worktrees/<name>`). fn path(&self) -> PathBuf; fn main_repository_path(&self) -> PathBuf; /// Updates the index to match the worktree at the given paths. /// /// If any of the paths have been deleted from the worktree, they will be removed from the index if found there. fn stage_paths( &self, paths: Vec<RepoPath>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; /// Updates the index to match HEAD at the given paths. /// /// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index. fn unstage_paths( &self, paths: Vec<RepoPath>, env: Arc<HashMap<String, String>>, ) -> BoxFuture<'_, Result<()>>; fn commit( &self, message: SharedString, name_and_email: Option<(SharedString, SharedString)>, options: CommitOptions,