Skip to content

handle case-insensitive file systems (those that apply folding rules) #341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ check: ## Build all code in suitable configurations
cd git-object && cargo check --all-features \
&& cargo check --features verbose-object-parsing-errors
cd git-index && cargo check --features serde1
cd git-worktree && cargo check --features serde1
cd git-actor && cargo check --features serde1
cd git-pack && cargo check --features serde1 \
&& cargo check --features pack-cache-lru-static \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ Follow linked crate name for detailed status. Please note that all crates follow
* `gitoxide-core`
* **very early**
* [git-index](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-index)
* [git-worktree](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-worktree)
* [git-bitmap](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-bitmap)
* **idea**
* [git-revision](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-revision)
* [git-worktree](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-worktree)
* [git-tui](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-tui)
* [git-bundle](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-bundle)

Expand Down
6 changes: 6 additions & 0 deletions git-index/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,11 @@ smallvec = "1.7.0"
atoi = "1.0.0"
bitflags = "1.3.2"

document-features = { version = "0.2.0", optional = true }

[dev-dependencies]
git-testtools = { path = "../tests/tools"}

[package.metadata.docs.rs]
features = ["document-features", "serde1"]

5 changes: 5 additions & 0 deletions git-index/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! ## Feature Flags
#![cfg_attr(
feature = "document-features",
cfg_attr(doc, doc = ::document_features::document_features!())
)]
#![deny(unsafe_code, missing_docs, rust_2018_idioms)]
#![allow(missing_docs)]

