parsers.rs
473 lines
| 14.7 KiB
| application/rls-services+xml
|
RustLexer
|
r42617 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||
// | ||||
// This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | ||||
|
r47168 | use crate::errors::HgError; | ||
|
r47872 | use crate::utils::hg_path::{HgPath, HgPathBuf}; | ||
|
r42828 | use crate::{ | ||
|
r47336 | dirstate::{CopyMap, EntryState, RawEntry, StateMap}, | ||
|
r47169 | DirstateEntry, DirstateParents, | ||
|
r42828 | }; | ||
|
r47336 | use byteorder::{BigEndian, WriteBytesExt}; | ||
use bytes_cast::BytesCast; | ||||
|
r45028 | use micro_timer::timed; | ||
|
r42994 | use std::convert::{TryFrom, TryInto}; | ||
|
r42617 | |||
/// Parents are stored in the dirstate as byte hashes. | ||||
|
r42993 | pub const PARENT_SIZE: usize = 20; | ||
|
r42617 | /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits. | ||
const MIN_ENTRY_SIZE: usize = 17; | ||||
|
r45916 | type ParseResult<'a> = ( | ||
|
r47336 | &'a DirstateParents, | ||
|
r45916 | Vec<(&'a HgPath, DirstateEntry)>, | ||
Vec<(&'a HgPath, &'a HgPath)>, | ||||
); | ||||
|
r42993 | |||
|
r47343 | pub fn parse_dirstate_parents( | ||
contents: &[u8], | ||||
) -> Result<&DirstateParents, HgError> { | ||||
let (parents, _rest) = DirstateParents::from_bytes(contents) | ||||
.map_err(|_| HgError::corrupted("Too little data for dirstate."))?; | ||||
Ok(parents) | ||||
} | ||||
|
r45028 | #[timed] | ||
|
r47867 | pub fn parse_dirstate(contents: &[u8]) -> Result<ParseResult, HgError> { | ||
|
r47336 | let mut copies = Vec::new(); | ||
let mut entries = Vec::new(); | ||||
|
r47867 | let parents = | ||
parse_dirstate_entries(contents, |path, entry, copy_source| { | ||||
if let Some(source) = copy_source { | ||||
copies.push((path, source)); | ||||
} | ||||
entries.push((path, *entry)); | ||||
})?; | ||||
Ok((parents, entries, copies)) | ||||
} | ||||
|
r42617 | |||
|
r47867 | pub fn parse_dirstate_entries<'a>( | ||
mut contents: &'a [u8], | ||||
mut each_entry: impl FnMut(&'a HgPath, &DirstateEntry, Option<&'a HgPath>), | ||||
) -> Result<&'a DirstateParents, HgError> { | ||||
|
r47336 | let (parents, rest) = DirstateParents::from_bytes(contents) | ||
.map_err(|_| HgError::corrupted("Too little data for dirstate."))?; | ||||
contents = rest; | ||||
while !contents.is_empty() { | ||||
let (raw_entry, rest) = RawEntry::from_bytes(contents) | ||||
.map_err(|_| HgError::corrupted("Overflow in dirstate."))?; | ||||
|
r42617 | |||
|
r47336 | let entry = DirstateEntry { | ||
state: EntryState::try_from(raw_entry.state)?, | ||||
mode: raw_entry.mode.get(), | ||||
mtime: raw_entry.mtime.get(), | ||||
size: raw_entry.size.get(), | ||||
}; | ||||
let (paths, rest) = | ||||
u8::slice_from_bytes(rest, raw_entry.length.get() as usize) | ||||
.map_err(|_| HgError::corrupted("Overflow in dirstate."))?; | ||||
|
r42617 | |||
|
r47336 | // `paths` is either a single path, or two paths separated by a NULL | ||
// byte | ||||
let mut iter = paths.splitn(2, |&byte| byte == b'\0'); | ||||
let path = HgPath::new( | ||||
iter.next().expect("splitn always yields at least one item"), | ||||
); | ||||
|
r47867 | let copy_source = iter.next().map(HgPath::new); | ||
each_entry(path, &entry, copy_source); | ||||
|
r42617 | |||
|
r47336 | contents = rest; | ||
|
r42617 | } | ||
|
r47867 | Ok(parents) | ||
|
r42617 | } | ||
|
r47872 | fn packed_filename_and_copy_source_size( | ||
filename: &HgPathBuf, | ||||
copy_source: Option<&HgPathBuf>, | ||||
) -> usize { | ||||
filename.len() | ||||
+ if let Some(source) = copy_source { | ||||
b"\0".len() + source.len() | ||||
} else { | ||||
0 | ||||
} | ||||
} | ||||
pub fn packed_entry_size( | ||||
filename: &HgPathBuf, | ||||
copy_source: Option<&HgPathBuf>, | ||||
) -> usize { | ||||
MIN_ENTRY_SIZE | ||||
+ packed_filename_and_copy_source_size(filename, copy_source) | ||||
} | ||||
pub fn pack_entry( | ||||
filename: &HgPathBuf, | ||||
entry: &DirstateEntry, | ||||
copy_source: Option<&HgPathBuf>, | ||||
packed: &mut Vec<u8>, | ||||
) { | ||||
let length = packed_filename_and_copy_source_size(filename, copy_source); | ||||
// Unwrapping because `impl std::io::Write for Vec<u8>` never errors | ||||
packed.write_u8(entry.state.into()).unwrap(); | ||||
packed.write_i32::<BigEndian>(entry.mode).unwrap(); | ||||
packed.write_i32::<BigEndian>(entry.size).unwrap(); | ||||
packed.write_i32::<BigEndian>(entry.mtime).unwrap(); | ||||
packed.write_i32::<BigEndian>(length as i32).unwrap(); | ||||
packed.extend(filename.as_bytes()); | ||||
if let Some(source) = copy_source { | ||||
packed.push(b'\0'); | ||||
packed.extend(source.as_bytes()); | ||||
} | ||||
} | ||||
|
r47871 | /// Seconds since the Unix epoch | ||
pub struct Timestamp(pub u64); | ||||
|
r47872 | pub fn clear_ambiguous_mtime( | ||
entry: &mut DirstateEntry, | ||||
mtime_now: i32, | ||||
) -> bool { | ||||
let ambiguous = | ||||
entry.state == EntryState::Normal && entry.mtime == mtime_now; | ||||
if ambiguous { | ||||
// The file was last modified "simultaneously" with the current | ||||
// write to dirstate (i.e. within the same second for file- | ||||
// systems with a granularity of 1 sec). This commonly happens | ||||
// for at least a couple of files on 'update'. | ||||
// The user could change the file without changing its size | ||||
// within the same second. Invalidate the file's mtime in | ||||
// dirstate, forcing future 'status' calls to compare the | ||||
// contents of the file if the size is the same. This prevents | ||||
// mistakenly treating such files as clean. | ||||
entry.mtime = -1; | ||||
} | ||||
ambiguous | ||||
} | ||||
|
r42617 | pub fn pack_dirstate( | ||
|
r42993 | state_map: &mut StateMap, | ||
copy_map: &CopyMap, | ||||
|
r42617 | parents: DirstateParents, | ||
|
r47871 | now: Timestamp, | ||
|
r47168 | ) -> Result<Vec<u8>, HgError> { | ||
|
r42993 | // TODO move away from i32 before 2038. | ||
|
r47871 | let now: i32 = now.0.try_into().expect("time overflow"); | ||
|
r42617 | |||
|
r42993 | let expected_size: usize = state_map | ||
|
r42617 | .iter() | ||
|
r42993 | .map(|(filename, _)| { | ||
|
r47872 | packed_entry_size(filename, copy_map.get(filename)) | ||
|
r42617 | }) | ||
.sum(); | ||||
let expected_size = expected_size + PARENT_SIZE * 2; | ||||
let mut packed = Vec::with_capacity(expected_size); | ||||
|
r47337 | packed.extend(parents.p1.as_bytes()); | ||
packed.extend(parents.p2.as_bytes()); | ||||
|
r42617 | |||
|
r46223 | for (filename, entry) in state_map.iter_mut() { | ||
|
r47872 | clear_ambiguous_mtime(entry, now); | ||
pack_entry(filename, entry, copy_map.get(filename), &mut packed) | ||||
|
r42617 | } | ||
if packed.len() != expected_size { | ||||
|
r47168 | return Err(HgError::CorruptedRepository(format!( | ||
"bad dirstate size: {} != {}", | ||||
expected_size, | ||||
packed.len() | ||||
))); | ||||
|
r42617 | } | ||
|
r42993 | Ok(packed) | ||
|
r42617 | } | ||
#[cfg(test)] | ||||
mod tests { | ||||
use super::*; | ||||
|
r44278 | use crate::{utils::hg_path::HgPathBuf, FastHashMap}; | ||
|
r46185 | use pretty_assertions::assert_eq; | ||
|
r42617 | |||
#[test] | ||||
fn test_pack_dirstate_empty() { | ||||
|
r46185 | let mut state_map = StateMap::default(); | ||
|
r44278 | let copymap = FastHashMap::default(); | ||
|
r42617 | let parents = DirstateParents { | ||
|
r47337 | p1: b"12345678910111213141".into(), | ||
p2: b"00000000000000000000".into(), | ||||
|
r42617 | }; | ||
|
r47871 | let now = Timestamp(15000000); | ||
|
r42993 | let expected = b"1234567891011121314100000000000000000000".to_vec(); | ||
|
r42617 | |||
assert_eq!( | ||||
expected, | ||||
|
r42993 | pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | ||
|
r42617 | ); | ||
|
r42993 | |||
assert!(state_map.is_empty()) | ||||
|
r42617 | } | ||
#[test] | ||||
fn test_pack_dirstate_one_entry() { | ||||
|
r42993 | let expected_state_map: StateMap = [( | ||
|
r43227 | HgPathBuf::from_bytes(b"f1"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Normal, | ||
|
r42617 | mode: 0o644, | ||
size: 0, | ||||
mtime: 791231220, | ||||
}, | ||||
|
r42993 | )] | ||
.iter() | ||||
.cloned() | ||||
.collect(); | ||||
let mut state_map = expected_state_map.clone(); | ||||
|
r44278 | let copymap = FastHashMap::default(); | ||
|
r42617 | let parents = DirstateParents { | ||
|
r47337 | p1: b"12345678910111213141".into(), | ||
p2: b"00000000000000000000".into(), | ||||
|
r42617 | }; | ||
|
r47871 | let now = Timestamp(15000000); | ||
|
r42993 | let expected = [ | ||
49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49, | ||||
51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, | ||||
48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47, | ||||
41, 58, 244, 0, 0, 0, 2, 102, 49, | ||||
] | ||||
.to_vec(); | ||||
|
r42617 | |||
assert_eq!( | ||||
expected, | ||||
|
r42993 | pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | ||
|
r42617 | ); | ||
|
r42993 | assert_eq!(expected_state_map, state_map); | ||
} | ||||
|
r42617 | #[test] | ||
|
r42993 | fn test_pack_dirstate_one_entry_with_copy() { | ||
let expected_state_map: StateMap = [( | ||||
|
r43227 | HgPathBuf::from_bytes(b"f1"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Normal, | ||
|
r42617 | mode: 0o644, | ||
size: 0, | ||||
mtime: 791231220, | ||||
}, | ||||
|
r42993 | )] | ||
.iter() | ||||
.cloned() | ||||
.collect(); | ||||
let mut state_map = expected_state_map.clone(); | ||||
|
r44278 | let mut copymap = FastHashMap::default(); | ||
|
r43227 | copymap.insert( | ||
HgPathBuf::from_bytes(b"f1"), | ||||
HgPathBuf::from_bytes(b"copyname"), | ||||
); | ||||
|
r42617 | let parents = DirstateParents { | ||
|
r47337 | p1: b"12345678910111213141".into(), | ||
p2: b"00000000000000000000".into(), | ||||
|
r42617 | }; | ||
|
r47871 | let now = Timestamp(15000000); | ||
|
r42993 | let expected = [ | ||
49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49, | ||||
51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, | ||||
48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47, | ||||
41, 58, 244, 0, 0, 0, 11, 102, 49, 0, 99, 111, 112, 121, 110, 97, | ||||
109, 101, | ||||
] | ||||
.to_vec(); | ||||
|
r42617 | |||
assert_eq!( | ||||
|
r42993 | expected, | ||
pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | ||||
); | ||||
assert_eq!(expected_state_map, state_map); | ||||
} | ||||
#[test] | ||||
fn test_parse_pack_one_entry_with_copy() { | ||||
let mut state_map: StateMap = [( | ||||
|
r43227 | HgPathBuf::from_bytes(b"f1"), | ||
|
r42993 | DirstateEntry { | ||
|
r42994 | state: EntryState::Normal, | ||
|
r42993 | mode: 0o644, | ||
size: 0, | ||||
mtime: 791231220, | ||||
}, | ||||
)] | ||||
.iter() | ||||
.cloned() | ||||
.collect(); | ||||
|
r44278 | let mut copymap = FastHashMap::default(); | ||
|
r43227 | copymap.insert( | ||
HgPathBuf::from_bytes(b"f1"), | ||||
HgPathBuf::from_bytes(b"copyname"), | ||||
); | ||||
|
r42993 | let parents = DirstateParents { | ||
|
r47337 | p1: b"12345678910111213141".into(), | ||
p2: b"00000000000000000000".into(), | ||||
|
r42993 | }; | ||
|
r47871 | let now = Timestamp(15000000); | ||
|
r42993 | let result = | ||
pack_dirstate(&mut state_map, ©map, parents.clone(), now) | ||||
.unwrap(); | ||||
|
r45916 | let (new_parents, entries, copies) = | ||
parse_dirstate(result.as_slice()).unwrap(); | ||||
let new_state_map: StateMap = entries | ||||
.into_iter() | ||||
.map(|(path, entry)| (path.to_owned(), entry)) | ||||
.collect(); | ||||
let new_copy_map: CopyMap = copies | ||||
.into_iter() | ||||
.map(|(path, copy)| (path.to_owned(), copy.to_owned())) | ||||
.collect(); | ||||
|
r42993 | assert_eq!( | ||
|
r47336 | (&parents, state_map, copymap), | ||
|
r42993 | (new_parents, new_state_map, new_copy_map) | ||
|
r42617 | ) | ||
} | ||||
#[test] | ||||
fn test_parse_pack_multiple_entries_with_copy() { | ||||
|
r42993 | let mut state_map: StateMap = [ | ||
|
r42617 | ( | ||
|
r43227 | HgPathBuf::from_bytes(b"f1"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Normal, | ||
|
r42617 | mode: 0o644, | ||
size: 0, | ||||
mtime: 791231220, | ||||
}, | ||||
), | ||||
( | ||||
|
r43227 | HgPathBuf::from_bytes(b"f2"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Merged, | ||
|
r42617 | mode: 0o777, | ||
size: 1000, | ||||
mtime: 791231220, | ||||
}, | ||||
), | ||||
( | ||||
|
r43227 | HgPathBuf::from_bytes(b"f3"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Removed, | ||
|
r42617 | mode: 0o644, | ||
size: 234553, | ||||
mtime: 791231220, | ||||
}, | ||||
), | ||||
( | ||||
|
r43227 | HgPathBuf::from_bytes(b"f4\xF6"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Added, | ||
|
r42617 | mode: 0o644, | ||
size: -1, | ||||
mtime: -1, | ||||
}, | ||||
), | ||||
|
r42993 | ] | ||
.iter() | ||||
.cloned() | ||||
.collect(); | ||||
|
r44278 | let mut copymap = FastHashMap::default(); | ||
|
r43227 | copymap.insert( | ||
HgPathBuf::from_bytes(b"f1"), | ||||
HgPathBuf::from_bytes(b"copyname"), | ||||
); | ||||
copymap.insert( | ||||
HgPathBuf::from_bytes(b"f4\xF6"), | ||||
HgPathBuf::from_bytes(b"copyname2"), | ||||
); | ||||
|
r42617 | let parents = DirstateParents { | ||
|
r47337 | p1: b"12345678910111213141".into(), | ||
p2: b"00000000000000000000".into(), | ||||
|
r42617 | }; | ||
|
r47871 | let now = Timestamp(15000000); | ||
|
r42617 | let result = | ||
|
r42993 | pack_dirstate(&mut state_map, ©map, parents.clone(), now) | ||
.unwrap(); | ||||
|
r42617 | |||
|
r45916 | let (new_parents, entries, copies) = | ||
parse_dirstate(result.as_slice()).unwrap(); | ||||
let new_state_map: StateMap = entries | ||||
.into_iter() | ||||
.map(|(path, entry)| (path.to_owned(), entry)) | ||||
.collect(); | ||||
let new_copy_map: CopyMap = copies | ||||
.into_iter() | ||||
.map(|(path, copy)| (path.to_owned(), copy.to_owned())) | ||||
.collect(); | ||||
|
r42617 | assert_eq!( | ||
|
r47336 | (&parents, state_map, copymap), | ||
|
r42993 | (new_parents, new_state_map, new_copy_map) | ||
|
r42617 | ) | ||
} | ||||
#[test] | ||||
/// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4 | ||||
fn test_parse_pack_one_entry_with_copy_and_time_conflict() { | ||||
|
r42993 | let mut state_map: StateMap = [( | ||
|
r43227 | HgPathBuf::from_bytes(b"f1"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Normal, | ||
|
r42617 | mode: 0o644, | ||
size: 0, | ||||
mtime: 15000000, | ||||
}, | ||||
|
r42993 | )] | ||
.iter() | ||||
.cloned() | ||||
.collect(); | ||||
|
r44278 | let mut copymap = FastHashMap::default(); | ||
|
r43227 | copymap.insert( | ||
HgPathBuf::from_bytes(b"f1"), | ||||
HgPathBuf::from_bytes(b"copyname"), | ||||
); | ||||
|
r42617 | let parents = DirstateParents { | ||
|
r47337 | p1: b"12345678910111213141".into(), | ||
p2: b"00000000000000000000".into(), | ||||
|
r42617 | }; | ||
|
r47871 | let now = Timestamp(15000000); | ||
|
r42617 | let result = | ||
|
r42993 | pack_dirstate(&mut state_map, ©map, parents.clone(), now) | ||
.unwrap(); | ||||
|
r45916 | let (new_parents, entries, copies) = | ||
parse_dirstate(result.as_slice()).unwrap(); | ||||
let new_state_map: StateMap = entries | ||||
.into_iter() | ||||
.map(|(path, entry)| (path.to_owned(), entry)) | ||||
.collect(); | ||||
let new_copy_map: CopyMap = copies | ||||
.into_iter() | ||||
.map(|(path, copy)| (path.to_owned(), copy.to_owned())) | ||||
.collect(); | ||||
|
r42617 | |||
assert_eq!( | ||||
( | ||||
|
r47336 | &parents, | ||
|
r42993 | [( | ||
|
r43227 | HgPathBuf::from_bytes(b"f1"), | ||
|
r42617 | DirstateEntry { | ||
|
r42994 | state: EntryState::Normal, | ||
|
r42617 | mode: 0o644, | ||
size: 0, | ||||
mtime: -1 | ||||
} | ||||
|
r42993 | )] | ||
.iter() | ||||
.cloned() | ||||
.collect::<StateMap>(), | ||||
copymap, | ||||
|
r42617 | ), | ||
|
r42993 | (new_parents, new_state_map, new_copy_map) | ||
|
r42617 | ) | ||
} | ||||
} | ||||