Skip to content

Commit 7ae3797

Browse files
authored
Merge pull request #2016 from GitoxideLabs/improvements
various improvements
2 parents 648022b + b985766 commit 7ae3797

File tree

19 files changed

+184
-27
lines changed

19 files changed

+184
-27
lines changed

gix-index/src/entry/mode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ impl Mode {
4949
) -> Option<Change> {
5050
match self {
5151
Mode::FILE if !stat.is_file() => (),
52+
Mode::SYMLINK if stat.is_symlink() => return None,
5253
Mode::SYMLINK if has_symlinks && !stat.is_symlink() => (),
5354
Mode::SYMLINK if !has_symlinks && !stat.is_file() => (),
5455
Mode::COMMIT | Mode::DIR if !stat.is_dir() => (),

gix-ref/src/store/file/mod.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ mod access {
5353
}
5454
}
5555

56-
use crate::file;
56+
use crate::{file, Target};
5757

5858
/// Access
5959
impl file::Store {
@@ -78,6 +78,35 @@ mod access {
7878
pub fn common_dir_resolved(&self) -> &Path {
7979
self.common_dir.as_deref().unwrap_or(&self.git_dir)
8080
}
81+
82+
/// Return `Some(true)` if this is a freshly initialized ref store without any observable changes.
83+
/// Return `None` if `HEAD` couldn't be read.
84+
///
85+
/// This is the case if:
86+
///
87+
/// * the ref-store is valid
88+
/// * `HEAD` exists
89+
/// * `HEAD` still points to `default_ref`
90+
/// * there are no packed refs
91+
/// * There are no observable references in `refs/`
92+
pub fn is_pristine(&self, default_ref: &crate::FullNameRef) -> Option<bool> {
93+
let head = self.find_loose("HEAD").ok()?;
94+
match head.target {
95+
Target::Object(_) => return Some(false),
96+
Target::Symbolic(name) => {
97+
if name.as_ref() != default_ref {
98+
return Some(false);
99+
}
100+
}
101+
}
102+
if self.loose_iter().ok()?.filter_map(Result::ok).next().is_some() {
103+
return Some(false);
104+
}
105+
if self.packed_refs_path().is_file() {
106+
return Some(false);
107+
}
108+
Some(true)
109+
}
81110
}
82111
}
83112

Binary file not shown.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init untouched
5+
6+
git init changed-headref
7+
(cd changed-headref
8+
echo "ref: refs/heads/other" >.git/HEAD
9+
)
10+
11+
git init detached
12+
(cd detached
13+
echo "abcdefabcdefabcdefabcdefabcdefabcdefabcd" >.git/HEAD
14+
)
15+
16+
git init invalid-loose-ref
17+
(cd invalid-loose-ref
18+
touch .git/refs/heads/empty
19+
)

gix-ref/tests/refs/file/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ pub fn store_with_packed_refs() -> crate::Result<Store> {
1414
}
1515

1616
pub fn store_at(name: &str) -> crate::Result<Store> {
17-
let path = gix_testtools::scripted_fixture_read_only_standalone(name)?;
18-
Ok(Store::at(path.join(".git"), Default::default()))
17+
named_store_at(name, "")
18+
}
19+
20+
pub fn named_store_at(script_name: &str, name: &str) -> crate::Result<Store> {
21+
let path = gix_testtools::scripted_fixture_read_only_standalone(script_name)?;
22+
Ok(Store::at(path.join(name).join(".git"), Default::default()))
1923
}
2024

2125
pub fn store_at_with_args(name: &str, args: impl IntoIterator<Item = impl Into<String>>) -> crate::Result<Store> {

gix-ref/tests/refs/file/store/access.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::file::store;
1+
use crate::file::{named_store_at, store};
22

33
#[test]
44
fn set_packed_buffer_mmap_threshold() -> crate::Result {
@@ -20,3 +20,22 @@ fn set_packed_buffer_mmap_threshold() -> crate::Result {
2020
);
2121
Ok(())
2222
}
23+
24+
#[test]
25+
fn is_pristine() -> crate::Result {
26+
let store = named_store_at("make_pristine.sh", "untouched")?;
27+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(true));
28+
assert_eq!(store.is_pristine("refs/heads/other".try_into()?), Some(false));
29+
30+
let store = named_store_at("make_pristine.sh", "changed-headref")?;
31+
assert_eq!(store.is_pristine("refs/heads/other".try_into()?), Some(true));
32+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(false));
33+
34+
let store = named_store_at("make_pristine.sh", "detached")?;
35+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(false));
36+
37+
let store = named_store_at("make_pristine.sh", "invalid-loose-ref")?;
38+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(true));
39+
40+
Ok(())
41+
}

