Move git related things into specialized git crate

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Julia 2022-09-28 11:43:33 -04:00
parent bf3b3da6ed
commit d5fd531743
17 changed files with 151 additions and 100 deletions

22
Cargo.lock generated
View file

@ -1697,6 +1697,7 @@ dependencies = [
"env_logger",
"futures",
"fuzzy",
"git",
"gpui",
"indoc",
"itertools",
@ -2224,6 +2225,23 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "git"
version = "0.1.0"
dependencies = [
"anyhow",
"clock",
"git2",
"lazy_static",
"log",
"parking_lot 0.11.2",
"smol",
"sum_tree",
"text",
"unindent",
"util",
]
[[package]]
name = "git2"
version = "0.15.0"
@ -2853,7 +2871,7 @@ dependencies = [
"env_logger",
"futures",
"fuzzy",
"git2",
"git",
"gpui",
"lazy_static",
"log",
@ -3996,7 +4014,7 @@ dependencies = [
"fsevent",
"futures",
"fuzzy",
"git2",
"git",
"gpui",
"ignore",
"language",

View file

@ -25,6 +25,7 @@ clock = { path = "../clock" }
collections = { path = "../collections" }
context_menu = { path = "../context_menu" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
gpui = { path = "../gpui" }
language = { path = "../language" }
lsp = { path = "../lsp" }

View file

@ -16,6 +16,7 @@ use crate::{
};
use clock::ReplicaId;
use collections::{BTreeMap, HashMap};
use git::diff::{DiffHunk, DiffHunkStatus};
use gpui::{
color::Color,
elements::*,
@ -34,7 +35,6 @@ use gpui::{
WeakViewHandle,
};
use json::json;
use language::git::{DiffHunk, DiffHunkStatus};
use language::{Bias, DiagnosticSeverity, OffsetUtf16, Selection};
use project::ProjectPath;
use settings::Settings;

View file

@ -4,13 +4,13 @@ pub use anchor::{Anchor, AnchorRangeExt};
use anyhow::Result;
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet};
use git::diff::DiffHunk;
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
pub use language::Completion;
use language::{
char_kind, git::DiffHunk, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind,
Chunk, DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, Outline,
OutlineItem, Selection, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _,
TransactionId,
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk,
DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, Outline, OutlineItem,
Selection, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
};
use smallvec::SmallVec;
use std::{

22
crates/git/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "git"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/git.rs"
[dependencies]
anyhow = "1.0.38"
clock = { path = "../clock" }
git2 = { version = "0.15", default-features = false }
lazy_static = "1.4.0"
sum_tree = { path = "../sum_tree" }
text = { path = "../text" }
util = { path = "../util" }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
smol = "1.2"
parking_lot = "0.11.1"
[dev-dependencies]
unindent = "0.1.7"

View file

@ -259,7 +259,7 @@ mod tests {
use text::Buffer;
use unindent::Unindent as _;
#[gpui::test]
#[test]
fn test_buffer_diff_simple() {
let head_text = "
one
@ -308,8 +308,4 @@ mod tests {
);
}
}
// use rand::rngs::StdRng;
// #[gpui::test(iterations = 100)]
// fn test_buffer_diff_random(mut rng: StdRng) {}
}

12
crates/git/src/git.rs Normal file
View file

@ -0,0 +1,12 @@
use std::ffi::OsStr;
pub use git2 as libgit;
pub use lazy_static::lazy_static;
pub mod diff;
pub mod repository;
lazy_static! {
pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git");
pub static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
}

View file

@ -1,7 +1,7 @@
use anyhow::Result;
use git2::{Repository as LibGitRepository, RepositoryOpenFlags as LibGitRepositoryOpenFlags};
use git2::Repository as LibGitRepository;
use parking_lot::Mutex;
use std::{ffi::OsStr, path::Path, sync::Arc};
use std::{path::Path, sync::Arc};
use util::ResultExt;
#[derive(Clone)]
@ -53,21 +53,17 @@ impl GitRepository {
self.scan_id
}
pub(super) fn set_scan_id(&mut self, scan_id: usize) {
pub fn set_scan_id(&mut self, scan_id: usize) {
println!("setting scan id");
self.scan_id = scan_id;
}
pub fn with_repo<F: FnOnce(&mut git2::Repository)>(&mut self, f: F) {
let mut git2 = self.libgit_repository.lock();
f(&mut git2)
}
pub async fn load_head_text(&self, file_path: &Path) -> Option<String> {
fn logic(repo: &LibGitRepository, file_path: &Path) -> Result<Option<String>> {
pub async fn load_head_text(&self, relative_file_path: &Path) -> Option<String> {
fn logic(repo: &LibGitRepository, relative_file_path: &Path) -> Result<Option<String>> {
let object = repo
.head()?
.peel_to_tree()?
.get_path(file_path)?
.get_path(relative_file_path)?
.to_object(&repo)?;
let content = match object.as_blob() {
@ -79,7 +75,7 @@ impl GitRepository {
Ok(Some(head_text))
}
match logic(&self.libgit_repository.lock(), file_path) {
match logic(&self.libgit_repository.as_ref().lock(), relative_file_path) {
Ok(value) => return value,
Err(err) => log::error!("Error loading head text: {:?}", err),
}

View file

@ -25,6 +25,7 @@ client = { path = "../client" }
clock = { path = "../clock" }
collections = { path = "../collections" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
gpui = { path = "../gpui" }
lsp = { path = "../lsp" }
rpc = { path = "../rpc" }
@ -51,7 +52,6 @@ smol = "1.2"
tree-sitter = "0.20"
tree-sitter-rust = { version = "*", optional = true }
tree-sitter-typescript = { version = "*", optional = true }
git2 = { version = "0.15", default-features = false }
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }

View file

@ -1,4 +1,3 @@
use crate::git;
pub use crate::{
diagnostic_set::DiagnosticSet,
highlight_map::{HighlightId, HighlightMap},
@ -47,14 +46,14 @@ pub use {tree_sitter_rust, tree_sitter_typescript};
pub use lsp::DiagnosticSeverity;
struct GitDiffStatus {
diff: git::BufferDiff,
diff: git::diff::BufferDiff,
update_in_progress: bool,
update_requested: bool,
}
pub struct Buffer {
text: TextBuffer,
head_text: Option<Arc<String>>,
head_text: Option<String>,
git_diff_status: GitDiffStatus,
file: Option<Arc<dyn File>>,
saved_version: clock::Global,
@ -83,7 +82,7 @@ pub struct Buffer {
pub struct BufferSnapshot {
text: text::BufferSnapshot,
pub git_diff: git::BufferDiff,
pub git_diff: git::diff::BufferDiff,
pub(crate) syntax: SyntaxSnapshot,
file: Option<Arc<dyn File>>,
diagnostics: DiagnosticSet,
@ -353,7 +352,7 @@ impl Buffer {
) -> Self {
Self::build(
TextBuffer::new(replica_id, cx.model_id() as u64, base_text.into()),
head_text.map(|h| Arc::new(h.into())),
head_text.map(|h| h.into().into_boxed_str().into()),
Some(file),
)
}
@ -364,7 +363,11 @@ impl Buffer {
file: Option<Arc<dyn File>>,
) -> Result<Self> {
let buffer = TextBuffer::new(replica_id, message.id, message.base_text);
let mut this = Self::build(buffer, message.head_text.map(|text| Arc::new(text)), file);
let mut this = Self::build(
buffer,
message.head_text.map(|text| text.into_boxed_str().into()),
file,
);
this.text.set_line_ending(proto::deserialize_line_ending(
proto::LineEnding::from_i32(message.line_ending)
.ok_or_else(|| anyhow!("missing line_ending"))?,
@ -420,11 +423,7 @@ impl Buffer {
self
}
fn build(
buffer: TextBuffer,
head_text: Option<Arc<String>>,
file: Option<Arc<dyn File>>,
) -> Self {
fn build(buffer: TextBuffer, head_text: Option<String>, file: Option<Arc<dyn File>>) -> Self {
let saved_mtime = if let Some(file) = file.as_ref() {
file.mtime()
} else {
@ -440,7 +439,7 @@ impl Buffer {
text: buffer,
head_text,
git_diff_status: GitDiffStatus {
diff: git::BufferDiff::new(),
diff: git::diff::BufferDiff::new(),
update_in_progress: false,
update_requested: false,
},
@ -613,7 +612,7 @@ impl Buffer {
cx,
);
}
self.update_git(cx);
self.git_diff_recalc(cx);
cx.emit(Event::Reloaded);
cx.notify();
}
@ -663,9 +662,8 @@ impl Buffer {
task
}
pub fn update_git(&mut self, cx: &mut ModelContext<Self>) {
//Grab head text
pub fn update_head_text(&mut self, head_text: Option<String>, cx: &mut ModelContext<Self>) {
self.head_text = head_text;
self.git_diff_recalc(cx);
}
@ -674,6 +672,7 @@ impl Buffer {
}
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
println!("recalc");
if self.git_diff_status.update_in_progress {
self.git_diff_status.update_requested = true;
return;
@ -2221,7 +2220,7 @@ impl BufferSnapshot {
pub fn git_diff_hunks_in_range<'a>(
&'a self,
query_row_range: Range<u32>,
) -> impl 'a + Iterator<Item = git::DiffHunk<u32>> {
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
self.git_diff.hunks_in_range(query_row_range, self)
}

View file

@ -1,6 +1,5 @@
mod buffer;
mod diagnostic_set;
pub mod git;
mod highlight_map;
mod outline;
pub mod proto;

View file

@ -24,6 +24,7 @@ collections = { path = "../collections" }
db = { path = "../db" }
fsevent = { path = "../fsevent" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
gpui = { path = "../gpui" }
language = { path = "../language" }
lsp = { path = "../lsp" }
@ -52,8 +53,6 @@ smol = "1.2.5"
thiserror = "1.0.29"
toml = "0.5"
rocksdb = "0.18"
git2 = { version = "0.15", default-features = false }
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }

View file

@ -1,11 +1,9 @@
use anyhow::{anyhow, Result};
use fsevent::EventStream;
use futures::{future::BoxFuture, Stream, StreamExt};
use language::git::libgit::{Repository, RepositoryOpenFlags};
use language::LineEnding;
use smol::io::{AsyncReadExt, AsyncWriteExt};
use std::{
ffi::OsStr,
io,
os::unix::fs::MetadataExt,
path::{Component, Path, PathBuf},
@ -22,8 +20,6 @@ use futures::lock::Mutex;
#[cfg(any(test, feature = "test-support"))]
use std::sync::{Arc, Weak};
use crate::git_repository::GitRepository;
#[async_trait::async_trait]
pub trait Fs: Send + Sync {
async fn create_dir(&self, path: &Path) -> Result<()>;

View file

@ -1,5 +1,4 @@
pub mod fs;
mod git_repository;
mod ignore;
mod lsp_command;
pub mod search;
@ -13,7 +12,7 @@ use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
use clock::ReplicaId;
use collections::{hash_map, BTreeMap, HashMap, HashSet};
use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt};
use git_repository::GitRepository;
use git::repository::GitRepository;
use gpui::{
AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle,
@ -4538,6 +4537,7 @@ impl Project {
cx.subscribe(worktree, |this, worktree, event, cx| match event {
worktree::Event::UpdatedEntries => this.update_local_worktree_buffers(worktree, cx),
worktree::Event::UpdatedGitRepositories(updated_repos) => {
println!("{updated_repos:#?}");
this.update_local_worktree_buffers_git_repos(updated_repos, cx)
}
})
@ -4649,24 +4649,35 @@ impl Project {
fn update_local_worktree_buffers_git_repos(
&mut self,
updated_repos: &[GitRepository],
repos: &[GitRepository],
cx: &mut ModelContext<Self>,
) {
for (buffer_id, buffer) in &self.opened_buffers {
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, |buffer, cx| {
let updated = updated_repos.iter().any(|repo| {
buffer
.file()
.and_then(|file| file.as_local())
.map(|file| repo.manages(&file.abs_path(cx)))
.unwrap_or(false)
});
//TODO: Produce protos
if updated {
buffer.update_git(cx);
}
});
for (_, buffer) in &self.opened_buffers {
if let Some(buffer) = buffer.upgrade(cx) {
let file = match buffer.read(cx).file().and_then(|file| file.as_local()) {
Some(file) => file,
None => return,
};
let path = file.path().clone();
let abs_path = file.abs_path(cx);
println!("got file");
let repo = match repos.iter().find(|repo| repo.manages(&abs_path)) {
Some(repo) => repo.clone(),
None => return,
};
println!("got repo");
cx.spawn(|_, mut cx| async move {
let head_text = repo.load_head_text(&path).await;
buffer.update(&mut cx, |buffer, cx| {
println!("got calling update");
buffer.update_head_text(head_text, cx);
});
})
.detach();
}
}
}

View file

@ -1,10 +1,9 @@
use crate::{copy_recursive, git_repository::GitRepository, ProjectEntryId, RemoveOptions};
use super::{
fs::{self, Fs},
ignore::IgnoreStack,
DiagnosticSummary,
};
use crate::{copy_recursive, ProjectEntryId, RemoveOptions};
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
use anyhow::{anyhow, Context, Result};
use client::{proto, Client};
@ -18,6 +17,8 @@ use futures::{
Stream, StreamExt,
};
use fuzzy::CharBag;
use git::repository::GitRepository;
use git::{DOT_GIT, GITIGNORE};
use gpui::{
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
Task,
@ -48,7 +49,7 @@ use std::{
time::{Duration, SystemTime},
};
use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
use util::{ResultExt, TryFutureExt, DOT_GIT, GITIGNORE};
use util::{ResultExt, TryFutureExt};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub struct WorktreeId(usize);
@ -523,7 +524,10 @@ impl LocalWorktree {
match self.scan_state() {
ScanState::Idle => {
let new_snapshot = self.background_snapshot.lock().clone();
let updated_repos = self.list_updated_repos(&new_snapshot);
let updated_repos = Self::list_updated_repos(
&self.snapshot.git_repositories,
&new_snapshot.git_repositories,
);
self.snapshot = new_snapshot;
if let Some(share) = self.share.as_mut() {
@ -541,7 +545,10 @@ impl LocalWorktree {
let is_fake_fs = self.fs.is_fake();
let new_snapshot = self.background_snapshot.lock().clone();
let updated_repos = self.list_updated_repos(&new_snapshot);
let updated_repos = Self::list_updated_repos(
&self.snapshot.git_repositories,
&new_snapshot.git_repositories,
);
self.snapshot = new_snapshot;
self.poll_task = Some(cx.spawn_weak(|this, mut cx| async move {
@ -573,16 +580,20 @@ impl LocalWorktree {
cx.notify();
}
fn list_updated_repos(&self, new_snapshot: &LocalSnapshot) -> Vec<GitRepository> {
let old_snapshot = &self.snapshot;
fn list_updated_repos(
old_repos: &[GitRepository],
new_repos: &[GitRepository],
) -> Vec<GitRepository> {
println!("old repos: {:#?}", old_repos);
println!("new repos: {:#?}", new_repos);
fn diff<'a>(
a: &'a LocalSnapshot,
b: &'a LocalSnapshot,
a: &'a [GitRepository],
b: &'a [GitRepository],
updated: &mut HashMap<&'a Path, GitRepository>,
) {
for a_repo in &a.git_repositories {
let matched = b.git_repositories.iter().find(|b_repo| {
for a_repo in a {
let matched = b.iter().find(|b_repo| {
a_repo.git_dir_path() == b_repo.git_dir_path()
&& a_repo.scan_id() == b_repo.scan_id()
});
@ -595,10 +606,10 @@ impl LocalWorktree {
let mut updated = HashMap::<&Path, GitRepository>::default();
diff(old_snapshot, new_snapshot, &mut updated);
diff(new_snapshot, old_snapshot, &mut updated);
diff(old_repos, new_repos, &mut updated);
diff(new_repos, old_repos, &mut updated);
updated.into_values().collect()
dbg!(updated.into_values().collect())
}
pub fn scan_complete(&self) -> impl Future<Output = ()> {
@ -653,7 +664,7 @@ impl LocalWorktree {
settings::GitFilesIncluded::All | settings::GitFilesIncluded::OnlyTracked
) {
let results = if let Some(repo) = snapshot.repo_for(&abs_path) {
repo.load_head_text(&abs_path).await
repo.load_head_text(&path).await
} else {
None
};
@ -1362,6 +1373,7 @@ impl LocalSnapshot {
}
pub(crate) fn in_dot_git(&mut self, path: &Path) -> Option<&mut GitRepository> {
println!("chechking {path:?}");
self.git_repositories
.iter_mut()
.rev() //git_repository is ordered lexicographically
@ -1510,7 +1522,6 @@ impl LocalSnapshot {
parent_path: Arc<Path>,
entries: impl IntoIterator<Item = Entry>,
ignore: Option<Arc<Gitignore>>,
fs: &dyn Fs,
) {
let mut parent_entry = if let Some(parent_entry) =
self.entries_by_path.get(&PathKey(parent_path.clone()), &())
@ -2391,12 +2402,9 @@ impl BackgroundScanner {
new_entries.push(child_entry);
}
self.snapshot.lock().populate_dir(
job.path.clone(),
new_entries,
new_ignore,
self.fs.as_ref(),
);
self.snapshot
.lock()
.populate_dir(job.path.clone(), new_entries, new_ignore);
for new_job in new_jobs {
job.scan_queue.send(new_job).await.unwrap();
}
@ -2595,7 +2603,7 @@ impl BackgroundScanner {
.git_repositories
.iter()
.cloned()
.filter(|repo| git2::Repository::open(repo.git_dir_path()).is_ok())
.filter(|repo| git::libgit::Repository::open(repo.git_dir_path()).is_ok())
.collect();
snapshot.git_repositories = new_repos;

View file

@ -2,20 +2,13 @@
pub mod test;
use futures::Future;
use lazy_static::lazy_static;
use std::{
cmp::Ordering,
ffi::OsStr,
ops::AddAssign,
pin::Pin,
task::{Context, Poll},
};
lazy_static! {
pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git");
pub static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
}
pub fn truncate(s: &str, max_chars: usize) -> &str {
match s.char_indices().nth(max_chars) {
None => s,

View file

@ -2,14 +2,15 @@ mod assertions;
mod marked_text;
use git2;
use std::path::{Path, PathBuf};
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use tempdir::TempDir;
pub use assertions::*;
pub use marked_text::*;
use crate::DOT_GIT;
pub fn temp_tree(tree: serde_json::Value) -> TempDir {
let dir = TempDir::new("").unwrap();
write_tree(dir.path(), tree);
@ -28,7 +29,7 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
Value::Object(_) => {
fs::create_dir(&path).unwrap();
if path.file_name() == Some(&DOT_GIT) {
if path.file_name() == Some(&OsStr::new(".git")) {
git2::Repository::init(&path.parent().unwrap()).unwrap();
}