Closes https://github.com/zed-industries/zed/issues/38690 Closes #37353 ### Background On Windows, paths are normally separated by `\`, unlike mac and linux where they are separated by `/`. When editing code in a project that uses a different path style than your local system (e.g. remoting from Windows to Linux, using WSL, and collaboration between windows and unix users), the correct separator for a path may differ from the "native" separator. Previously, to work around this, Zed converted paths' separators in numerous places. This was applied to both absolute and relative paths, leading to incorrect conversions in some cases. ### Solution Many code paths in Zed use paths that are *relative* to either a worktree root or a git repository. This PR introduces a dedicated type for these paths called `RelPath`, which stores the path in the same way regardless of host platform, and offers `Path`-like manipulation APIs. RelPath supports *displaying* the path using either separator, so that we can display paths in a style that is determined at runtime based on the current project. The representation of absolute paths is left untouched, for now. Absolute paths are different from relative paths because (except in contexts where we know that the path refers to the local filesystem) they should generally be treated as opaque strings. Currently we use a mix of types for these paths (std::path::Path, String, SanitizedPath). Release Notes: - N/A --------- Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Peter Tripp <petertripp@gmail.com> Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com> Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
224 lines
6.2 KiB
Rust
224 lines
6.2 KiB
Rust
pub mod blame;
|
|
pub mod commit;
|
|
mod hosting_provider;
|
|
mod remote;
|
|
pub mod repository;
|
|
pub mod stash;
|
|
pub mod status;
|
|
|
|
pub use crate::hosting_provider::*;
|
|
pub use crate::remote::*;
|
|
use anyhow::{Context as _, Result};
|
|
pub use git2 as libgit;
|
|
use gpui::{Action, actions};
|
|
pub use repository::RemoteCommandOutput;
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fmt;
|
|
use std::str::FromStr;
|
|
|
|
pub const DOT_GIT: &str = ".git";
|
|
pub const GITIGNORE: &str = ".gitignore";
|
|
pub const FSMONITOR_DAEMON: &str = "fsmonitor--daemon";
|
|
pub const LFS_DIR: &str = "lfs";
|
|
pub const COMMIT_MESSAGE: &str = "COMMIT_EDITMSG";
|
|
pub const INDEX_LOCK: &str = "index.lock";
|
|
|
|
actions!(
|
|
git,
|
|
[
|
|
// per-hunk
|
|
/// Toggles the staged state of the hunk or status entry at cursor.
|
|
ToggleStaged,
|
|
/// Stage status entries between an anchor entry and the cursor.
|
|
StageRange,
|
|
/// Stages the current hunk and moves to the next one.
|
|
StageAndNext,
|
|
/// Unstages the current hunk and moves to the next one.
|
|
UnstageAndNext,
|
|
/// Restores the selected hunks to their original state.
|
|
#[action(deprecated_aliases = ["editor::RevertSelectedHunks"])]
|
|
Restore,
|
|
// per-file
|
|
/// Shows git blame information for the current file.
|
|
#[action(deprecated_aliases = ["editor::ToggleGitBlame"])]
|
|
Blame,
|
|
/// Stages the current file.
|
|
StageFile,
|
|
/// Unstages the current file.
|
|
UnstageFile,
|
|
// repo-wide
|
|
/// Stages all changes in the repository.
|
|
StageAll,
|
|
/// Unstages all changes in the repository.
|
|
UnstageAll,
|
|
/// Stashes all changes in the repository, including untracked files.
|
|
StashAll,
|
|
/// Pops the most recent stash.
|
|
StashPop,
|
|
/// Apply the most recent stash.
|
|
StashApply,
|
|
/// Restores all tracked files to their last committed state.
|
|
RestoreTrackedFiles,
|
|
/// Moves all untracked files to trash.
|
|
TrashUntrackedFiles,
|
|
/// Undoes the last commit, keeping changes in the working directory.
|
|
Uncommit,
|
|
/// Pushes commits to the remote repository.
|
|
Push,
|
|
/// Pushes commits to a specific remote branch.
|
|
PushTo,
|
|
/// Force pushes commits to the remote repository.
|
|
ForcePush,
|
|
/// Pulls changes from the remote repository.
|
|
Pull,
|
|
/// Fetches changes from the remote repository.
|
|
Fetch,
|
|
/// Fetches changes from a specific remote.
|
|
FetchFrom,
|
|
/// Creates a new commit with staged changes.
|
|
Commit,
|
|
/// Amends the last commit with staged changes.
|
|
Amend,
|
|
/// Enable the --signoff option.
|
|
Signoff,
|
|
/// Cancels the current git operation.
|
|
Cancel,
|
|
/// Expands the commit message editor.
|
|
ExpandCommitEditor,
|
|
/// Generates a commit message using AI.
|
|
GenerateCommitMessage,
|
|
/// Initializes a new git repository.
|
|
Init,
|
|
/// Opens all modified files in the editor.
|
|
OpenModifiedFiles,
|
|
/// Clones a repository.
|
|
Clone,
|
|
]
|
|
);
|
|
|
|
/// Renames a git branch.
|
|
#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
|
|
#[action(namespace = git)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RenameBranch {
|
|
/// The branch to rename.
|
|
///
|
|
/// Default: the current branch.
|
|
#[serde(default)]
|
|
pub branch: Option<String>,
|
|
}
|
|
|
|
/// Restores a file to its last committed state, discarding local changes.
|
|
#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
|
|
#[action(namespace = git, deprecated_aliases = ["editor::RevertFile"])]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RestoreFile {
|
|
#[serde(default)]
|
|
pub skip_prompt: bool,
|
|
}
|
|
|
|
/// The length of a Git short SHA.
|
|
pub const SHORT_SHA_LENGTH: usize = 7;
|
|
|
|
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
|
pub struct Oid(libgit::Oid);
|
|
|
|
impl Oid {
|
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
|
let oid = libgit::Oid::from_bytes(bytes).context("failed to parse bytes into git oid")?;
|
|
Ok(Self(oid))
|
|
}
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
pub fn random(rng: &mut impl rand::Rng) -> Self {
|
|
let mut bytes = [0; 20];
|
|
rng.fill(&mut bytes);
|
|
Self::from_bytes(&bytes).unwrap()
|
|
}
|
|
|
|
pub fn as_bytes(&self) -> &[u8] {
|
|
self.0.as_bytes()
|
|
}
|
|
|
|
pub(crate) fn is_zero(&self) -> bool {
|
|
self.0.is_zero()
|
|
}
|
|
|
|
/// Returns this [`Oid`] as a short SHA.
|
|
pub fn display_short(&self) -> String {
|
|
self.to_string().chars().take(SHORT_SHA_LENGTH).collect()
|
|
}
|
|
}
|
|
|
|
impl FromStr for Oid {
|
|
type Err = anyhow::Error;
|
|
|
|
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
|
|
libgit::Oid::from_str(s)
|
|
.context("parsing git oid")
|
|
.map(Self)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Oid {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Oid {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl Serialize for Oid {
|
|
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&self.0.to_string())
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Oid {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let s = String::deserialize(deserializer)?;
|
|
s.parse::<Oid>().map_err(serde::de::Error::custom)
|
|
}
|
|
}
|
|
|
|
impl Default for Oid {
|
|
fn default() -> Self {
|
|
Self(libgit::Oid::zero())
|
|
}
|
|
}
|
|
|
|
impl From<Oid> for u32 {
|
|
fn from(oid: Oid) -> Self {
|
|
let bytes = oid.0.as_bytes();
|
|
debug_assert!(bytes.len() > 4);
|
|
|
|
let mut u32_bytes: [u8; 4] = [0; 4];
|
|
u32_bytes.copy_from_slice(&bytes[..4]);
|
|
|
|
u32::from_ne_bytes(u32_bytes)
|
|
}
|
|
}
|
|
|
|
impl From<Oid> for usize {
|
|
fn from(oid: Oid) -> Self {
|
|
let bytes = oid.0.as_bytes();
|
|
debug_assert!(bytes.len() > 8);
|
|
|
|
let mut u64_bytes: [u8; 8] = [0; 8];
|
|
u64_bytes.copy_from_slice(&bytes[..8]);
|
|
|
|
u64::from_ne_bytes(u64_bytes) as usize
|
|
}
|
|
}
|