gix-ref/tests/refs/file/store/find.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod existing {
99
"make_packed_ref_repository_for_overlay.sh",
1010
] {
1111
let store = store_at(fixture)?;
12+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(false));
1213
let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03");
1314
let r = store.find("main")?;
1415
assert_eq!(r.target.into_id(), c1);

gix-ref/tests/refs/file/store/iter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ fn overlay_partial_prefix_iter_when_prefix_is_dir() -> crate::Result {
576576
use gix_ref::Target::*;
577577

578578
let store = store_at("make_packed_ref_repository_for_overlay.sh")?;
579+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(false));
579580
let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03");
580581

581582
let ref_names = store

gix-ref/tests/refs/file/worktree.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ mod read_only {
8484
fn linked() -> crate::Result {
8585
for packed in [false, true] {
8686
let (store, odb, _tmp) = worktree_store(packed, "w1", Mode::Read)?;
87+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(false));
8788
let peel = into_peel(&store, odb);
8889

8990
let w1_head_id = peel(store.find("HEAD").unwrap());
@@ -132,6 +133,7 @@ mod read_only {
132133
fn main() -> crate::Result {
133134
for packed in [false, true] {
134135
let (store, odb, _tmp) = main_store(packed, Mode::Read)?;
136+
assert_eq!(store.is_pristine("refs/heads/main".try_into()?), Some(false));
135137
let peel = into_peel(&store, odb);
136138

137139
let head_id = peel(store.find("HEAD").unwrap());

gix-status/src/index_as_worktree/function.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,12 @@ impl<'index> State<'_, 'index> {
408408
None => false,
409409
};
410410

411-
// Here we implement racy-git. See racy-git.txt in the git documentation for a detailed documentation.
411+
// We implement racy-git. See racy-git.txt in the git documentation for detailed documentation.
412412
//
413413
// A file is racy if:
414-
// 1. its `mtime` is at or after the last index timestamp and its entry stat information
415-
// matches the on-disk file but the file contents are actually modified
416-
// 2. it's size is 0 (set after detecting a file was racy previously)
414+
// 1. Its `mtime` is at or after the last index timestamp and its entry stat information
415+
// matches the on-disk file, but the file contents are actually modified
416+
// 2. Its size is 0 (set after detecting a file was racy previously)
417417
//
418418
// The first case is detected below by checking the timestamp if the file is marked unmodified.
419419
// The second case is usually detected either because the on-disk file is not empty, hence
@@ -449,7 +449,16 @@ impl<'index> State<'_, 'index> {
449449
file_len: file_size_bytes,
450450
filter: &mut self.filter,
451451
attr_stack: &mut self.attr_stack,
452-
options: self.options,
452+
core_symlinks:
453+
// If this is legitimately a symlink, then pretend symlinks are enabled as the option seems stale.
454+
// Otherwise, respect the option.
455+
if metadata.is_symlink()
456+
&& entry.mode.to_tree_entry_mode().map(|m| m.kind()) == Some(gix_object::tree::EntryKind::Link)
457+
{
458+
true
459+
} else {
460+
self.options.fs.symlink
461+
},
453462
id: &entry.id,
454463
objects,
455464
worktree_reads: self.worktree_reads,
@@ -517,7 +526,7 @@ where
517526
entry: &'a gix_index::Entry,
518527
filter: &'a mut gix_filter::Pipeline,
519528
attr_stack: &'a mut gix_worktree::Stack,
520-
options: &'a Options,
529+
core_symlinks: bool,
521530
id: &'a gix_hash::oid,
522531
objects: Find,
523532
worktree_bytes: &'a AtomicU64,
@@ -545,7 +554,7 @@ where
545554
//
546555
let is_symlink = self.entry.mode == gix_index::entry::Mode::SYMLINK;
547556
// TODO: what to do about precompose unicode and ignore_case for symlinks
548-
let out = if is_symlink && self.options.fs.symlink {
557+
let out = if is_symlink && self.core_symlinks {
549558
// conversion to bstr can never fail because symlinks are only used
550559
// on unix (by git) so no reason to use the try version here
551560
let symlink_path =

gix-status/tests/status/index_as_worktree.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ fn nonfile_fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome {
4848
false,
4949
Default::default(),
5050
false,
51+
None,
5152
)
5253
}
5354

@@ -65,6 +66,7 @@ fn fixture_with_index(
6566
false,
6667
Default::default(),
6768
false,
69+
None,
6870
)
6971
}
7072

@@ -78,6 +80,7 @@ fn submodule_fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome
7880
false,
7981
Default::default(),
8082
false,
83+
None,
8184
)
8285
}
8386

@@ -91,6 +94,7 @@ fn conflict_fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome
9194
false,
9295
Default::default(),
9396
false,
97+
None,
9498
)
9599
}
96100

