Represent relative paths using a dedicated, separator-agnostic type (#38744)
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>
This commit is contained in:
co-authored by
Cole Miller
Piotr Osiewicz
Peter Tripp
Smit Barmase
Lukas Wirth
parent
3c626f3758
commit
03f9cf4414
@@ -1,4 +1,3 @@
|
||||
use ::proto::{FromProto, ToProto};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use lsp::LanguageServerId;
|
||||
|
||||
@@ -34,7 +33,7 @@ use std::{
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
};
|
||||
use sysinfo::System;
|
||||
use util::ResultExt;
|
||||
use util::{ResultExt, paths::PathStyle, rel_path::RelPath};
|
||||
use worktree::Worktree;
|
||||
|
||||
pub struct HeadlessProject {
|
||||
@@ -405,7 +404,7 @@ impl HeadlessProject {
|
||||
) -> Result<proto::AddWorktreeResponse> {
|
||||
use client::ErrorCodeExt;
|
||||
let fs = this.read_with(&cx, |this, _| this.fs.clone())?;
|
||||
let path = PathBuf::from_proto(shellexpand::tilde(&message.payload.path).to_string());
|
||||
let path = PathBuf::from(shellexpand::tilde(&message.payload.path).to_string());
|
||||
|
||||
let canonicalized = match fs.canonicalize(&path).await {
|
||||
Ok(path) => path,
|
||||
@@ -443,7 +442,7 @@ impl HeadlessProject {
|
||||
let worktree = worktree.read(cx);
|
||||
proto::AddWorktreeResponse {
|
||||
worktree_id: worktree.id().to_proto(),
|
||||
canonicalized_path: canonicalized.to_proto(),
|
||||
canonicalized_path: canonicalized.to_string_lossy().to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -492,16 +491,11 @@ impl HeadlessProject {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::OpenBufferResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
|
||||
let path = RelPath::from_proto(&message.payload.path)?;
|
||||
let (buffer_store, buffer) = this.update(&mut cx, |this, cx| {
|
||||
let buffer_store = this.buffer_store.clone();
|
||||
let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
|
||||
buffer_store.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::<Path>::from_proto(message.payload.path),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
buffer_store.open_buffer(ProjectPath { worktree_id, path }, cx)
|
||||
});
|
||||
anyhow::Ok((buffer_store, buffer))
|
||||
})??;
|
||||
@@ -590,7 +584,7 @@ impl HeadlessProject {
|
||||
buffer_store.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: path.into(),
|
||||
path: path,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -630,7 +624,10 @@ impl HeadlessProject {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::FindSearchCandidatesResponse> {
|
||||
let message = envelope.payload;
|
||||
let query = SearchQuery::from_proto(message.query.context("missing query field")?)?;
|
||||
let query = SearchQuery::from_proto(
|
||||
message.query.context("missing query field")?,
|
||||
PathStyle::local(),
|
||||
)?;
|
||||
let results = this.update(&mut cx, |this, cx| {
|
||||
this.buffer_store.update(cx, |buffer_store, cx| {
|
||||
buffer_store.find_search_candidates(&query, message.limit as _, this.fs.clone(), cx)
|
||||
@@ -662,7 +659,7 @@ impl HeadlessProject {
|
||||
cx: AsyncApp,
|
||||
) -> Result<proto::ListRemoteDirectoryResponse> {
|
||||
let fs = cx.read_entity(&this, |this, _| this.fs.clone())?;
|
||||
let expanded = PathBuf::from_proto(shellexpand::tilde(&envelope.payload.path).to_string());
|
||||
let expanded = PathBuf::from(shellexpand::tilde(&envelope.payload.path).to_string());
|
||||
let check_info = envelope
|
||||
.payload
|
||||
.config
|
||||
@@ -694,7 +691,7 @@ impl HeadlessProject {
|
||||
cx: AsyncApp,
|
||||
) -> Result<proto::GetPathMetadataResponse> {
|
||||
let fs = cx.read_entity(&this, |this, _| this.fs.clone())?;
|
||||
let expanded = PathBuf::from_proto(shellexpand::tilde(&envelope.payload.path).to_string());
|
||||
let expanded = PathBuf::from(shellexpand::tilde(&envelope.payload.path).to_string());
|
||||
|
||||
let metadata = fs.metadata(&expanded).await?;
|
||||
let is_dir = metadata.map(|metadata| metadata.is_dir).unwrap_or(false);
|
||||
@@ -702,7 +699,7 @@ impl HeadlessProject {
|
||||
Ok(proto::GetPathMetadataResponse {
|
||||
exists: metadata.is_some(),
|
||||
is_dir,
|
||||
path: expanded.to_proto(),
|
||||
path: expanded.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use language::{
|
||||
use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::{
|
||||
Project, ProjectPath,
|
||||
Project,
|
||||
agent_server_store::AgentServerCommand,
|
||||
search::{SearchQuery, SearchResult},
|
||||
};
|
||||
@@ -34,7 +34,7 @@ use std::{
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
use unindent::Unindent as _;
|
||||
use util::path;
|
||||
use util::{path, rel_path::rel_path};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||
@@ -57,7 +57,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
.await;
|
||||
fs.set_index_for_repo(
|
||||
Path::new(path!("/code/project1/.git")),
|
||||
&[("src/lib.rs".into(), "fn one() -> usize { 0 }".into())],
|
||||
&[("src/lib.rs", "fn one() -> usize { 0 }".into())],
|
||||
);
|
||||
|
||||
let (project, _headless) = init_test(&fs, cx, server_cx).await;
|
||||
@@ -73,11 +73,11 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||
worktree.update(cx, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
rel_path("README.md"),
|
||||
rel_path("src"),
|
||||
rel_path("src/lib.rs"),
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -86,7 +86,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
// contents are loaded from the remote filesystem.
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -130,12 +130,12 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
cx.executor().run_until_parked();
|
||||
worktree.update(cx, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
worktree.paths().collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
Path::new("src/main.rs"),
|
||||
rel_path("README.md"),
|
||||
rel_path("src"),
|
||||
rel_path("src/lib.rs"),
|
||||
rel_path("src/main.rs"),
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -150,12 +150,12 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
buffer.update(cx, |buffer, _| {
|
||||
assert_eq!(&**buffer.file().unwrap().path(), Path::new("src/lib2.rs"));
|
||||
assert_eq!(&**buffer.file().unwrap().path(), rel_path("src/lib2.rs"));
|
||||
});
|
||||
|
||||
fs.set_index_for_repo(
|
||||
Path::new(path!("/code/project1/.git")),
|
||||
&[("src/lib2.rs".into(), "fn one() -> usize { 100 }".into())],
|
||||
&[("src/lib2.rs", "fn one() -> usize { 100 }".into())],
|
||||
);
|
||||
cx.executor().run_until_parked();
|
||||
diff.update(cx, |diff, _| {
|
||||
@@ -336,7 +336,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -356,7 +356,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
|
||||
AllLanguageSettings::get(
|
||||
Some(SettingsLocation {
|
||||
worktree_id,
|
||||
path: Path::new("src/lib.rs")
|
||||
path: rel_path("src/lib.rs")
|
||||
}),
|
||||
cx
|
||||
)
|
||||
@@ -463,7 +463,7 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
|
||||
|
||||
let (buffer, _handle) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer_with_lsp((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer_with_lsp((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -643,7 +643,7 @@ async fn test_remote_cancel_language_server_work(
|
||||
|
||||
let (buffer, _handle) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer_with_lsp((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer_with_lsp((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -757,7 +757,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -851,7 +851,7 @@ async fn test_remote_resolve_path_in_buffer(
|
||||
|
||||
let buffer2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree2_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer((worktree2_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -863,10 +863,7 @@ async fn test_remote_resolve_path_in_buffer(
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(path.is_file());
|
||||
assert_eq!(
|
||||
path.abs_path().unwrap().to_string_lossy(),
|
||||
path!("/code/project2/README.md")
|
||||
);
|
||||
assert_eq!(path.abs_path().unwrap(), path!("/code/project2/README.md"));
|
||||
|
||||
let path = project
|
||||
.update(cx, |project, cx| {
|
||||
@@ -877,7 +874,7 @@ async fn test_remote_resolve_path_in_buffer(
|
||||
assert!(path.is_file());
|
||||
assert_eq!(
|
||||
path.project_path().unwrap().clone(),
|
||||
ProjectPath::from((worktree2_id, "README.md"))
|
||||
(worktree2_id, rel_path("README.md")).into()
|
||||
);
|
||||
|
||||
let path = project
|
||||
@@ -888,7 +885,7 @@ async fn test_remote_resolve_path_in_buffer(
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
path.project_path().unwrap().clone(),
|
||||
ProjectPath::from((worktree2_id, "src"))
|
||||
(worktree2_id, rel_path("src")).into()
|
||||
);
|
||||
assert!(path.is_dir());
|
||||
}
|
||||
@@ -920,10 +917,7 @@ async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut T
|
||||
.unwrap();
|
||||
|
||||
assert!(path.is_file());
|
||||
assert_eq!(
|
||||
path.abs_path().unwrap().to_string_lossy(),
|
||||
path!("/code/project1/README.md")
|
||||
);
|
||||
assert_eq!(path.abs_path().unwrap(), path!("/code/project1/README.md"));
|
||||
|
||||
let path = project
|
||||
.update(cx, |project, cx| {
|
||||
@@ -933,10 +927,7 @@ async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut T
|
||||
.unwrap();
|
||||
|
||||
assert!(path.is_dir());
|
||||
assert_eq!(
|
||||
path.abs_path().unwrap().to_string_lossy(),
|
||||
path!("/code/project1/src")
|
||||
);
|
||||
assert_eq!(path.abs_path().unwrap(), path!("/code/project1/src"));
|
||||
|
||||
let path = project
|
||||
.update(cx, |project, cx| {
|
||||
@@ -973,14 +964,18 @@ async fn test_canceling_buffer_opening(cx: &mut TestAppContext, server_cx: &mut
|
||||
let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
|
||||
|
||||
// Open a buffer on the client but cancel after a random amount of time.
|
||||
let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, "src/lib.rs"), cx));
|
||||
let buffer = project.update(cx, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
});
|
||||
cx.executor().simulate_random_delay().await;
|
||||
drop(buffer);
|
||||
|
||||
// Try opening the same buffer again as the client, and ensure we can
|
||||
// still do it despite the cancellation above.
|
||||
let buffer = project
|
||||
.update(cx, |p, cx| p.open_buffer((worktree_id, "src/lib.rs"), cx))
|
||||
.update(cx, |p, cx| {
|
||||
p.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1042,10 +1037,7 @@ async fn test_adding_then_removing_then_adding_worktrees(
|
||||
assert!(worktree.is_visible());
|
||||
let entries = worktree.entries(true, 0).collect::<Vec<_>>();
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(
|
||||
entries[1].path.to_string_lossy().to_string(),
|
||||
"README.md".to_string()
|
||||
)
|
||||
assert_eq!(entries[1].path.as_str(), "README.md")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1111,7 +1103,7 @@ async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext)
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1203,10 +1195,11 @@ async fn test_remote_rename_entry(cx: &mut TestAppContext, server_cx: &mut TestA
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let entry = worktree
|
||||
.update(cx, |worktree, cx| {
|
||||
let entry = worktree.entry_for_path("README.md").unwrap();
|
||||
worktree.rename_entry(entry.id, Path::new("README.rst"), cx)
|
||||
let entry = project
|
||||
.update(cx, |project, cx| {
|
||||
let worktree = worktree.read(cx);
|
||||
let entry = worktree.entry_for_path(rel_path("README.md")).unwrap();
|
||||
project.rename_entry(entry.id, (worktree.id(), rel_path("README.rst")).into(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -1216,7 +1209,10 @@ async fn test_remote_rename_entry(cx: &mut TestAppContext, server_cx: &mut TestA
|
||||
cx.run_until_parked();
|
||||
|
||||
worktree.update(cx, |worktree, _| {
|
||||
assert_eq!(worktree.entry_for_path("README.rst").unwrap().id, entry.id)
|
||||
assert_eq!(
|
||||
worktree.entry_for_path(rel_path("README.rst")).unwrap().id,
|
||||
entry.id
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1277,7 +1273,7 @@ async fn test_copy_file_into_remote_project(
|
||||
worktree
|
||||
.update(cx, |worktree, cx| {
|
||||
worktree.copy_external_entries(
|
||||
Path::new("src").into(),
|
||||
rel_path("src").into(),
|
||||
vec![
|
||||
Path::new(path!("/local-code/dir1/file1")).into(),
|
||||
Path::new(path!("/local-code/dir1/dir2")).into(),
|
||||
@@ -1363,11 +1359,11 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
|
||||
.await;
|
||||
fs.set_index_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_1.clone())],
|
||||
&[("src/lib.rs", text_1.clone())],
|
||||
);
|
||||
fs.set_head_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_1.clone())],
|
||||
&[("src/lib.rs", text_1.clone())],
|
||||
"deadbeef",
|
||||
);
|
||||
|
||||
@@ -1383,7 +1379,7 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1409,7 +1405,7 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
|
||||
// stage the current buffer's contents
|
||||
fs.set_index_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_2.clone())],
|
||||
&[("src/lib.rs", text_2.clone())],
|
||||
);
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
@@ -1428,7 +1424,7 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
|
||||
// commit the current buffer's contents
|
||||
fs.set_head_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_2.clone())],
|
||||
&[("src/lib.rs", text_2.clone())],
|
||||
"deadbeef",
|
||||
);
|
||||
|
||||
@@ -1492,7 +1488,7 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
||||
let worktree_id = cx.update(|cx| worktree.read(cx).id());
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1519,11 +1515,11 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
||||
|
||||
fs.set_index_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_1.clone())],
|
||||
&[("src/lib.rs", text_1.clone())],
|
||||
);
|
||||
fs.set_head_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_1.clone())],
|
||||
&[("src/lib.rs", text_1.clone())],
|
||||
"sha",
|
||||
);
|
||||
|
||||
@@ -1551,7 +1547,7 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
||||
// stage the current buffer's contents
|
||||
fs.set_index_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_2.clone())],
|
||||
&[("src/lib.rs", text_2.clone())],
|
||||
);
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
@@ -1570,7 +1566,7 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
||||
// commit the current buffer's contents
|
||||
fs.set_head_for_repo(
|
||||
Path::new("/code/project1/.git"),
|
||||
&[("src/lib.rs".into(), text_2.clone())],
|
||||
&[("src/lib.rs", text_2.clone())],
|
||||
"sha",
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user