Skip to content

Commit 5751a8e

Browse files
committed
Implemented git-worktree
1 parent 28e3251 commit 5751a8e

File tree

8 files changed

+201
-0
lines changed

8 files changed

+201
-0
lines changed

Cargo.lock

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ check: ## Build all code in suitable configurations
8888
cd git-object && cargo check --all-features \
8989
&& cargo check --features verbose-object-parsing-errors
9090
cd git-index && cargo check --features serde1
91+
cd git-worktree && cargo check
9192
cd git-actor && cargo check --features serde1
9293
cd git-pack && cargo check --features serde1 \
9394
&& cargo check --features pack-cache-lru-static \
@@ -147,6 +148,7 @@ unit-tests: ## run all unit tests
147148
&& cargo test --features "internal-testing-git-features-parallel"
148149
cd git-index && cargo test --features internal-testing-to-avoid-being-run-by-cargo-test-all \
149150
&& cargo test --features "internal-testing-git-features-parallel"
151+
cd git-worktree && cargo test
150152
cd git-packetline && cargo test \
151153
&& cargo test --features blocking-io,maybe-async/is_sync --test blocking-packetline \
152154
&& cargo test --features "async-io" --test async-packetline

git-worktree/Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,14 @@ doctest = false
1313
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1414

1515
[dependencies]
16+
git-index = { version = "^0.1.0", path = "../git-index" }
17+
rayon = "1.5.1"
18+
anyhow = "1.0.42"
19+
git-hash = { version = "^0.9.0", path = "../git-hash" }
20+
git-object = { version = "^0.17.0", path = "../git-object" }
21+
22+
[dev-dependencies]
23+
git-odb = { version = "^0.26.0", path = "../git-odb" }
24+
walkdir = "2.3.2"
25+
git-testtools = { path = "../tests/tools" }
26+
tempfile = "3.2.0"

git-worktree/src/lib.rs

+55
Original file line numberDiff line numberDiff line change
@@ -1 +1,56 @@
11
#![forbid(unsafe_code, rust_2018_idioms)]
2+
//! Git Worktree
3+
4+
use anyhow::Result;
5+
use git_hash::oid;
6+
use git_index::{entry::Mode, State};
7+
use git_object::Data;
8+
use std::fs::create_dir_all;
9+
use std::path::Path;
10+
use std::str;
11+
12+
mod options;
13+
14+
pub use options::Options;
15+
16+
/// Copy index to `path`
17+
pub fn copy_index<P, Find>(state: &State, path: P, mut find: Find, opts: Options) -> Result<()>
18+
where
19+
P: AsRef<Path>,
20+
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<Data<'a>>,
21+
{
22+
let path = path.as_ref();
23+
let entries = state.entries();
24+
let mut buf = Vec::new();
25+
for entry in entries {
26+
let dest = path.join(entry.path(state).to_string());
27+
create_dir_all(dest.parent().expect("Path has no parent"))?;
28+
match entry.mode {
29+
Mode::FILE | Mode::FILE_EXECUTABLE => {
30+
let obj = find(&entry.id, &mut buf).unwrap();
31+
std::fs::write(dest, obj.data)?;
32+
}
33+
Mode::SYMLINK => {
34+
let obj = find(&entry.id, &mut buf).unwrap();
35+
let linked_to = str::from_utf8(obj.data)?;
36+
if opts.symlinks {
37+
#[cfg(unix)]
38+
std::os::unix::fs::symlink(linked_to, dest)?;
39+
#[cfg(windows)]
40+
std::os::windows::fs::symlink(linked_to, dest)?;
41+
} else {
42+
let linked_to_path = path.join(linked_to);
43+
if linked_to_path.exists() {
44+
std::fs::copy(linked_to_path, dest)?;
45+
} else {
46+
std::fs::write(dest, linked_to)?;
47+
}
48+
}
49+
}
50+
Mode::DIR => todo!(),
51+
Mode::COMMIT => todo!(),
52+
_ => {}
53+
}
54+
}
55+
Ok(())
56+
}

git-worktree/src/options.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Options for [copy_index](crate::copy_index)
2+
pub struct Options {
3+
/// Enable/disable symlinks
4+
pub symlinks: bool,
5+
}
6+
7+
impl Default for Options {
8+
fn default() -> Self {
9+
Options { symlinks: true }
10+
}
11+
}