@@ -104,6 +108,7 @@ fn submodule_fixture_status(name: &str, expected_status: &[Expectation<'_>], sub
104108
submodule_dirty,
105109
Default::default(),
106110
false,
111+
None,
107112
)
108113
}
109114

@@ -117,6 +122,7 @@ fn fixture_filtered(name: &str, pathspecs: &[&str], expected_status: &[Expectati
117122
false,
118123
Default::default(),
119124
false,
125+
None,
120126
)
121127
}
122128

@@ -130,6 +136,7 @@ fn fixture_filtered_detailed(
130136
submodule_dirty: bool,
131137
auto_crlf: gix_filter::eol::AutoCrlf,
132138
use_odb: bool,
139+
fs_capabilities: Option<&dyn Fn(&std::path::Path) -> gix_fs::Capabilities>,
133140
) -> Outcome {
134141
// This can easily happen in some fixtures, which can cause flakiness. It's time-dependent after all.
135142
fn ignore_racyclean(mut out: Outcome) -> Outcome {
@@ -179,7 +186,7 @@ fn fixture_filtered_detailed(
179186
should_interrupt: &AtomicBool::default(),
180187
};
181188
let options = Options {
182-
fs: gix_fs::Capabilities::probe(&git_dir),
189+
fs: fs_capabilities.map_or_else(|| gix_fs::Capabilities::probe(&git_dir), |new| new(&git_dir)),
183190
stat: TEST_OPTIONS,
184191
..Options::default()
185192
};
@@ -353,6 +360,7 @@ fn replace_dir_with_file() {
353360
false,
354361
Default::default(),
355362
false,
363+
None,
356364
);
357365
assert_eq!(
358366
out,
@@ -560,6 +568,24 @@ fn unchanged() {
560568
fixture("status_unchanged", &[]);
561569
}
562570

571+
#[test]
572+
fn unchanged_symlinks_present_but_deactivated() {
573+
fixture_filtered_detailed(
574+
"status_unchanged",
575+
"",
576+
&[],
577+
&[],
578+
|_| {},
579+
false,
580+
Default::default(),
581+
false,
582+
Some(&|dir| gix_fs::Capabilities {
583+
symlink: false,
584+
..gix_fs::Capabilities::probe(dir)
585+
}),
586+
);
587+
}
588+
563589
#[test]
564590
fn unchanged_despite_filter() {
565591
let actual_outcome = fixture_filtered_detailed(
@@ -571,6 +597,7 @@ fn unchanged_despite_filter() {
571597
false,
572598
AutoCrlf::Enabled,
573599
true, /* make ODB available */
600+
None,
574601
);
575602

576603
let expected_outcome = Outcome {

gix/src/repository/location.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::path::{Path, PathBuf};
23

34
use gix_path::realpath::MAX_SYMLINKS;
@@ -108,4 +109,20 @@ impl crate::Repository {
108109
None => crate::repository::Kind::Bare,
109110
}
110111
}
112+
113+
/// Returns `Some(true)` if the reference database [is untouched](gix_ref::file::Store::is_pristine()).
114+
/// This typically indicates that the repository is new and empty.
115+
/// Return `None` if a defect in the database makes the answer uncertain.
116+
#[doc(alias = "is_empty", alias = "git2")]
117+
pub fn is_pristine(&self) -> Option<bool> {
118+
let name = self
119+
.config
120+
.resolved
121+
.string(crate::config::tree::Init::DEFAULT_BRANCH)
122+
.unwrap_or(Cow::Borrowed("master".into()));
123+
let default_branch_ref_name: gix_ref::FullName = format!("refs/heads/{name}")
124+
.try_into()
125+
.unwrap_or_else(|_| gix_ref::FullName::try_from("refs/heads/master").expect("known to be valid"));
126+
self.refs.is_pristine(default_branch_ref_name.as_ref())
127+
}
111128
}

gix/src/repository/submodule.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl Repository {
2626
)?))
2727
}
2828

29-
/// Return a shared [`.gitmodules` file](crate::submodule::File) which is updated automatically if the in-memory snapshot
29+
/// Return a shared [`.gitmodules` file](submodule::File) which is updated automatically if the in-memory snapshot
3030
/// has become stale as the underlying file on disk has changed. The snapshot based on the file on disk is shared across all
3131
/// clones of this repository.
3232
///
@@ -54,12 +54,20 @@ impl Repository {
5454
}) {
5555
Some(id) => id,
5656
None => match self
57-
.head_commit()?
58-
.tree()?
59-
.find_entry(submodule::MODULES_FILE)
60-
.map(|entry| entry.inner.oid)
57+
.head()?
58+
.try_peel_to_id_in_place()?
59+
.map(|id| -> Result<Option<_>, submodule::modules::Error> {
60+
Ok(id
61+
.object()?
62+
.peel_to_commit()?
63+
.tree()?
64+
.find_entry(submodule::MODULES_FILE)
65+
.map(|entry| entry.inner.oid.to_owned()))
66+
})
67+
.transpose()?
68+
.flatten()
6169
{
62-
Some(id) => id.to_owned(),
70+
Some(id) => id,
6371
None => return Ok(None),
6472
},
6573
};

