Skip to content

Commit

Permalink
Merge branch 'index-from-tree'
Browse files Browse the repository at this point in the history
Conflicts:
	git-repository/src/repository/revision.rs
  • Loading branch information
Byron committed Sep 15, 2022
2 parents 59767b1 + c40528e commit 172f73c
Show file tree
Hide file tree
Showing 18 changed files with 265 additions and 28 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions git-index/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ git-features = { version = "^0.22.4", path = "../git-features", features = ["rus
git-hash = { version = "^0.9.9", path = "../git-hash" }
git-bitmap = { version = "^0.1.2", path = "../git-bitmap" }
git-object = { version = "^0.20.2", path = "../git-object" }
git-traverse = { version = "^0.16.0", path = "../git-traverse" }

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

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

[package.metadata.docs.rs]
features = ["document-features", "serde1"]
Expand Down
3 changes: 1 addition & 2 deletions git-index/src/access.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use bstr::{BStr, ByteSlice};

use crate::{entry, extension, Entry, PathStorage, State, Version};
use bstr::{BStr, ByteSlice};

/// General information and entries
impl State {
Expand Down
23 changes: 14 additions & 9 deletions git-index/src/entry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use flags::Flags;
mod write;

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

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

mod _impls {
use std::cmp::Ordering;

use crate::{Entry, State};
use bstr::BStr;
use std::cmp::Ordering;

impl Entry {
/// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
/// entry length and stage.
pub fn cmp(&self, other: &Self, state: &State) -> Ordering {
let lhs = self.path(state);
let rhs = other.path(state);
let common_len = lhs.len().min(rhs.len());
lhs[..common_len]
.cmp(&rhs[..common_len])
.then_with(|| lhs.len().cmp(&rhs.len()))
.then_with(|| self.stage().cmp(&other.stage()))
Entry::cmp_filepaths(lhs, rhs).then_with(|| self.stage().cmp(&other.stage()))
}

/// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
/// entry length.
pub fn cmp_filepaths(a: &BStr, b: &BStr) -> Ordering {
let common_len = a.len().min(b.len());
a[..common_len]
.cmp(&b[..common_len])
.then_with(|| a.len().cmp(&b.len()))
}
}
}
128 changes: 128 additions & 0 deletions git-index/src/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::{
entry::{Flags, Mode, Stat},
Entry, PathStorage, State, Version,
};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use git_object::{
tree::{self, EntryMode},
TreeRefIter,
};
use git_traverse::tree::{breadthfirst, visit::Action, Visit};
use std::collections::VecDeque;

/// initialization
impl State {
/// Takes in an oid of a tree object and creates and returns a [`State`][git_index::State] from its children.
pub fn from_tree<Find>(tree: &git_hash::oid, mut find: Find) -> Result<Self, breadthfirst::Error>
where
Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>,
{
let mut buf = Vec::new();
let root = find(tree, &mut buf).ok_or(breadthfirst::Error::NotFound { oid: tree.into() })?;
let state = breadthfirst::State::default();
let mut delegate = EntryBuilder::new();
breadthfirst(root, state, &mut find, &mut delegate)?;

Ok(State {
timestamp: filetime::FileTime::now(),
version: Version::V2,
entries: delegate.entries,
path_backing: delegate.path_backing,
is_sparse: false,
tree: None,
link: None,
resolve_undo: None,
untracked: None,
fs_monitor: None,
})
}
}

struct EntryBuilder {
entries: Vec<Entry>,
path_backing: PathStorage,
path: BString,
path_deque: VecDeque<BString>,
}

impl EntryBuilder {
pub fn new() -> EntryBuilder {
EntryBuilder {
entries: Vec::new(),
path_backing: Vec::new(),
path: BString::default(),
path_deque: VecDeque::new(),
}
}

fn push_element(&mut self, name: &BStr) {
if !self.path.is_empty() {
self.path.push(b'/');
}
self.path.push_str(name);
}

pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
let mode = match entry.mode {
EntryMode::Tree => unreachable!("visit_non_tree() called us"),
EntryMode::Blob => Mode::FILE,
EntryMode::BlobExecutable => Mode::FILE_EXECUTABLE,
EntryMode::Link => Mode::SYMLINK,
EntryMode::Commit => Mode::COMMIT,
};

let path_start = self.path_backing.len();
self.path_backing.extend_from_slice(&self.path);

let new_entry = Entry {
stat: Stat::default(),
id: entry.oid.into(),
flags: Flags::empty(),
mode,
path: path_start..self.path_backing.len(),
};

match self
.entries
.binary_search_by(|entry| Entry::cmp_filepaths(entry.path_in(&self.path_backing), self.path.as_bstr()))
{
Ok(pos) => self.entries[pos] = new_entry,
Err(pos) => self.entries.insert(pos, new_entry),
};
}
}

impl Visit for EntryBuilder {
fn pop_front_tracked_path_and_set_current(&mut self) {
self.path = self
.path_deque
.pop_front()
.expect("every call is matched with push_tracked_path_component");
}

fn push_back_tracked_path_component(&mut self, component: &bstr::BStr) {
self.push_element(component);
self.path_deque.push_back(self.path.clone());
}

fn push_path_component(&mut self, component: &bstr::BStr) {
self.push_element(component);
}

fn pop_path_component(&mut self) {
if let Some(pos) = self.path.rfind_byte(b'/') {
self.path.resize(pos, 0);
} else {
self.path.clear();
}
}

fn visit_tree(&mut self, _entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
Action::Continue
}

fn visit_nontree(&mut self, entry: &git_object::tree::EntryRef<'_>) -> git_traverse::tree::visit::Action {
self.add_entry(entry);
Action::Continue
}
}
2 changes: 2 additions & 0 deletions git-index/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub mod entry;

mod access;

mod init;

///
pub mod decode;

Expand Down
Git LFS file not shown
24 changes: 24 additions & 0 deletions git-index/tests/fixtures/make_index/v2_all_file_kinds.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
set -eu -o pipefail

export GIT_INDEX_VERSION=2;

git init -q sub
(cd sub

touch a b c
git add .
git commit -m "init"
)

git init -q
git config index.threads 1

touch a b
chmod +x b
ln -s a c
mkdir d
(cd d && touch a b c)

git add .
git commit -m "init"
2 changes: 2 additions & 0 deletions git-index/tests/index/file/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ fn roundtrips() -> crate::Result {
(Generated("v2"), Options::default()),
(Generated("V2_empty"), Options::default()),
(Generated("v2_more_files"), all_ext_but_eoie()),
(Generated("v2_all_file_kinds"), all_ext_but_eoie()),
];

for (fixture, options) in input {
Expand Down Expand Up @@ -70,6 +71,7 @@ fn state_comparisons_with_various_extension_configurations() {
Generated("V2_empty"),
Generated("v2"),
Generated("v2_more_files"),
Generated("v2_all_file_kinds"),
Generated("v2_split_index"),
Generated("v4_more_files_IEOT"),
] {
Expand Down
47 changes: 47 additions & 0 deletions git-index/tests/index/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use git_index::verify::extensions::no_find;
use git_index::State;
use git_repository as git;
use git_repository::prelude::FindExt;
use git_testtools::scripted_fixture_repo_read_only;

#[test]
fn tree_to_state() -> crate::Result {
let fixtures = [
"make_index/v2.sh",
"make_index/v2_more_files.sh",
"make_index/v2_all_file_kinds.sh",
"make_index/v4_more_files_IEOT.sh",
];

for fixture in fixtures {
let repo_dir = scripted_fixture_repo_read_only(fixture)?;
let repo = git::open(&repo_dir)?;

let tree_id = repo.head_commit()?.tree_id()?;

let expected_state = repo.index()?;
let actual_state = State::from_tree(&tree_id, |oid, buf| repo.objects.find_tree_iter(oid, buf).ok())?;

compare_states(&actual_state, &expected_state, fixture)
}
Ok(())
}

fn compare_states(actual: &State, expected: &State, fixture: &str) {
actual.verify_entries().expect("valid");
actual.verify_extensions(false, no_find).expect("valid");

assert_eq!(
actual.entries().len(),
expected.entries().len(),
"entry count mismatch in {:?}",
fixture
);

for (a, e) in actual.entries().iter().zip(expected.entries()) {
assert_eq!(a.id, e.id, "entry id mismatch in {:?}", fixture);
assert_eq!(a.flags, e.flags, "entry flags mismatch in {:?}", fixture);
assert_eq!(a.mode, e.mode, "entry mode mismatch in {:?}", fixture);
assert_eq!(a.path(actual), e.path(expected), "entry path mismatch in {:?}", fixture);
}
}
1 change: 1 addition & 0 deletions git-index/tests/index/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};

mod file;
mod init;

pub fn fixture_index_path(name: &str) -> PathBuf {
let dir = git_testtools::scripted_fixture_repo_read_only(Path::new("make_index").join(name).with_extension("sh"))
Expand Down
12 changes: 6 additions & 6 deletions git-pathspec/tests/pathspec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ mod parse {
":_()", ":`()", ":~()",
];

inputs.into_iter().for_each(|input| {
for input in inputs.into_iter() {
assert!(
!check_against_baseline(input),
"This pathspec is valid in git: {}",
Expand All @@ -294,7 +294,7 @@ mod parse {
let output = git_pathspec::parse(input.as_bytes());
assert!(output.is_err());
assert!(matches!(output.unwrap_err(), Error::Unimplemented { .. }));
});
}
}

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

inputs.into_iter().for_each(|input| {
for input in inputs.into_iter() {
assert!(
!check_against_baseline(input),
"This pathspec is valid in git: {}",
Expand All @@ -316,7 +316,7 @@ mod parse {
let output = git_pathspec::parse(input.as_bytes());
assert!(output.is_err());
assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. }));
});
}
}

#[test]
Expand Down Expand Up @@ -454,7 +454,7 @@ mod parse {
}

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

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

0 comments on commit 172f73c

Please sign in to comment.