Expand Down
17 changes: 4 additions & 13 deletions git-pack/src/data/output/entry/iter_from_counts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,31 +56,20 @@ where
);
let (chunk_size, thread_limit, _) =
parallel::optimize_chunk_size_and_thread_limit(chunk_size, Some(counts.len()), thread_limit, None);
let chunks = util::ChunkRanges::new(chunk_size, counts.len());
{
let progress = Arc::new(parking_lot::Mutex::new(progress.add_child("resolving")));
progress.lock().init(None, git_features::progress::count("counts"));
let enough_counts_present = counts.len() > 4_000;
let start = std::time::Instant::now();
parallel::in_parallel_if(
|| enough_counts_present,
chunks.clone(),
counts.chunks_mut(chunk_size),
thread_limit,
|_n| Vec::<u8>::new(),
{
let progress = Arc::clone(&progress);
let counts = &counts;
let db = db.clone();
move |chunk_range, buf| {
let chunk = {
let c = &counts[chunk_range];
let mut_ptr = c.as_ptr() as *mut output::Count;
// SAFETY: We know that 'chunks' is only non-overlapping slices, and this function owns `counts`.
#[allow(unsafe_code)]
unsafe {
std::slice::from_raw_parts_mut(mut_ptr, c.len())
}
};
move |chunk, buf| {
let chunk_size = chunk.len();
for count in chunk {
use crate::data::output::count::PackLocation::*;
Expand Down Expand Up @@ -135,8 +124,10 @@ where
index
}
};

let counts = Arc::new(counts);
let progress = Arc::new(parking_lot::Mutex::new(progress));
let chunks = util::ChunkRanges::new(chunk_size, counts.len());

parallel::reduce::Stepwise::new(
chunks.enumerate(),
Expand Down
6 changes: 5 additions & 1 deletion git-ref/tests/packed/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ fn find_speed() -> crate::Result {
let packed = store.open_packed_buffer()?.expect("packed-refs present");
let start = std::time::Instant::now();
let mut num_refs = 0;
for r in packed.iter()?.take(10_000) {
#[cfg(windows)]
let count = 500;
#[cfg(not(windows))]
let count = 10_000;
for r in packed.iter()?.take(count) {
num_refs += 1;
let r = r?;
assert_eq!(
Expand Down
12 changes: 12 additions & 0 deletions git-worktree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,30 @@ doctest = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
## Data structures implement `serde::Serialize` and `serde::Deserialize`.
serde1 = [ "serde", "bstr/serde1", "git-index/serde1", "git-hash/serde1", "git-object/serde1" ]

[dependencies]
git-index = { version = "^0.1.0", path = "../git-index" }
git-hash = { version = "^0.9.0", path = "../git-hash" }
git-object = { version = "^0.17.0", path = "../git-object" }
git-features = { version = "^0.19.1", path = "../git-features" }

serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}

quick-error = "2.0.1"
bstr = { version = "0.2.13", default-features = false }

document-features = { version = "0.2.0", optional = true }
symlink = "0.1.0"

[dev-dependencies]
git-testtools = { path = "../tests/tools" }
git-odb = { path = "../git-odb" }

walkdir = "2.3.2"
tempfile = "3.2.0"

[package.metadata.docs.rs]
features = ["document-features", "serde1"]
141 changes: 141 additions & 0 deletions git-worktree/src/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::path::Path;

/// Common knowledge about the worktree that is needed across most interactions with the work tree
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
pub struct Capabilities {
/// If true, the filesystem will store paths as decomposed unicode, i.e. `ä` becomes `"a\u{308}"`, which means that
/// we have to turn these forms back from decomposed to precomposed unicode before storing it in the index or generally
/// using it. This also applies to input received from the command-line, so callers may have to be aware of this and
/// perform conversions accordingly.
/// If false, no conversions will be performed.
pub precompose_unicode: bool,
/// If true, the filesystem ignores the case of input, which makes `A` the same file as `a`.
/// This is also called case-folding.
pub ignore_case: bool,
/// If true, we assume the the executable bit is honored as part of the files mode. If false, we assume the file system
/// ignores the executable bit, hence it will be reported as 'off' even though we just tried to set it to be on.
pub executable_bit: bool,
/// If true, the file system supports symbolic links and we should try to create them. Otherwise symbolic links will be checked
/// out as files which contain the link as text.
pub symlink: bool,
}

impl Capabilities {
/// try to determine all values in this context by probing them in the given `git_dir`, which
/// should be on the file system the git repository is located on.
/// `git_dir` is a typical git repository, expected to be populated with the typical files like `config`.
///
/// All errors are ignored and interpreted on top of the default for the platform the binary is compiled for.
pub fn probe(git_dir: impl AsRef<Path>) -> Self {
let root = git_dir.as_ref();
let ctx = Capabilities::default();
Capabilities {
symlink: Self::probe_symlink(root).unwrap_or(ctx.symlink),
ignore_case: Self::probe_ignore_case(root).unwrap_or(ctx.ignore_case),
precompose_unicode: Self::probe_precompose_unicode(root).unwrap_or(ctx.precompose_unicode),
executable_bit: Self::probe_file_mode(root).unwrap_or(ctx.executable_bit),
}
}

#[cfg(unix)]
fn probe_file_mode(root: &Path) -> std::io::Result<bool> {
use std::os::unix::fs::{MetadataExt, OpenOptionsExt};

// test it exactly as we typically create executable files, not using chmod.
let test_path = root.join("_test_executable_bit");
let res = std::fs::OpenOptions::new()
.create_new(true)
.write(true)
.mode(0o777)
.open(&test_path)
.and_then(|f| f.metadata().map(|m| m.mode() & 0o100 == 0o100));
std::fs::remove_file(test_path)?;
res
}

#[cfg(not(unix))]
fn probe_file_mode(_root: &Path) -> std::io::Result<bool> {
Ok(false)
}

fn probe_ignore_case(git_dir: &Path) -> std::io::Result<bool> {
std::fs::metadata(git_dir.join("cOnFiG")).map(|_| true).or_else(|err| {
if err.kind() == std::io::ErrorKind::NotFound {
Ok(false)
} else {
Err(err)
}
})
}

fn probe_precompose_unicode(root: &Path) -> std::io::Result<bool> {
let precomposed = "ä";
let decomposed = "a\u{308}";

let precomposed = root.join(precomposed);
std::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&precomposed)?;
let res = root.join(decomposed).symlink_metadata().map(|_| true);
std::fs::remove_file(precomposed)?;
res
}

fn probe_symlink(root: &Path) -> std::io::Result<bool> {
let src_path = root.join("__link_src_file");
std::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&src_path)?;
let link_path = root.join("__file_link");
if symlink::symlink_file(&src_path, &link_path).is_err() {
std::fs::remove_file(&src_path)?;
return Ok(false);
}

let res = std::fs::symlink_metadata(&link_path).map(|m| m.is_symlink());
let cleanup = std::fs::remove_file(&src_path);
symlink::remove_symlink_file(&link_path)
.or_else(|_| std::fs::remove_file(&link_path))
.and(cleanup)?;
res
}
}

#[cfg(windows)]
impl Default for Capabilities {
fn default() -> Self {
Capabilities {
precompose_unicode: false,
ignore_case: true,
executable_bit: false,
symlink: false,
}
}
}

#[cfg(target_os = "macos")]
impl Default for Capabilities {
fn default() -> Self {
Capabilities {
precompose_unicode: true,
ignore_case: true,
executable_bit: true,
symlink: true,
}
}
}

#[cfg(all(unix, not(target_os = "macos")))]
impl Default for Capabilities {
fn default() -> Self {
Capabilities {
precompose_unicode: false,
ignore_case: false,
executable_bit: true,
symlink: true,
}
}
}
Loading