gix/src/status/index_worktree.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,6 @@ mod submodule_status {
226226
v
227227
}
228228
Ok(None) => Vec::new(),
229-
Err(crate::submodule::modules::Error::FindHeadCommit(
230-
crate::reference::head_commit::Error::PeelToCommit(
231-
crate::head::peel::to_commit::Error::PeelToObject(
232-
crate::head::peel::to_object::Error::Unborn { .. },
233-
),
234-
),
235-
)) => Vec::new(),
236229
Err(err) => return Err(err),
237230
};
238231
Ok(Self {

gix/src/submodule/errors.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ pub mod modules {
2323
OpenIndex(#[from] crate::worktree::open_index::Error),
2424
#[error("Could not find the .gitmodules file by id in the object database")]
2525
FindExistingBlob(#[from] crate::object::find::existing::Error),
26-
#[error("Did not find commit in current HEAD to access its tree")]
27-
FindHeadCommit(#[from] crate::reference::head_commit::Error),
26+
#[error(transparent)]
27+
FindHeadRef(#[from] crate::reference::find::existing::Error),
28+
#[error(transparent)]
29+
PeelHeadRef(#[from] crate::head::peel::Error),
30+
#[error(transparent)]
31+
PeelObjectToCommit(#[from] crate::object::peel::to_kind::Error),
2832
#[error(transparent)]
2933
TreeFromCommit(#[from] crate::object::commit::Error),
3034
}
Binary file not shown.

0 commit comments

Comments
 (0)