-
-
Notifications
You must be signed in to change notification settings - Fork 257
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'write-index-files' into rev-parse-delegate
- Loading branch information
Showing
6 changed files
with
413 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
use crate::{extension, State, Version}; | ||
use bstr::ByteVec; | ||
use std::{ | ||
collections::{hash_map, HashMap}, | ||
ops::Range, | ||
}; | ||
|
||
impl State { | ||
pub fn write_to(&self, options: Options) -> Vec<u8> { | ||
let mut writer = Writer::new(self, options); | ||
writer.generate(); | ||
writer.data | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
pub struct Options { | ||
hash_kind: git_hash::Kind, | ||
} | ||
|
||
struct Writer<'a> { | ||
state: &'a State, | ||
options: Options, | ||
data: Vec<u8>, | ||
index_table: HashMap<&'static str, Range<usize>>, | ||
} | ||
|
||
impl<'a> Writer<'a> { | ||
pub fn new(state: &'a State, options: Options) -> Self { | ||
Self { | ||
state, | ||
options, | ||
data: Vec::default(), | ||
index_table: Default::default(), | ||
} | ||
} | ||
|
||
pub fn generate(&mut self) { | ||
self.header(); | ||
self.entries(); | ||
|
||
// TODO: Tree extension is always included, I think | ||
if let Some(t) = self.state.tree() { | ||
self.tree(t) | ||
} | ||
|
||
self.end_of_index(); | ||
} | ||
|
||
fn push(&mut self, data: &[u8], key: &'static str) { | ||
let start = self.data.len(); | ||
let end = start + data.len(); | ||
|
||
match self.index_table.entry(key) { | ||
hash_map::Entry::Occupied(mut e) => e.get_mut().end = end, | ||
hash_map::Entry::Vacant(e) => { | ||
e.insert(start..end); | ||
} | ||
}; | ||
|
||
self.data.push_str(data); | ||
} | ||
|
||
fn header(&mut self) { | ||
let signature = b"DIRC"; | ||
let version = match self.state.version() { | ||
Version::V2 => 2_u32.to_be_bytes(), | ||
Version::V3 => 3_u32.to_be_bytes(), | ||
Version::V4 => 4_u32.to_be_bytes(), | ||
}; | ||
let num_entries = self.state.entries().len() as u32; | ||
|
||
self.push(signature, "header"); | ||
self.push(&version, "header"); | ||
self.push(&(num_entries).to_be_bytes(), "header"); | ||
} | ||
|
||
fn entries(&mut self) { | ||
for e in self.state.entries() { | ||
self.push(&e.stat.ctime.secs.to_be_bytes(), "entries"); | ||
self.push(&e.stat.ctime.nsecs.to_be_bytes(), "entries"); | ||
self.push(&e.stat.mtime.secs.to_be_bytes(), "entries"); | ||
self.push(&e.stat.mtime.nsecs.to_be_bytes(), "entries"); | ||
self.push(&e.stat.dev.to_be_bytes(), "entries"); | ||
self.push(&e.stat.ino.to_be_bytes(), "entries"); | ||
self.push(&e.mode.bits().to_be_bytes(), "entries"); | ||
self.push(&e.stat.uid.to_be_bytes(), "entries"); | ||
self.push(&e.stat.gid.to_be_bytes(), "entries"); | ||
self.push(&e.stat.size.to_be_bytes(), "entries"); | ||
self.push(e.id.as_bytes(), "entries"); | ||
//FIXME: correct flag values | ||
// probably convert 'in-memory' Flags to at_rest::Flags | ||
// self.push(&e.flags.bits().to_be_bytes(), "entries"); | ||
self.push(b"\x00\x01\x61\x00", "entries"); | ||
|
||
println!("{:?}", e.flags.bits()); | ||
} | ||
} | ||
|
||
fn tree(&mut self, tree: &extension::Tree) { | ||
let signature = b"TREE"; | ||
let mut size: u32 = 0; | ||
|
||
self.push(signature, "tree"); | ||
self.push(&size.to_be_bytes(), "tree"); | ||
|
||
self.tree_entry(tree); | ||
|
||
if let Some(range) = self.index_table.get("tree") { | ||
size = (range.end - (range.start + 8)) as u32; | ||
self.data[range.start + 4..range.start + 8].copy_from_slice(&size.to_be_bytes()); | ||
} | ||
} | ||
|
||
fn tree_entry(&mut self, tree: &extension::Tree) { | ||
let path = [tree.name.as_slice(), b"\0"].concat(); | ||
|
||
let num_entries_ascii = tree.num_entries.to_string(); | ||
let num_children_ascii = tree.children.len().to_string(); | ||
|
||
self.push(path.as_slice(), "tree"); | ||
self.push(num_entries_ascii.as_bytes(), "tree"); | ||
self.push(b" ", "tree"); | ||
self.push(num_children_ascii.as_bytes(), "tree"); | ||
self.push(b"\n", "tree"); | ||
self.push(tree.id.as_bytes(), "tree"); | ||
|
||
for child in &tree.children { | ||
self.tree_entry(child); | ||
} | ||
} | ||
|
||
fn end_of_index(&mut self) { | ||
match self.index_table.get("entries") { | ||
Some(range) => { | ||
let signature = b"EOIE"; | ||
let extension_size = 4 + self.options.hash_kind.len_in_bytes() as u32; | ||
let offset: u32 = range.end as u32; | ||
|
||
let mut hasher = git_features::hash::hasher(self.options.hash_kind); | ||
|
||
match self.index_table.get("tree") { | ||
Some(range) => { | ||
hasher.update(b"TREE"); | ||
hasher.update(&self.data[range.start + 4..range.start + 8]); | ||
} | ||
None => {} | ||
} | ||
|
||
let hash = hasher.digest(); | ||
|
||
self.data.push_str(signature); | ||
self.data.push_str(extension_size.to_be_bytes()); | ||
self.data.push_str(offset.to_be_bytes()); | ||
self.data.push_str(hash); | ||
} | ||
None => {} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,212 +1,2 @@ | ||
mod init { | ||
use std::path::{Path, PathBuf}; | ||
|
||
use bstr::ByteSlice; | ||
use git_index::{entry, Version}; | ||
use git_testtools::hex_to_id; | ||
|
||
fn verify(index: git_index::File) -> git_index::File { | ||
index.verify_integrity().unwrap(); | ||
index.verify_entries().unwrap(); | ||
index | ||
.verify_extensions(false, git_index::verify::extensions::no_find) | ||
.unwrap(); | ||
index | ||
} | ||
|
||
fn loose_file(name: &str) -> git_index::File { | ||
let path = git_testtools::fixture_path(Path::new("loose_index").join(name).with_extension("git-index")); | ||
let file = git_index::File::at(path, git_index::decode::Options::default()).unwrap(); | ||
verify(file) | ||
} | ||
fn file(name: &str) -> git_index::File { | ||
let file = git_index::File::at(crate::fixture_path(name), git_index::decode::Options::default()).unwrap(); | ||
verify(file) | ||
} | ||
fn file_opt(name: &str, opts: git_index::decode::Options) -> git_index::File { | ||
let file = git_index::File::at(crate::fixture_path(name), opts).unwrap(); | ||
verify(file) | ||
} | ||
|
||
#[test] | ||
fn read_v2_with_single_entry_tree_and_eoie_ext() { | ||
let file_disallow_threaded_loading = file_opt( | ||
"v2", | ||
git_index::decode::Options { | ||
min_extension_block_in_bytes_for_threading: 100000, | ||
..Default::default() | ||
}, | ||
); | ||
for file in [file("v2"), file_disallow_threaded_loading] { | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert_eq!(file.entries().len(), 1); | ||
|
||
let entry = &file.entries()[0]; | ||
assert_eq!(entry.id, hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); | ||
assert!(entry.flags.is_empty()); | ||
assert_eq!(entry.mode, entry::Mode::FILE); | ||
assert_eq!(entry.path(&file.state), "a"); | ||
|
||
let tree = file.tree().unwrap(); | ||
assert_eq!(tree.num_entries, 1); | ||
assert_eq!(tree.id, hex_to_id("496d6428b9cf92981dc9495211e6e1120fb6f2ba")); | ||
assert!(tree.name.is_empty()); | ||
assert!(tree.children.is_empty()); | ||
} | ||
} | ||
#[test] | ||
fn read_v2_empty() { | ||
let file = file("V2_empty"); | ||
assert_eq!(file.version(), Version::V2); | ||
assert_eq!(file.entries().len(), 0); | ||
let tree = file.tree().unwrap(); | ||
assert_eq!(tree.num_entries, 0); | ||
assert!(tree.name.is_empty()); | ||
assert!(tree.children.is_empty()); | ||
assert_eq!(tree.id, hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904")); | ||
} | ||
|
||
#[test] | ||
fn read_v2_with_multiple_entries_without_eoie_ext() { | ||
let file = file("v2_more_files"); | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert_eq!(file.entries().len(), 6); | ||
for (idx, path) in ["a", "b", "c", "d/a", "d/b", "d/c"].iter().enumerate() { | ||
let e = &file.entries()[idx]; | ||
assert_eq!(e.path(&file), path); | ||
assert!(e.flags.is_empty()); | ||
assert_eq!(e.mode, entry::Mode::FILE); | ||
assert_eq!(e.id, hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")) | ||
} | ||
|
||
let tree = file.tree().unwrap(); | ||
assert_eq!(tree.id, hex_to_id("c9b29c3168d8e677450cc650238b23d9390801fb")); | ||
assert_eq!(tree.num_entries, 6); | ||
assert!(tree.name.is_empty()); | ||
assert_eq!(tree.children.len(), 1); | ||
|
||
let tree = &tree.children[0]; | ||
assert_eq!(tree.id, hex_to_id("765b32c65d38f04c4f287abda055818ec0f26912")); | ||
assert_eq!(tree.num_entries, 3); | ||
assert_eq!(tree.name.as_bstr(), "d"); | ||
} | ||
|
||
fn find_shared_index_for(index: impl AsRef<Path>) -> PathBuf { | ||
let mut matches = std::fs::read_dir(index.as_ref().parent().unwrap()) | ||
.unwrap() | ||
.map(Result::unwrap) | ||
.filter(|e: &std::fs::DirEntry| e.file_name().into_string().unwrap().starts_with("sharedindex.")); | ||
let res = matches.next().unwrap(); | ||
assert!(matches.next().is_none(), "found more than one shared indices"); | ||
res.path() | ||
} | ||
|
||
#[test] | ||
fn read_split_index_without_any_extension() { | ||
let file = git_index::File::at( | ||
find_shared_index_for(crate::fixture_path("v2_split_index")), | ||
git_index::decode::Options::default(), | ||
) | ||
.unwrap(); | ||
assert_eq!(file.version(), Version::V2); | ||
} | ||
|
||
#[test] | ||
fn read_v2_split_index() { | ||
let file = file("v2_split_index"); | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert!(file.link().is_some()); | ||
} | ||
|
||
#[test] | ||
fn read_v3_extended_flags() { | ||
let file = loose_file("extended-flags"); | ||
assert_eq!(file.version(), Version::V3); | ||
} | ||
|
||
#[test] | ||
fn read_v2_very_long_path() { | ||
let file = loose_file("very-long-path"); | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert_eq!(file.state.entries().len(), 9); | ||
assert_eq!( | ||
file.state.entries()[0].path(&file.state), | ||
std::iter::repeat('a') | ||
.take(4096) | ||
.chain(std::iter::once('q')) | ||
.collect::<String>() | ||
); | ||
} | ||
|
||
#[test] | ||
fn read_reuc_extension() { | ||
let file = loose_file("REUC"); | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert!(file.resolve_undo().is_some()); | ||
} | ||
|
||
#[test] | ||
fn read_untr_extension() { | ||
let file = loose_file("UNTR"); | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert!(file.untracked().is_some()); | ||
} | ||
|
||
#[test] | ||
fn read_untr_extension_with_oids() { | ||
let file = loose_file("UNTR-with-oids"); | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert!(file.untracked().is_some()); | ||
} | ||
|
||
#[test] | ||
fn read_fsmn_v1() { | ||
let file = loose_file("FSMN"); | ||
assert_eq!(file.version(), Version::V2); | ||
|
||
assert!(file.fs_monitor().is_some()); | ||
} | ||
|
||
#[test] | ||
fn read_file_with_conflicts() { | ||
let file = loose_file("conflicting-file"); | ||
assert_eq!(file.version(), Version::V2); | ||
assert_eq!(file.entries().len(), 3); | ||
} | ||
|
||
#[test] | ||
fn read_v4_with_delta_paths_and_ieot_ext() { | ||
let file = file("v4_more_files_IEOT"); | ||
assert_eq!(file.version(), Version::V4); | ||
|
||
assert_eq!(file.entries().len(), 10); | ||
for (idx, path) in [ | ||
"a", | ||
"b", | ||
"c", | ||
"d/a", | ||
"d/b", | ||
"d/c", | ||
"d/last/123", | ||
"d/last/34", | ||
"d/last/6", | ||
"x", | ||
] | ||
.iter() | ||
.enumerate() | ||
{ | ||
let e = &file.entries()[idx]; | ||
assert_eq!(e.path(&file), path); | ||
assert!(e.flags.is_empty()); | ||
assert_eq!(e.mode, entry::Mode::FILE); | ||
assert_eq!(e.id, hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")) | ||
} | ||
} | ||
} | ||
mod read; | ||
mod write; |
Oops, something went wrong.