git-worktree/tests/copy_index/mod.rs

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use crate::{dir_structure, fixture_path};
2+
use anyhow::Result;
3+
use git_odb::FindExt;
4+
use git_worktree::{copy_index, Options};
5+
use std::fs;
6+
7+
#[test]
8+
fn test_copy_index() -> Result<()> {
9+
let path = fixture_path("make_repo");
10+
let path_git = path.join(".git");
11+
let file = git_index::File::at(path_git.join("index"), git_index::decode::Options::default())?;
12+
let output_dir = tempfile::tempdir()?;
13+
let output = output_dir.path();
14+
let odb_handle = git_odb::at(path_git.join("objects")).unwrap();
15+
16+
let res = copy_index(
17+
&file,
18+
&output,
19+
move |oid, buf| odb_handle.find(oid, buf).ok(),
20+
Options::default(),
21+
);
22+
23+
let repo_files = dir_structure(&path);
24+
let copy_files = dir_structure(output);
25+
26+
let srepo_files: Vec<_> = repo_files.iter().flat_map(|p| p.strip_prefix(&path)).collect();
27+
let scopy_files: Vec<_> = copy_files.iter().flat_map(|p| p.strip_prefix(output)).collect();
28+
assert!(srepo_files == scopy_files);
29+
30+
for (file1, file2) in repo_files.iter().zip(copy_files.iter()) {
31+
assert!(fs::metadata(file1)?.file_type() == fs::metadata(file2)?.file_type());
32+
assert!(fs::read(file1)? == fs::read(file2)?);
33+
}
34+
35+
res
36+
}
37+
38+
#[test]
39+
fn test_copy_index_without_symlinks() -> Result<()> {
40+
let path = fixture_path("make_repo");
41+
let path_git = path.join(".git");
42+
let file = git_index::File::at(path_git.join("index"), git_index::decode::Options::default())?;
43+
let output_dir = tempfile::tempdir()?;
44+
let output = output_dir.path();
45+
let odb_handle = git_odb::at(path_git.join("objects")).unwrap();
46+
47+
let res = copy_index(
48+
&file,
49+
&output,
50+
move |oid, buf| odb_handle.find(oid, buf).ok(),
51+
Options { symlinks: false },
52+
);
53+
54+
let repo_files = dir_structure(&path);
55+
let copy_files = dir_structure(output);
56+
57+
let srepo_files: Vec<_> = repo_files.iter().flat_map(|p| p.strip_prefix(&path)).collect();
58+
let scopy_files: Vec<_> = copy_files.iter().flat_map(|p| p.strip_prefix(output)).collect();
59+
assert!(srepo_files == scopy_files);
60+
61+
for (file1, file2) in repo_files.iter().zip(copy_files.iter()) {
62+
if file2.is_symlink() {
63+
assert!(!file1.is_symlink());
64+
} else {
65+
assert!(fs::metadata(file1)?.file_type() == fs::metadata(file2)?.file_type());
66+
}
67+
assert!(fs::read(file1)? == fs::read(file2)?);
68+
}
69+
70+
res
71+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
touch a
7+
echo "Test Vals" > a
8+
touch b
9+
touch c
10+
11+
mkdir d
12+
touch d/a
13+
echo "Subdir" > d/a
14+
ln -sf d/a sa
15+
16+
git add -A
17+
git commit -m "Commit"

git-worktree/tests/mod.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::path::{Path, PathBuf};
2+
use walkdir::WalkDir;
3+
4+
mod copy_index;
5+
6+
pub fn dir_structure<P: AsRef<Path>>(path: P) -> Vec<PathBuf> {
7+
let path = path.as_ref();
8+
let mut ps: Vec<_> = WalkDir::new(path)
9+
.into_iter()
10+
.filter_entry(|e| e.path() == path || !e.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false))
11+
.flatten()
12+
.filter(|e| e.path().is_file())
13+
.map(|p| p.path().to_path_buf())
14+
.collect();
15+
ps.sort_unstable();
16+
ps
17+
}
18+
19+
pub fn fixture_path(name: &str) -> PathBuf {
20+
let dir =
21+
git_testtools::scripted_fixture_repo_read_only(Path::new(name).with_extension("sh")).expect("script works");
22+
dir
23+
}

0 commit comments

Comments
 (0)