6072 lines · 199.3 KB · Rust
RawDownload
use crate::commit::{CommitDiffObject, CommitDiffObjectKind, parse_git_diff_raw};use crate::stash::GitStash;use crate::status::{DiffTreeType, GitStatus, TreeDiff};use crate::{Oid, RunHook, SHORT_SHA_LENGTH};use anyhow::{Context as _, Result, anyhow, bail};use async_channel::Sender;use collections::HashMap;use futures::channel::oneshot;use futures::future::BoxFuture;use futures::io::BufWriter;use futures::{AsyncWriteExt, FutureExt as _, select_biased};use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Task};use parking_lot::Mutex;use rope::Rope;use schemars::JsonSchema;use serde::Deserialize;use smallvec::SmallVec;use smol::io::{AsyncBufReadExt, AsyncReadExt, BufReader};use text::LineEnding; use std::borrow::Cow;use std::collections::HashSet;use std::ffi::{OsStr, OsString};use std::sync::atomic::AtomicBool; use std::process::{ExitStatus, Output};use std::str::FromStr;use std::time::SystemTime;use std::{    cmp::Ordering,    path::{Path, PathBuf},    sync::Arc,};use sum_tree::MapSeekTarget;use thiserror::Error;use util::command::{Stdio, new_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"; /// Format string used in graph log to get initial data for the git graph/// %H - Full commit hash/// %P - Parent hashes/// %D - Ref names/// %x00 - Null byte separator, used to split up commit datastatic GRAPH_COMMIT_FORMAT: &str = "--format=%H%x00%P%x00%D"; /// Used to get commits that match with a search/// %H - Full commit hashstatic SEARCH_COMMIT_FORMAT: &str = "--format=%H"; /// Number of commits to load per chunk for the git graph.pub const GRAPH_CHUNK_SIZE: usize = 1000; /// Default value for the `git.worktree_directory` setting.pub const DEFAULT_WORKTREE_DIRECTORY: &str = "../worktrees"; /// Given the git common directory (from `commondir()`), derive the original/// repository's working directory.////// For a standard checkout, `common_dir` is `<work_dir>/.git`, so the parent/// is the working directory. For a git worktree, `common_dir` is the **main**/// repo's `.git` directory, so the parent is the original repo's working directory.////// Returns `None` if `common_dir` doesn't end with `.git` (e.g. bare repos),/// because there is no working-tree root to resolve to in that case.pub fn original_repo_path_from_common_dir(common_dir: &Path) -> Option<PathBuf> {    if common_dir.file_name() == Some(OsStr::new(".git")) {        common_dir.parent().map(|p| p.to_path_buf())    } else {        None    }} fn linked_worktree_git_dir(worktree_path: &Path) -> Result<PathBuf> {    let dot_git_path = worktree_path.join(".git");    let git_file = std::fs::read_to_string(&dot_git_path)        .with_context(|| format!("failed to read {}", dot_git_path.display()))?;    let git_dir = git_file        .strip_prefix("gitdir:")        .context("worktree .git file missing gitdir pointer")?        .trim();    Ok(worktree_path.join(git_dir))} fn normalize_git_metadata_path(path: PathBuf) -> Result<PathBuf> {    paths::normalize_lexically(&path)        .map_err(|_| anyhow!("git metadata path escapes its filesystem root: {path:?}"))} /// Commit data needed for the git graph visualization.#[derive(Debug, Clone)]pub struct CommitData {    pub sha: Oid,    /// Most commits have a single parent, so we use a SmallVec to avoid allocations.    pub parents: SmallVec<[Oid; 1]>,    pub author_name: SharedString,    pub author_email: SharedString,    pub commit_timestamp: i64,    pub subject: SharedString,    pub message: SharedString,} #[derive(Debug)]pub struct InitialGraphCommitData {    pub sha: Oid,    pub parents: SmallVec<[Oid; 1]>,    pub ref_names: Vec<SharedString>,} impl InitialGraphCommitData {    pub fn tag_names(&self) -> Vec<&str> {        self.ref_names            .iter()            .filter_map(|ref_name| {                let tag_name = ref_name.strip_prefix("tag: ")?;                 if tag_name.is_empty() {                    return None;                }                 Some(tag_name)            })            .collect()    }} struct CommitDataRequest {    sha: Oid,    response_tx: oneshot::Sender<Result<CommitData>>,} pub struct CommitDataReader {    request_tx: async_channel::Sender<CommitDataRequest>,    _task: Task<()>,} impl CommitDataReader {    pub async fn read(&self, sha: Oid) -> Result<CommitData> {        let (response_tx, response_rx) = oneshot::channel();        self.request_tx            .send(CommitDataRequest { sha, response_tx })            .await            .map_err(|_| anyhow!("commit data reader task closed"))?;        response_rx            .await            .map_err(|_| anyhow!("commit data reader task dropped response"))?    }     #[cfg(any(test, feature = "test-support"))]    pub fn for_test(        executor: BackgroundExecutor,        resolve: impl 'static + Send + Sync + Fn(Oid) -> Result<CommitData>,    ) -> Self {        let (request_tx, request_rx) = smol::channel::bounded::<CommitDataRequest>(64);        let resolve = Arc::new(resolve);        let delay_executor = executor.clone();        let task = executor.spawn(async move {            while let Ok(CommitDataRequest { sha, response_tx }) = request_rx.recv().await {                delay_executor.simulate_random_delay().await;                response_tx.send(resolve(sha)).ok();            }        });         Self {            request_tx,            _task: task,        }    }} fn parse_cat_file_commit(sha: Oid, content: &str) -> Option<CommitData> {    let mut parents = SmallVec::new();    let mut author_name = SharedString::default();    let mut author_email = SharedString::default();    let mut commit_timestamp = 0i64;    let mut in_headers = true;    let mut subject = None;    let mut message_lines = Vec::new();     for line in content.lines() {        if in_headers {            if line.is_empty() {                in_headers = false;                continue;            }             if let Some(parent_sha) = line.strip_prefix("parent ") {                if let Ok(oid) = Oid::from_str(parent_sha.trim()) {                    parents.push(oid);                }            } else if let Some(author_line) = line.strip_prefix("author ") {                if let Some((name_email, _timestamp_tz)) = author_line.rsplit_once(' ') {                    if let Some((name_email, timestamp_str)) = name_email.rsplit_once(' ') {                        if let Ok(ts) = timestamp_str.parse::<i64>() {                            commit_timestamp = ts;                        }                        if let Some((name, email)) = name_email.rsplit_once(" <") {                            author_name = SharedString::from(name.to_string());                            author_email =                                SharedString::from(email.trim_end_matches('>').to_string());                        }                    }                }            }        } else {            if subject.is_none() {                subject = Some(SharedString::from(line.to_string()));            }            message_lines.push(line);        }    }     Some(CommitData {        sha,        parents,        author_name,        author_email,        commit_timestamp,        subject: subject.unwrap_or_default(),        message: SharedString::from(message_lines.join("\n")),    })} #[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>,} #[derive(Clone, Debug, Default, PartialEq, Eq)]pub struct BranchesScanResult {    pub branches: Vec<Branch>,    pub error: Option<SharedString>,} impl From<Vec<Branch>> for BranchesScanResult {    fn from(branches: Vec<Branch>) -> Self {        Self {            branches,            error: None,        }    }} 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 remote_name(&self) -> Option<&str> {        self.ref_name            .strip_prefix("refs/remotes/")            .and_then(|stripped| stripped.split("/").next())    }     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: Option<SharedString>,    // todo(git_worktree) This type should be a Oid    pub sha: SharedString,    pub is_main: bool,    pub is_bare: bool,} /// Describes how a new worktree should choose or create its checked-out HEAD.#[derive(Clone, Debug, Hash, PartialEq, Eq)]pub enum CreateWorktreeTarget {    /// Check out an existing local branch in the new worktree.    ExistingBranch {        /// The existing local branch to check out.        branch_name: String,    },    /// Create a new local branch for the new worktree.    NewBranch {        /// The new local branch to create and check out.        branch_name: String,        /// The commit or ref to create the branch from. Uses `HEAD` when `None`.        base_sha: Option<String>,    },    /// Check out a commit or ref in detached HEAD state.    Detached {        /// The commit or ref to check out. Uses `HEAD` when `None`.        base_sha: Option<String>,    },} impl CreateWorktreeTarget {    pub fn branch_name(&self) -> Option<&str> {        match self {            Self::ExistingBranch { branch_name } | Self::NewBranch { branch_name, .. } => {                Some(branch_name)            }            Self::Detached { .. } => None,        }    }} impl Worktree {    /// Returns the branch name if the worktree is attached to a branch.    pub fn branch_name(&self) -> Option<&str> {        self.ref_name.as_ref().map(|ref_name| {            ref_name                .strip_prefix("refs/heads/")                .or_else(|| ref_name.strip_prefix("refs/remotes/"))                .unwrap_or(ref_name)        })    }     /// Returns a display name for the worktree, suitable for use in the UI.    ///    /// If the worktree is attached to a branch, returns the branch name.    /// Otherwise, returns the short SHA of the worktree's HEAD commit.    pub fn display_name(&self) -> &str {        self.branch_name()            .unwrap_or(&self.sha[..self.sha.len().min(SHORT_SHA_LENGTH)])    }     pub fn directory_name(&self, main_worktree_path: Option<&Path>) -> String {        if self.is_main {            return "main worktree".to_string();        }         let dir_name = self            .path            .file_name()            .and_then(|name| name.to_str())            .unwrap_or(self.display_name());         if let Some(main_path) = main_worktree_path {            let main_dir = main_path.file_name().and_then(|n| n.to_str());            if main_dir == Some(dir_name) {                if let Some(parent_name) = self                    .path                    .parent()                    .and_then(|p| p.file_name())                    .and_then(|n| n.to_str())                {                    return parent_name.to_string();                }            }        }         dir_name.to_string()    }} pub fn parse_worktrees_from_str<T: AsRef<str>>(    raw_worktrees: T,    main_worktree_path: Option<&Path>,) -> Vec<Worktree> {    let mut worktrees = Vec::new();    let normalized = raw_worktrees.as_ref().replace("\r\n", "\n");    let entries = normalized.split("\n\n");    for entry in entries {        let mut path = None;        let mut sha = None;        let mut ref_name = None;         let mut is_bare = false;         for line in entry.lines() {            let line = line.trim();            if line.is_empty() {                continue;            }            if let Some(rest) = line.strip_prefix("worktree ") {                path = Some(rest.to_string());            } else if let Some(rest) = line.strip_prefix("HEAD ") {                sha = Some(rest.to_string());            } else if let Some(rest) = line.strip_prefix("branch ") {                ref_name = Some(rest.to_string());            } else if line == "bare" {                is_bare = true;            }            // Ignore other lines: detached, locked, prunable, etc.        }         if let (Some(path), Some(sha)) = (path, sha) {            let path = PathBuf::from(path);            let is_main =                main_worktree_path.is_some_and(|main_worktree_path| path == main_worktree_path);            worktrees.push(Worktree {                path,                ref_name: ref_name.map(Into::into),                sha: sha.into(),                is_main,                is_bare,            });        }    }     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/")    }     pub fn branch_name(&self) -> Option<&str> {        self.ref_name            .strip_prefix("refs/remotes/")            .and_then(|stripped| stripped.split_once('/').map(|(_, name)| name))    }} #[derive(Clone, Copy, Default)]pub struct CommitOptions {    pub amend: bool,    pub signoff: bool,    pub allow_empty: bool,} #[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()    }}

Lines 1–500 of 6072.