Skip to content

Commit 172f73c

Browse files
committed
Merge branch 'index-from-tree'
Conflicts: git-repository/src/repository/revision.rs
2 parents 59767b1 + c40528e commit 172f73c

File tree

18 files changed

+265
-28
lines changed

18 files changed

+265
-28
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

git-index/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ git-features = { version = "^0.22.4", path = "../git-features", features = ["rus
3434
git-hash = { version = "^0.9.9", path = "../git-hash" }
3535
git-bitmap = { version = "^0.1.2", path = "../git-bitmap" }
3636
git-object = { version = "^0.20.2", path = "../git-object" }
37+
git-traverse = { version = "^0.16.0", path = "../git-traverse" }
3738

3839
thiserror = "1.0.32"
3940
memmap2 = "0.5.0"
@@ -50,6 +51,7 @@ document-features = { version = "0.2.0", optional = true }
5051

5152
[dev-dependencies]
5253
git-testtools = { path = "../tests/tools"}
54+
git-repository = { path = "../git-repository"}
5355

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

git-index/src/access.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use bstr::{BStr, ByteSlice};
2-
31
use crate::{entry, extension, Entry, PathStorage, State, Version};
2+
use bstr::{BStr, ByteSlice};
43

54
/// General information and entries
65
impl State {

git-index/src/entry/mod.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub use flags::Flags;
1111
mod write;
1212

1313
/// The time component in a [`Stat`] struct.
14-
#[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
14+
#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
1515
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
1616
pub struct Time {
1717
/// The amount of seconds elapsed since EPOCH
@@ -21,7 +21,7 @@ pub struct Time {
2121
}
2222

2323
/// An entry's filesystem stat information.
24-
#[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
24+
#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
2525
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
2626
pub struct Stat {
2727
/// Modification time
@@ -64,21 +64,26 @@ mod access {
6464
}
6565

6666
mod _impls {
67-
use std::cmp::Ordering;
68-
6967
use crate::{Entry, State};
68+
use bstr::BStr;
69+
use std::cmp::Ordering;
7070

7171
impl Entry {
7272
/// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
7373
/// entry length and stage.
7474
pub fn cmp(&self, other: &Self, state: &State) -> Ordering {
7575
let lhs = self.path(state);
7676
let rhs = other.path(state);
77-
let common_len = lhs.len().min(rhs.len());
78-
lhs[..common_len]
79-
.cmp(&rhs[..common_len])
80-
.then_with(|| lhs.len().cmp(&rhs.len()))
81-
.then_with(|| self.stage().cmp(&other.stage()))
77+
Entry::cmp_filepaths(lhs, rhs).then_with(|| self.stage().cmp(&other.stage()))
78+
}
79+
80+
/// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
81+
/// entry length.
82+
pub fn cmp_filepaths(a: &BStr, b: &BStr) -> Ordering {
83+
let common_len = a.len().min(b.len());
84+
a[..common_len]
85+
.cmp(&b[..common_len])
86+
.then_with(|| a.len().cmp(&b.len()))
8287
}
8388
}
8489
}

git-index/src/init.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::{
2+
entry::{Flags, Mode, Stat},
3+
Entry, PathStorage, State, Version,
4+
};
5+
use bstr::{BStr, BString, ByteSlice, ByteVec};
6+
use git_object::{
7+
tree::{self, EntryMode},
8+
TreeRefIter,
9+
};
10+
use git_traverse::tree::{breadthfirst, visit::Action, Visit};
11+
use std::collections::VecDeque;
12+
13+
/// initialization
14+
impl State {
15+
/// Takes in an oid of a tree object and creates and returns a [`State`][git_index::State] from its children.
16+
pub fn from_tree<Find>(tree: &git_hash::oid, mut find: Find) -> Result<Self, breadthfirst::Error>
17+
where
18+
Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>,
19+
{
20+
let mut buf = Vec::new();
21+
let root = find(tree, &mut buf).ok_or(breadthfirst::Error::NotFound { oid: tree.into() })?;
22+
let state = breadthfirst::State::default();
23+
let mut delegate = EntryBuilder::new();
24+
breadthfirst(root, state, &mut find, &mut delegate)?;
25+
26+
Ok(State {
27+
timestamp: filetime::FileTime::now(),
28+
version: Version::V2,
29+
entries: delegate.entries,
30+
path_backing: delegate.path_backing,
31+
is_sparse: false,
32+
tree: None,
33+
link: None,
34+
resolve_undo: None,
35+
untracked: None,
36+
fs_monitor: None,
37+
})
38+
}
39+
}
40+
41+
struct EntryBuilder {
42+
entries: Vec<Entry>,
43+
path_backing: PathStorage,
44+
path: BString,
45+
path_deque: VecDeque<BString>,
46+
}
47+
48+
impl EntryBuilder {
49+
pub fn new() -> EntryBuilder {
50+
EntryBuilder {
51+
entries: Vec::new(),
52+
path_backing: Vec::new(),
53+
path: BString::default(),
54+
path_deque: VecDeque::new(),
55+
}
56+
}
57+
58+
fn push_element(&mut self, name: &BStr) {
59+
if !self.path.is_empty() {
60+
self.path.push(b'/');
61+
}
62+
self.path.push_str(name);
63+
}
64+
65+
pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
66+
let mode = match entry.mode {
67+
EntryMode::Tree => unreachable!("visit_non_tree() called us"),
68+
EntryMode::Blob => Mode::FILE,
69+
EntryMode::BlobExecutable => Mode::FILE_EXECUTABLE,
70+
EntryMode::Link => Mode::SYMLINK,
71+
EntryMode::Commit => Mode::COMMIT,
72+
};
73+
74+
let path_start = self.path_backing.len();
75+
self.path_backing.extend_from_slice(&self.path);
76+
77+
let new_entry = Entry {
78+
stat: Stat::default(),
79+
id: entry.oid.into(),
80+
flags: Flags::empty(),
81+
mode,
82+
path: path_start..self.path_backing.len(),
83+
};
84+
85+
match self
86+
.entries
87+
.binary_search_by(|entry| Entry::cmp_filepaths(entry.path_in(&self.path_backing), self.path.as_bstr()))
88+
{
89+
Ok(pos) => self.entries[pos] = new_entry,
90+
Err(pos) => self.entries.insert(pos, new_entry),
91+
};
92+
}
93+
}
94+
95+
impl Visit for EntryBuilder {
96+
fn pop_front_tracked_path_and_set_current(&mut self) {
97+
self.path = self
98+
.path_deque
99+
.pop_front()
100+
.expect("every call is matched with push_tracked_path_component");
101+
}
102+
103+
fn push_back_tracked_path_component(&mut self, component: &bstr::BStr) {
104+
self.push_element(component);
105+
self.path_deque.push_back(self.path.clone());
106+
}
107+
108+
fn push_path_component(&mut self, component: &bstr::BStr) {
109+
self.push_element(component);
110+
}
111+
112+
fn pop_path_component(&mut self) {
113+
if let Some(pos) = self.path.rfind_byte(b'/') {
114+
self.path.resize(pos, 0);
115+
} else {
116+
self.path.clear();
117+
}
118+
}
119+
120+
fn visit_tree(&mut self, _entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
121+
Action::Continue
122+
}
123+
124+
fn visit_nontree(&mut self, entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
125+
self.add_entry(entry);
126+
Action::Continue
127+
}
128+
}

git-index/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub mod entry;
2121

2222
mod access;
2323

24+
mod init;
25+
2426
///
2527
pub mod decode;
2628

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:5acb6a5de229b312880af9847cefa54ccb8385f4273f0bb365f2af66dd789f0f
3+
size 11040
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
export GIT_INDEX_VERSION=2;
5+
6+
git init -q sub
7+
(cd sub
8+
9+
touch a b c
10+
git add .
11+
git commit -m "init"
12+
)
13+
14+
git init -q
15+
git config index.threads 1
16+
17+
touch a b
18+
chmod +x b
19+
ln -s a c
20+
mkdir d
21+
(cd d && touch a b c)
22+
23+
git add .
24+
git commit -m "init"

git-index/tests/index/file/write.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ fn roundtrips() -> crate::Result {
1818
(Generated("v2"), Options::default()),
1919
(Generated("V2_empty"), Options::default()),
2020
(Generated("v2_more_files"), all_ext_but_eoie()),
21+
(Generated("v2_all_file_kinds"), all_ext_but_eoie()),
2122
];
2223

2324
for (fixture, options) in input {
@@ -70,6 +71,7 @@ fn state_comparisons_with_various_extension_configurations() {
7071
Generated("V2_empty"),
7172
Generated("v2"),
7273
Generated("v2_more_files"),
74+
Generated("v2_all_file_kinds"),
7375
Generated("v2_split_index"),
7476
Generated("v4_more_files_IEOT"),
7577
] {

git-index/tests/index/init.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use git_index::verify::extensions::no_find;
2+
use git_index::State;
3+
use git_repository as git;
4+
use git_repository::prelude::FindExt;
5+
use git_testtools::scripted_fixture_repo_read_only;
6+
7+
#[test]
8+
fn tree_to_state() -> crate::Result {
9+
let fixtures = [
10+
"make_index/v2.sh",
11+
"make_index/v2_more_files.sh",
12+
"make_index/v2_all_file_kinds.sh",
13+
"make_index/v4_more_files_IEOT.sh",
14+
];
15+
16+
for fixture in fixtures {
17+
let repo_dir = scripted_fixture_repo_read_only(fixture)?;
18+
let repo = git::open(&repo_dir)?;
19+
20+
let tree_id = repo.head_commit()?.tree_id()?;
21+
22+
let expected_state = repo.index()?;
23+
let actual_state = State::from_tree(&tree_id, |oid, buf| repo.objects.find_tree_iter(oid, buf).ok())?;
24+
25+
compare_states(&actual_state, &expected_state, fixture)
26+
}
27+
Ok(())
28+
}
29+
30+
fn compare_states(actual: &State, expected: &State, fixture: &str) {
31+
actual.verify_entries().expect("valid");
32+
actual.verify_extensions(false, no_find).expect("valid");
33+
34+
assert_eq!(
35+
actual.entries().len(),
36+
expected.entries().len(),
37+
"entry count mismatch in {:?}",
38+
fixture
39+
);
40+
41+
for (a, e) in actual.entries().iter().zip(expected.entries()) {
42+
assert_eq!(a.id, e.id, "entry id mismatch in {:?}", fixture);
43+
assert_eq!(a.flags, e.flags, "entry flags mismatch in {:?}", fixture);
44+
assert_eq!(a.mode, e.mode, "entry mode mismatch in {:?}", fixture);
45+
assert_eq!(a.path(actual), e.path(expected), "entry path mismatch in {:?}", fixture);
46+
}
47+
}

git-index/tests/index/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::path::{Path, PathBuf};
22

33
mod file;
4+
mod init;
45

56
pub fn fixture_index_path(name: &str) -> PathBuf {
67
let dir = git_testtools::scripted_fixture_repo_read_only(Path::new("make_index").join(name).with_extension("sh"))

git-pathspec/tests/pathspec.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ mod parse {
284284
":_()", ":`()", ":~()",
285285
];
286286

287-
inputs.into_iter().for_each(|input| {
287+
for input in inputs.into_iter() {
288288
assert!(
289289
!check_against_baseline(input),
290290
"This pathspec is valid in git: {}",
@@ -294,7 +294,7 @@ mod parse {
294294
let output = git_pathspec::parse(input.as_bytes());
295295
assert!(output.is_err());
296296
assert!(matches!(output.unwrap_err(), Error::Unimplemented { .. }));
297-
});
297+
}
298298
}
299299

300300
#[test]
@@ -306,7 +306,7 @@ mod parse {
306306
":(top,exclude,icse)some/path",
307307
];
308308

309-
inputs.into_iter().for_each(|input| {
309+
for input in inputs.into_iter() {
310310
assert!(
311311
!check_against_baseline(input),
312312
"This pathspec is valid in git: {}",
@@ -316,7 +316,7 @@ mod parse {
316316
let output = git_pathspec::parse(input.as_bytes());
317317
assert!(output.is_err());
318318
assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. }));
319-
});
319+
}
320320
}
321321

322322
#[test]
@@ -454,7 +454,7 @@ mod parse {
454454
}
455455

456456
fn check_valid_inputs<'a>(inputs: impl IntoIterator<Item = (&'a str, PatternForTesting)>) {
457-
inputs.into_iter().for_each(|(input, expected)| {
457+
for (input, expected) in inputs.into_iter() {
458458
assert!(
459459
check_against_baseline(input),
460460
"This pathspec is invalid in git: {}",
@@ -465,7 +465,7 @@ mod parse {
465465
.unwrap_or_else(|_| panic!("parsing should not fail with pathspec {}", input))
466466
.into();
467467
assert_eq!(pattern, expected, "while checking input: \"{}\"", input);
468-
});
468+
}
469469
}
470470

471471
fn pat_with_path(path: &str) -> PatternForTesting {

0 commit comments

Comments
 (0)