Details
Details
- Reviewers
kevincox - Group Reviewers
hg-reviewers - Commits
- rHG7a01778bc7b7: rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf
Diff Detail
Diff Detail
- Repository
- rHG Mercurial
- Lint
Lint Skipped - Unit
Unit Tests Skipped
kevincox |
hg-reviewers |
Lint Skipped |
Unit Tests Skipped |
rust/hg-core/src/utils/files.rs | ||
---|---|---|
56 | I think the rebase fixed the diff. |
Path | Packages | |||
---|---|---|---|---|
M | rust/hg-core/src/dirstate.rs (6 lines) | |||
M | rust/hg-core/src/dirstate/dirs_multiset.rs (105 lines) | |||
M | rust/hg-core/src/dirstate/dirstate_map.rs (37 lines) | |||
M | rust/hg-core/src/dirstate/parsers.rs (58 lines) | |||
M | rust/hg-core/src/filepatterns.rs (30 lines) | |||
M | rust/hg-core/src/lib.rs (3 lines) | |||
M | rust/hg-core/src/utils/files.rs (53 lines) | |||
M | rust/hg-cpython/src/dirstate.rs (7 lines) | |||
M | rust/hg-cpython/src/dirstate/copymap.rs (20 lines) | |||
M | rust/hg-cpython/src/dirstate/dirs_multiset.rs (33 lines) | |||
M | rust/hg-cpython/src/dirstate/dirstate_map.rs (89 lines) | |||
M | rust/hg-cpython/src/exceptions.rs (1 line) | |||
M | rust/hg-cpython/src/filepatterns.rs (31 lines) | |||
M | rust/hg-cpython/src/parsers.rs (18 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
Raphaël Gomès | Sep 1 2019, 2:53 PM |
// dirstate module | // dirstate module | ||||
// | // | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
use crate::DirstateParseError; | use crate::{utils::hg_path::HgPathBuf, DirstateParseError}; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::convert::TryFrom; | use std::convert::TryFrom; | ||||
pub mod dirs_multiset; | pub mod dirs_multiset; | ||||
pub mod dirstate_map; | pub mod dirstate_map; | ||||
pub mod parsers; | pub mod parsers; | ||||
#[derive(Debug, PartialEq, Clone)] | #[derive(Debug, PartialEq, Clone)] | ||||
pub struct DirstateParents { | pub struct DirstateParents { | ||||
pub p1: [u8; 20], | pub p1: [u8; 20], | ||||
pub p2: [u8; 20], | pub p2: [u8; 20], | ||||
} | } | ||||
/// The C implementation uses all signed types. This will be an issue | /// The C implementation uses all signed types. This will be an issue | ||||
/// either when 4GB+ source files are commonplace or in 2038, whichever | /// either when 4GB+ source files are commonplace or in 2038, whichever | ||||
/// comes first. | /// comes first. | ||||
#[derive(Debug, PartialEq, Copy, Clone)] | #[derive(Debug, PartialEq, Copy, Clone)] | ||||
pub struct DirstateEntry { | pub struct DirstateEntry { | ||||
pub state: EntryState, | pub state: EntryState, | ||||
pub mode: i32, | pub mode: i32, | ||||
pub mtime: i32, | pub mtime: i32, | ||||
pub size: i32, | pub size: i32, | ||||
} | } | ||||
pub type StateMap = HashMap<Vec<u8>, DirstateEntry>; | pub type StateMap = HashMap<HgPathBuf, DirstateEntry>; | ||||
pub type CopyMap = HashMap<Vec<u8>, Vec<u8>>; | pub type CopyMap = HashMap<HgPathBuf, HgPathBuf>; | ||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)] | #[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||||
pub enum EntryState { | pub enum EntryState { | ||||
Normal, | Normal, | ||||
Added, | Added, | ||||
Removed, | Removed, | ||||
Merged, | Merged, | ||||
Unknown, | Unknown, |
// dirs_multiset.rs | // dirs_multiset.rs | ||||
// | // | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
//! A multiset of directory names. | //! A multiset of directory names. | ||||
//! | //! | ||||
//! Used to counts the references to directories in a manifest or dirstate. | //! Used to counts the references to directories in a manifest or dirstate. | ||||
use crate::utils::hg_path::{HgPath, HgPathBuf}; | |||||
use crate::{ | use crate::{ | ||||
dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError, | dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError, | ||||
}; | }; | ||||
use std::collections::hash_map::Entry; | use std::collections::hash_map::Entry; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
#[derive(PartialEq, Debug)] | #[derive(PartialEq, Debug)] | ||||
pub struct DirsMultiset { | pub struct DirsMultiset { | ||||
inner: HashMap<Vec<u8>, u32>, | inner: HashMap<HgPathBuf, u32>, | ||||
} | } | ||||
impl DirsMultiset { | impl DirsMultiset { | ||||
/// Initializes the multiset from a dirstate. | /// Initializes the multiset from a dirstate. | ||||
/// | /// | ||||
/// If `skip_state` is provided, skips dirstate entries with equal state. | /// If `skip_state` is provided, skips dirstate entries with equal state. | ||||
pub fn from_dirstate( | pub fn from_dirstate( | ||||
vec: &HashMap<Vec<u8>, DirstateEntry>, | vec: &HashMap<HgPathBuf, DirstateEntry>, | ||||
skip_state: Option<EntryState>, | skip_state: Option<EntryState>, | ||||
) -> Self { | ) -> Self { | ||||
let mut multiset = DirsMultiset { | let mut multiset = DirsMultiset { | ||||
inner: HashMap::new(), | inner: HashMap::new(), | ||||
}; | }; | ||||
for (filename, DirstateEntry { state, .. }) in vec { | for (filename, DirstateEntry { state, .. }) in vec { | ||||
// This `if` is optimized out of the loop | // This `if` is optimized out of the loop | ||||
if let Some(skip) = skip_state { | if let Some(skip) = skip_state { | ||||
if skip != *state { | if skip != *state { | ||||
multiset.add_path(filename); | multiset.add_path(filename); | ||||
} | } | ||||
} else { | } else { | ||||
multiset.add_path(filename); | multiset.add_path(filename); | ||||
} | } | ||||
} | } | ||||
multiset | multiset | ||||
} | } | ||||
/// Initializes the multiset from a manifest. | /// Initializes the multiset from a manifest. | ||||
pub fn from_manifest(vec: &Vec<Vec<u8>>) -> Self { | pub fn from_manifest(vec: &Vec<HgPathBuf>) -> Self { | ||||
let mut multiset = DirsMultiset { | let mut multiset = DirsMultiset { | ||||
inner: HashMap::new(), | inner: HashMap::new(), | ||||
}; | }; | ||||
for filename in vec { | for filename in vec { | ||||
multiset.add_path(filename); | multiset.add_path(filename); | ||||
} | } | ||||
multiset | multiset | ||||
} | } | ||||
/// Increases the count of deepest directory contained in the path. | /// Increases the count of deepest directory contained in the path. | ||||
/// | /// | ||||
/// If the directory is not yet in the map, adds its parents. | /// If the directory is not yet in the map, adds its parents. | ||||
pub fn add_path(&mut self, path: &[u8]) { | pub fn add_path(&mut self, path: &HgPath) { | ||||
for subpath in files::find_dirs(path) { | for subpath in files::find_dirs(path) { | ||||
if let Some(val) = self.inner.get_mut(subpath) { | if let Some(val) = self.inner.get_mut(subpath) { | ||||
*val += 1; | *val += 1; | ||||
break; | break; | ||||
} | } | ||||
self.inner.insert(subpath.to_owned(), 1); | self.inner.insert(subpath.to_owned(), 1); | ||||
} | } | ||||
} | } | ||||
/// Decreases the count of deepest directory contained in the path. | /// Decreases the count of deepest directory contained in the path. | ||||
/// | /// | ||||
/// If it is the only reference, decreases all parents until one is | /// If it is the only reference, decreases all parents until one is | ||||
/// removed. | /// removed. | ||||
/// If the directory is not in the map, something horrible has happened. | /// If the directory is not in the map, something horrible has happened. | ||||
pub fn delete_path( | pub fn delete_path( | ||||
&mut self, | &mut self, | ||||
path: &[u8], | path: &HgPath, | ||||
) -> Result<(), DirstateMapError> { | ) -> Result<(), DirstateMapError> { | ||||
for subpath in files::find_dirs(path) { | for subpath in files::find_dirs(path) { | ||||
match self.inner.entry(subpath.to_owned()) { | match self.inner.entry(subpath.to_owned()) { | ||||
Entry::Occupied(mut entry) => { | Entry::Occupied(mut entry) => { | ||||
let val = entry.get().clone(); | let val = entry.get().clone(); | ||||
if val > 1 { | if val > 1 { | ||||
entry.insert(val - 1); | entry.insert(val - 1); | ||||
break; | break; | ||||
} | } | ||||
entry.remove(); | entry.remove(); | ||||
} | } | ||||
Entry::Vacant(_) => { | Entry::Vacant(_) => { | ||||
return Err(DirstateMapError::PathNotFound( | return Err(DirstateMapError::PathNotFound( | ||||
path.to_owned(), | path.to_owned(), | ||||
)) | )) | ||||
} | } | ||||
}; | }; | ||||
} | } | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
pub fn contains(&self, key: &[u8]) -> bool { | pub fn contains(&self, key: &HgPath) -> bool { | ||||
self.inner.contains_key(key) | self.inner.contains_key(key) | ||||
} | } | ||||
pub fn iter(&self) -> impl Iterator<Item = &Vec<u8>> { | pub fn iter(&self) -> impl Iterator<Item = &HgPathBuf> { | ||||
self.inner.keys() | self.inner.keys() | ||||
} | } | ||||
pub fn len(&self) -> usize { | pub fn len(&self) -> usize { | ||||
self.inner.len() | self.inner.len() | ||||
} | } | ||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::*; | use super::*; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
#[test] | #[test] | ||||
fn test_delete_path_path_not_found() { | fn test_delete_path_path_not_found() { | ||||
let mut map = DirsMultiset::from_manifest(&vec![]); | let mut map = DirsMultiset::from_manifest(&vec![]); | ||||
let path = b"doesnotexist/"; | let path = HgPathBuf::from_bytes(b"doesnotexist/"); | ||||
assert_eq!( | assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(path.to_vec())), | Err(DirstateMapError::PathNotFound(path.to_owned())), | ||||
map.delete_path(path) | map.delete_path(&path) | ||||
); | ); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_delete_path_empty_path() { | fn test_delete_path_empty_path() { | ||||
let mut map = DirsMultiset::from_manifest(&vec![vec![]]); | let mut map = DirsMultiset::from_manifest(&vec![HgPathBuf::new()]); | ||||
let path = b""; | let path = HgPath::new(b""); | ||||
assert_eq!(Ok(()), map.delete_path(path)); | assert_eq!(Ok(()), map.delete_path(path)); | ||||
assert_eq!( | assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(path.to_vec())), | Err(DirstateMapError::PathNotFound(path.to_owned())), | ||||
map.delete_path(path) | map.delete_path(path) | ||||
); | ); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_delete_path_successful() { | fn test_delete_path_successful() { | ||||
let mut map = DirsMultiset { | let mut map = DirsMultiset { | ||||
inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)] | inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)] | ||||
.iter() | .iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||||
.collect(), | .collect(), | ||||
}; | }; | ||||
assert_eq!(Ok(()), map.delete_path(b"a/b/")); | assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/"))); | ||||
assert_eq!(Ok(()), map.delete_path(b"a/b/")); | eprintln!("{:?}", map); | ||||
assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/"))); | |||||
eprintln!("{:?}", map); | |||||
assert_eq!( | assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(b"a/b/".to_vec())), | Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes( | ||||
map.delete_path(b"a/b/") | b"a/b/" | ||||
))), | |||||
map.delete_path(HgPath::new(b"a/b/")) | |||||
); | ); | ||||
assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap()); | assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap()); | assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap()); | ||||
eprintln!("{:?}", map); | eprintln!("{:?}", map); | ||||
assert_eq!(Ok(()), map.delete_path(b"a/")); | assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/"))); | ||||
eprintln!("{:?}", map); | eprintln!("{:?}", map); | ||||
assert_eq!(Ok(()), map.delete_path(b"a/c/")); | assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/c/"))); | ||||
assert_eq!( | assert_eq!( | ||||
Err(DirstateMapError::PathNotFound(b"a/c/".to_vec())), | Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes( | ||||
map.delete_path(b"a/c/") | b"a/c/" | ||||
))), | |||||
map.delete_path(HgPath::new(b"a/c/")) | |||||
); | ); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_add_path_empty_path() { | fn test_add_path_empty_path() { | ||||
let mut map = DirsMultiset::from_manifest(&vec![]); | let mut map = DirsMultiset::from_manifest(&vec![]); | ||||
let path = b""; | let path = HgPath::new(b""); | ||||
map.add_path(path); | map.add_path(path); | ||||
assert_eq!(1, map.len()); | assert_eq!(1, map.len()); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_add_path_successful() { | fn test_add_path_successful() { | ||||
let mut map = DirsMultiset::from_manifest(&vec![]); | let mut map = DirsMultiset::from_manifest(&vec![]); | ||||
map.add_path(b"a/"); | map.add_path(HgPath::new(b"a/")); | ||||
assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap()); | assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(1, *map.inner.get(&Vec::new()).unwrap()); | assert_eq!(1, *map.inner.get(HgPath::new(b"")).unwrap()); | ||||
assert_eq!(2, map.len()); | assert_eq!(2, map.len()); | ||||
// Non directory should be ignored | // Non directory should be ignored | ||||
map.add_path(b"a"); | map.add_path(HgPath::new(b"a")); | ||||
assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap()); | assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(2, map.len()); | assert_eq!(2, map.len()); | ||||
// Non directory will still add its base | // Non directory will still add its base | ||||
map.add_path(b"a/b"); | map.add_path(HgPath::new(b"a/b")); | ||||
assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap()); | assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(2, map.len()); | assert_eq!(2, map.len()); | ||||
// Duplicate path works | // Duplicate path works | ||||
map.add_path(b"a/"); | map.add_path(HgPath::new(b"a/")); | ||||
assert_eq!(3, *map.inner.get(&b"a".to_vec()).unwrap()); | assert_eq!(3, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
// Nested dir adds to its base | // Nested dir adds to its base | ||||
map.add_path(b"a/b/"); | map.add_path(HgPath::new(b"a/b/")); | ||||
assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap()); | assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(1, *map.inner.get(&b"a/b".to_vec()).unwrap()); | assert_eq!(1, *map.inner.get(HgPath::new(b"a/b")).unwrap()); | ||||
// but not its base's base, because it already existed | // but not its base's base, because it already existed | ||||
map.add_path(b"a/b/c/"); | map.add_path(HgPath::new(b"a/b/c/")); | ||||
assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap()); | assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap()); | ||||
assert_eq!(2, *map.inner.get(&b"a/b".to_vec()).unwrap()); | assert_eq!(2, *map.inner.get(HgPath::new(b"a/b")).unwrap()); | ||||
map.add_path(b"a/c/"); | map.add_path(HgPath::new(b"a/c/")); | ||||
assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap()); | assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap()); | ||||
let expected = DirsMultiset { | let expected = DirsMultiset { | ||||
inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)] | inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)] | ||||
.iter() | .iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||||
.collect(), | .collect(), | ||||
}; | }; | ||||
assert_eq!(map, expected); | assert_eq!(map, expected); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_dirsmultiset_new_empty() { | fn test_dirsmultiset_new_empty() { | ||||
let new = DirsMultiset::from_manifest(&vec![]); | let new = DirsMultiset::from_manifest(&vec![]); | ||||
let expected = DirsMultiset { | let expected = DirsMultiset { | ||||
inner: HashMap::new(), | inner: HashMap::new(), | ||||
}; | }; | ||||
assert_eq!(expected, new); | assert_eq!(expected, new); | ||||
let new = DirsMultiset::from_dirstate(&HashMap::new(), None); | let new = DirsMultiset::from_dirstate(&HashMap::new(), None); | ||||
let expected = DirsMultiset { | let expected = DirsMultiset { | ||||
inner: HashMap::new(), | inner: HashMap::new(), | ||||
}; | }; | ||||
assert_eq!(expected, new); | assert_eq!(expected, new); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_dirsmultiset_new_no_skip() { | fn test_dirsmultiset_new_no_skip() { | ||||
let input_vec = ["a/", "b/", "a/c", "a/d/"] | let input_vec = ["a/", "b/", "a/c", "a/d/"] | ||||
.iter() | .iter() | ||||
.map(|e| e.as_bytes().to_vec()) | .map(|e| HgPathBuf::from_bytes(e.as_bytes())) | ||||
.collect(); | .collect(); | ||||
let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | ||||
.iter() | .iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||||
.collect(); | .collect(); | ||||
let new = DirsMultiset::from_manifest(&input_vec); | let new = DirsMultiset::from_manifest(&input_vec); | ||||
let expected = DirsMultiset { | let expected = DirsMultiset { | ||||
inner: expected_inner, | inner: expected_inner, | ||||
}; | }; | ||||
assert_eq!(expected, new); | assert_eq!(expected, new); | ||||
let input_map = ["a/", "b/", "a/c", "a/d/"] | let input_map = ["a/", "b/", "a/c", "a/d/"] | ||||
.iter() | .iter() | ||||
.map(|f| { | .map(|f| { | ||||
( | ( | ||||
f.as_bytes().to_vec(), | HgPathBuf::from_bytes(f.as_bytes()), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 0, | mode: 0, | ||||
mtime: 0, | mtime: 0, | ||||
size: 0, | size: 0, | ||||
}, | }, | ||||
) | ) | ||||
}) | }) | ||||
.collect(); | .collect(); | ||||
let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)] | ||||
.iter() | .iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||||
.collect(); | .collect(); | ||||
let new = DirsMultiset::from_dirstate(&input_map, None); | let new = DirsMultiset::from_dirstate(&input_map, None); | ||||
let expected = DirsMultiset { | let expected = DirsMultiset { | ||||
inner: expected_inner, | inner: expected_inner, | ||||
}; | }; | ||||
assert_eq!(expected, new); | assert_eq!(expected, new); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_dirsmultiset_new_skip() { | fn test_dirsmultiset_new_skip() { | ||||
let input_map = [ | let input_map = [ | ||||
("a/", EntryState::Normal), | ("a/", EntryState::Normal), | ||||
("a/b/", EntryState::Normal), | ("a/b/", EntryState::Normal), | ||||
("a/c", EntryState::Removed), | ("a/c", EntryState::Removed), | ||||
("a/d/", EntryState::Merged), | ("a/d/", EntryState::Merged), | ||||
] | ] | ||||
.iter() | .iter() | ||||
.map(|(f, state)| { | .map(|(f, state)| { | ||||
( | ( | ||||
f.as_bytes().to_vec(), | HgPathBuf::from_bytes(f.as_bytes()), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: *state, | state: *state, | ||||
mode: 0, | mode: 0, | ||||
mtime: 0, | mtime: 0, | ||||
size: 0, | size: 0, | ||||
}, | }, | ||||
) | ) | ||||
}) | }) | ||||
.collect(); | .collect(); | ||||
// "a" incremented with "a/c" and "a/d/" | // "a" incremented with "a/c" and "a/d/" | ||||
let expected_inner = [("", 1), ("a", 2), ("a/d", 1)] | let expected_inner = [("", 1), ("a", 2), ("a/d", 1)] | ||||
.iter() | .iter() | ||||
.map(|(k, v)| (k.as_bytes().to_vec(), *v)) | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | ||||
.collect(); | .collect(); | ||||
let new = | let new = | ||||
DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal)); | DirsMultiset::from_dirstate(&input_map, Some(EntryState::Normal)); | ||||
let expected = DirsMultiset { | let expected = DirsMultiset { | ||||
inner: expected_inner, | inner: expected_inner, | ||||
}; | }; | ||||
assert_eq!(expected, new); | assert_eq!(expected, new); | ||||
} | } | ||||
} | } |
// dirstate_map.rs | // dirstate_map.rs | ||||
// | // | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
use crate::utils::hg_path::{HgPath, HgPathBuf}; | |||||
use crate::{ | use crate::{ | ||||
dirstate::{parsers::PARENT_SIZE, EntryState}, | dirstate::{parsers::PARENT_SIZE, EntryState}, | ||||
pack_dirstate, parse_dirstate, CopyMap, DirsMultiset, DirstateEntry, | pack_dirstate, parse_dirstate, CopyMap, DirsMultiset, DirstateEntry, | ||||
DirstateError, DirstateMapError, DirstateParents, DirstateParseError, | DirstateError, DirstateMapError, DirstateParents, DirstateParseError, | ||||
StateMap, | StateMap, | ||||
}; | }; | ||||
use core::borrow::Borrow; | use core::borrow::Borrow; | ||||
use std::collections::{HashMap, HashSet}; | use std::collections::{HashMap, HashSet}; | ||||
use std::convert::TryInto; | use std::convert::TryInto; | ||||
use std::iter::FromIterator; | use std::iter::FromIterator; | ||||
use std::ops::Deref; | use std::ops::Deref; | ||||
use std::time::Duration; | use std::time::Duration; | ||||
pub type FileFoldMap = HashMap<Vec<u8>, Vec<u8>>; | pub type FileFoldMap = HashMap<HgPathBuf, HgPathBuf>; | ||||
const NULL_ID: [u8; 20] = [0; 20]; | const NULL_ID: [u8; 20] = [0; 20]; | ||||
const MTIME_UNSET: i32 = -1; | const MTIME_UNSET: i32 = -1; | ||||
const SIZE_DIRTY: i32 = -2; | const SIZE_DIRTY: i32 = -2; | ||||
#[derive(Default)] | #[derive(Default)] | ||||
pub struct DirstateMap { | pub struct DirstateMap { | ||||
state_map: StateMap, | state_map: StateMap, | ||||
pub copy_map: CopyMap, | pub copy_map: CopyMap, | ||||
file_fold_map: Option<FileFoldMap>, | file_fold_map: Option<FileFoldMap>, | ||||
pub dirs: Option<DirsMultiset>, | pub dirs: Option<DirsMultiset>, | ||||
pub all_dirs: Option<DirsMultiset>, | pub all_dirs: Option<DirsMultiset>, | ||||
non_normal_set: HashSet<Vec<u8>>, | non_normal_set: HashSet<HgPathBuf>, | ||||
other_parent_set: HashSet<Vec<u8>>, | other_parent_set: HashSet<HgPathBuf>, | ||||
parents: Option<DirstateParents>, | parents: Option<DirstateParents>, | ||||
dirty_parents: bool, | dirty_parents: bool, | ||||
} | } | ||||
/// Should only really be used in python interface code, for clarity | /// Should only really be used in python interface code, for clarity | ||||
impl Deref for DirstateMap { | impl Deref for DirstateMap { | ||||
type Target = StateMap; | type Target = StateMap; | ||||
fn deref(&self) -> &Self::Target { | fn deref(&self) -> &Self::Target { | ||||
&self.state_map | &self.state_map | ||||
} | } | ||||
} | } | ||||
impl FromIterator<(Vec<u8>, DirstateEntry)> for DirstateMap { | impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap { | ||||
fn from_iter<I: IntoIterator<Item = (Vec<u8>, DirstateEntry)>>( | fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>( | ||||
iter: I, | iter: I, | ||||
) -> Self { | ) -> Self { | ||||
Self { | Self { | ||||
state_map: iter.into_iter().collect(), | state_map: iter.into_iter().collect(), | ||||
..Self::default() | ..Self::default() | ||||
} | } | ||||
} | } | ||||
} | } | ||||
p1: NULL_ID, | p1: NULL_ID, | ||||
p2: NULL_ID, | p2: NULL_ID, | ||||
}) | }) | ||||
} | } | ||||
/// Add a tracked file to the dirstate | /// Add a tracked file to the dirstate | ||||
pub fn add_file( | pub fn add_file( | ||||
&mut self, | &mut self, | ||||
filename: &[u8], | filename: &HgPath, | ||||
old_state: EntryState, | old_state: EntryState, | ||||
entry: DirstateEntry, | entry: DirstateEntry, | ||||
) { | ) { | ||||
if old_state == EntryState::Unknown || old_state == EntryState::Removed | if old_state == EntryState::Unknown || old_state == EntryState::Removed | ||||
{ | { | ||||
if let Some(ref mut dirs) = self.dirs { | if let Some(ref mut dirs) = self.dirs { | ||||
dirs.add_path(filename) | dirs.add_path(filename) | ||||
} | } | ||||
/// Mark a file as removed in the dirstate. | /// Mark a file as removed in the dirstate. | ||||
/// | /// | ||||
/// The `size` parameter is used to store sentinel values that indicate | /// The `size` parameter is used to store sentinel values that indicate | ||||
/// the file's previous state. In the future, we should refactor this | /// the file's previous state. In the future, we should refactor this | ||||
/// to be more explicit about what that state is. | /// to be more explicit about what that state is. | ||||
pub fn remove_file( | pub fn remove_file( | ||||
&mut self, | &mut self, | ||||
filename: &[u8], | filename: &HgPath, | ||||
old_state: EntryState, | old_state: EntryState, | ||||
size: i32, | size: i32, | ||||
) -> Result<(), DirstateMapError> { | ) -> Result<(), DirstateMapError> { | ||||
if old_state != EntryState::Unknown && old_state != EntryState::Removed | if old_state != EntryState::Unknown && old_state != EntryState::Removed | ||||
{ | { | ||||
if let Some(ref mut dirs) = self.dirs { | if let Some(ref mut dirs) = self.dirs { | ||||
dirs.delete_path(filename)?; | dirs.delete_path(filename)?; | ||||
} | } | ||||
self.non_normal_set.insert(filename.to_owned()); | self.non_normal_set.insert(filename.to_owned()); | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
/// Remove a file from the dirstate. | /// Remove a file from the dirstate. | ||||
/// Returns `true` if the file was previously recorded. | /// Returns `true` if the file was previously recorded. | ||||
pub fn drop_file( | pub fn drop_file( | ||||
&mut self, | &mut self, | ||||
filename: &[u8], | filename: &HgPath, | ||||
old_state: EntryState, | old_state: EntryState, | ||||
) -> Result<bool, DirstateMapError> { | ) -> Result<bool, DirstateMapError> { | ||||
let exists = self.state_map.remove(filename).is_some(); | let exists = self.state_map.remove(filename).is_some(); | ||||
if exists { | if exists { | ||||
if old_state != EntryState::Removed { | if old_state != EntryState::Removed { | ||||
if let Some(ref mut dirs) = self.dirs { | if let Some(ref mut dirs) = self.dirs { | ||||
dirs.delete_path(filename)?; | dirs.delete_path(filename)?; | ||||
} | } | ||||
} | } | ||||
if let Some(ref mut all_dirs) = self.all_dirs { | if let Some(ref mut all_dirs) = self.all_dirs { | ||||
all_dirs.delete_path(filename)?; | all_dirs.delete_path(filename)?; | ||||
} | } | ||||
} | } | ||||
if let Some(ref mut file_fold_map) = self.file_fold_map { | if let Some(ref mut file_fold_map) = self.file_fold_map { | ||||
file_fold_map.remove(&filename.to_ascii_uppercase()); | file_fold_map.remove(&filename.to_ascii_uppercase()); | ||||
} | } | ||||
self.non_normal_set.remove(filename); | self.non_normal_set.remove(filename); | ||||
Ok(exists) | Ok(exists) | ||||
} | } | ||||
pub fn clear_ambiguous_times( | pub fn clear_ambiguous_times( | ||||
&mut self, | &mut self, | ||||
filenames: Vec<Vec<u8>>, | filenames: Vec<HgPathBuf>, | ||||
now: i32, | now: i32, | ||||
) { | ) { | ||||
for filename in filenames { | for filename in filenames { | ||||
let mut changed = false; | let mut changed = false; | ||||
self.state_map | self.state_map | ||||
.entry(filename.to_owned()) | .entry(filename.to_owned()) | ||||
.and_modify(|entry| { | .and_modify(|entry| { | ||||
if entry.state == EntryState::Normal && entry.mtime == now | if entry.state == EntryState::Normal && entry.mtime == now | ||||
{ | { | ||||
changed = true; | changed = true; | ||||
*entry = DirstateEntry { | *entry = DirstateEntry { | ||||
mtime: MTIME_UNSET, | mtime: MTIME_UNSET, | ||||
..*entry | ..*entry | ||||
}; | }; | ||||
} | } | ||||
}); | }); | ||||
if changed { | if changed { | ||||
self.non_normal_set.insert(filename.to_owned()); | self.non_normal_set.insert(filename.to_owned()); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
pub fn non_normal_other_parent_entries( | pub fn non_normal_other_parent_entries( | ||||
&self, | &self, | ||||
) -> (HashSet<Vec<u8>>, HashSet<Vec<u8>>) { | ) -> (HashSet<HgPathBuf>, HashSet<HgPathBuf>) { | ||||
let mut non_normal = HashSet::new(); | let mut non_normal = HashSet::new(); | ||||
let mut other_parent = HashSet::new(); | let mut other_parent = HashSet::new(); | ||||
for ( | for ( | ||||
filename, | filename, | ||||
DirstateEntry { | DirstateEntry { | ||||
state, size, mtime, .. | state, size, mtime, .. | ||||
}, | }, | ||||
if self.dirs.is_none() { | if self.dirs.is_none() { | ||||
self.dirs = Some(DirsMultiset::from_dirstate( | self.dirs = Some(DirsMultiset::from_dirstate( | ||||
&self.state_map, | &self.state_map, | ||||
Some(EntryState::Removed), | Some(EntryState::Removed), | ||||
)); | )); | ||||
} | } | ||||
} | } | ||||
pub fn has_tracked_dir(&mut self, directory: &[u8]) -> bool { | pub fn has_tracked_dir(&mut self, directory: &HgPath) -> bool { | ||||
self.set_dirs(); | self.set_dirs(); | ||||
self.dirs.as_ref().unwrap().contains(directory) | self.dirs.as_ref().unwrap().contains(directory) | ||||
} | } | ||||
pub fn has_dir(&mut self, directory: &[u8]) -> bool { | pub fn has_dir(&mut self, directory: &HgPath) -> bool { | ||||
self.set_all_dirs(); | self.set_all_dirs(); | ||||
self.all_dirs.as_ref().unwrap().contains(directory) | self.all_dirs.as_ref().unwrap().contains(directory) | ||||
} | } | ||||
pub fn parents( | pub fn parents( | ||||
&mut self, | &mut self, | ||||
file_contents: &[u8], | file_contents: &[u8], | ||||
) -> Result<&DirstateParents, DirstateError> { | ) -> Result<&DirstateParents, DirstateError> { | ||||
use super::*; | use super::*; | ||||
#[test] | #[test] | ||||
fn test_dirs_multiset() { | fn test_dirs_multiset() { | ||||
let mut map = DirstateMap::new(); | let mut map = DirstateMap::new(); | ||||
assert!(map.dirs.is_none()); | assert!(map.dirs.is_none()); | ||||
assert!(map.all_dirs.is_none()); | assert!(map.all_dirs.is_none()); | ||||
assert_eq!(false, map.has_dir(b"nope")); | assert_eq!(false, map.has_dir(HgPath::new(b"nope"))); | ||||
assert!(map.all_dirs.is_some()); | assert!(map.all_dirs.is_some()); | ||||
assert!(map.dirs.is_none()); | assert!(map.dirs.is_none()); | ||||
assert_eq!(false, map.has_tracked_dir(b"nope")); | assert_eq!(false, map.has_tracked_dir(HgPath::new(b"nope"))); | ||||
assert!(map.dirs.is_some()); | assert!(map.dirs.is_some()); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_add_file() { | fn test_add_file() { | ||||
let mut map = DirstateMap::new(); | let mut map = DirstateMap::new(); | ||||
assert_eq!(0, map.len()); | assert_eq!(0, map.len()); | ||||
map.add_file( | map.add_file( | ||||
b"meh", | HgPath::new(b"meh"), | ||||
EntryState::Normal, | EntryState::Normal, | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 1337, | mode: 1337, | ||||
mtime: 1337, | mtime: 1337, | ||||
size: 1337, | size: 1337, | ||||
}, | }, | ||||
); | ); | ||||
(b"f8", (EntryState::Merged, 1337, 1337, 1337)), | (b"f8", (EntryState::Merged, 1337, 1337, 1337)), | ||||
(b"f9", (EntryState::Merged, 1337, -2, 1337)), | (b"f9", (EntryState::Merged, 1337, -2, 1337)), | ||||
(b"fa", (EntryState::Added, 1337, -2, 1337)), | (b"fa", (EntryState::Added, 1337, -2, 1337)), | ||||
(b"fb", (EntryState::Removed, 1337, -2, 1337)), | (b"fb", (EntryState::Removed, 1337, -2, 1337)), | ||||
] | ] | ||||
.iter() | .iter() | ||||
.map(|(fname, (state, mode, size, mtime))| { | .map(|(fname, (state, mode, size, mtime))| { | ||||
( | ( | ||||
fname.to_vec(), | HgPathBuf::from_bytes(fname.as_ref()), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: *state, | state: *state, | ||||
mode: *mode, | mode: *mode, | ||||
size: *size, | size: *size, | ||||
mtime: *mtime, | mtime: *mtime, | ||||
}, | }, | ||||
) | ) | ||||
}) | }) | ||||
.collect(); | .collect(); | ||||
let non_normal = [ | let non_normal = [ | ||||
b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb", | b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb", | ||||
] | ] | ||||
.iter() | .iter() | ||||
.map(|x| x.to_vec()) | .map(|x| HgPathBuf::from_bytes(x.as_ref())) | ||||
.collect(); | .collect(); | ||||
let mut other_parent = HashSet::new(); | let mut other_parent = HashSet::new(); | ||||
other_parent.insert(b"f4".to_vec()); | other_parent.insert(HgPathBuf::from_bytes(b"f4")); | ||||
assert_eq!( | assert_eq!( | ||||
(non_normal, other_parent), | (non_normal, other_parent), | ||||
map.non_normal_other_parent_entries() | map.non_normal_other_parent_entries() | ||||
); | ); | ||||
} | } | ||||
} | } |
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
use crate::utils::hg_path::HgPath; | |||||
use crate::{ | use crate::{ | ||||
dirstate::{CopyMap, EntryState, StateMap}, | dirstate::{CopyMap, EntryState, StateMap}, | ||||
DirstateEntry, DirstatePackError, DirstateParents, DirstateParseError, | DirstateEntry, DirstatePackError, DirstateParents, DirstateParseError, | ||||
}; | }; | ||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; | ||||
use std::convert::{TryFrom, TryInto}; | use std::convert::{TryFrom, TryInto}; | ||||
use std::io::Cursor; | use std::io::Cursor; | ||||
use std::time::Duration; | use std::time::Duration; | ||||
let path = &entry_bytes[MIN_ENTRY_SIZE..MIN_ENTRY_SIZE + (path_len)]; | let path = &entry_bytes[MIN_ENTRY_SIZE..MIN_ENTRY_SIZE + (path_len)]; | ||||
let (path, copy) = match memchr::memchr(0, path) { | let (path, copy) = match memchr::memchr(0, path) { | ||||
None => (path, None), | None => (path, None), | ||||
Some(i) => (&path[..i], Some(&path[(i + 1)..])), | Some(i) => (&path[..i], Some(&path[(i + 1)..])), | ||||
}; | }; | ||||
if let Some(copy_path) = copy { | if let Some(copy_path) = copy { | ||||
copy_map.insert(path.to_owned(), copy_path.to_owned()); | copy_map.insert( | ||||
HgPath::new(path).to_owned(), | |||||
HgPath::new(copy_path).to_owned(), | |||||
); | |||||
}; | }; | ||||
state_map.insert( | state_map.insert( | ||||
path.to_owned(), | HgPath::new(path).to_owned(), | ||||
DirstateEntry { | DirstateEntry { | ||||
state, | state, | ||||
mode, | mode, | ||||
size, | size, | ||||
mtime, | mtime, | ||||
}, | }, | ||||
); | ); | ||||
curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len); | curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len); | ||||
let mut packed = Vec::with_capacity(expected_size); | let mut packed = Vec::with_capacity(expected_size); | ||||
let mut new_state_map = vec![]; | let mut new_state_map = vec![]; | ||||
packed.extend(&parents.p1); | packed.extend(&parents.p1); | ||||
packed.extend(&parents.p2); | packed.extend(&parents.p2); | ||||
for (filename, entry) in state_map.iter() { | for (filename, entry) in state_map.iter() { | ||||
let mut new_filename: Vec<u8> = filename.to_owned(); | let new_filename = filename.to_owned(); | ||||
let mut new_mtime: i32 = entry.mtime; | let mut new_mtime: i32 = entry.mtime; | ||||
if entry.state == EntryState::Normal && entry.mtime == now { | if entry.state == EntryState::Normal && entry.mtime == now { | ||||
// The file was last modified "simultaneously" with the current | // The file was last modified "simultaneously" with the current | ||||
// write to dirstate (i.e. within the same second for file- | // write to dirstate (i.e. within the same second for file- | ||||
// systems with a granularity of 1 sec). This commonly happens | // systems with a granularity of 1 sec). This commonly happens | ||||
// for at least a couple of files on 'update'. | // for at least a couple of files on 'update'. | ||||
// The user could change the file without changing its size | // The user could change the file without changing its size | ||||
// within the same second. Invalidate the file's mtime in | // within the same second. Invalidate the file's mtime in | ||||
// dirstate, forcing future 'status' calls to compare the | // dirstate, forcing future 'status' calls to compare the | ||||
// contents of the file if the size is the same. This prevents | // contents of the file if the size is the same. This prevents | ||||
// mistakenly treating such files as clean. | // mistakenly treating such files as clean. | ||||
new_mtime = -1; | new_mtime = -1; | ||||
new_state_map.push(( | new_state_map.push(( | ||||
filename.to_owned(), | filename.to_owned(), | ||||
DirstateEntry { | DirstateEntry { | ||||
mtime: new_mtime, | mtime: new_mtime, | ||||
..*entry | ..*entry | ||||
}, | }, | ||||
)); | )); | ||||
} | } | ||||
let mut new_filename = new_filename.into_vec(); | |||||
if let Some(copy) = copy_map.get(filename) { | if let Some(copy) = copy_map.get(filename) { | ||||
new_filename.push('\0' as u8); | new_filename.push('\0' as u8); | ||||
new_filename.extend(copy); | new_filename.extend(copy.bytes()); | ||||
} | } | ||||
packed.write_u8(entry.state.into())?; | packed.write_u8(entry.state.into())?; | ||||
packed.write_i32::<BigEndian>(entry.mode)?; | packed.write_i32::<BigEndian>(entry.mode)?; | ||||
packed.write_i32::<BigEndian>(entry.size)?; | packed.write_i32::<BigEndian>(entry.size)?; | ||||
packed.write_i32::<BigEndian>(new_mtime)?; | packed.write_i32::<BigEndian>(new_mtime)?; | ||||
packed.write_i32::<BigEndian>(new_filename.len() as i32)?; | packed.write_i32::<BigEndian>(new_filename.len() as i32)?; | ||||
packed.extend(new_filename) | packed.extend(new_filename) | ||||
} | } | ||||
if packed.len() != expected_size { | if packed.len() != expected_size { | ||||
return Err(DirstatePackError::BadSize(expected_size, packed.len())); | return Err(DirstatePackError::BadSize(expected_size, packed.len())); | ||||
} | } | ||||
state_map.extend(new_state_map); | state_map.extend(new_state_map); | ||||
Ok(packed) | Ok(packed) | ||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::*; | use super::*; | ||||
use crate::utils::hg_path::HgPathBuf; | |||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
#[test] | #[test] | ||||
fn test_pack_dirstate_empty() { | fn test_pack_dirstate_empty() { | ||||
let mut state_map: StateMap = HashMap::new(); | let mut state_map: StateMap = HashMap::new(); | ||||
let copymap = HashMap::new(); | let copymap = HashMap::new(); | ||||
let parents = DirstateParents { | let parents = DirstateParents { | ||||
p1: *b"12345678910111213141", | p1: *b"12345678910111213141", | ||||
p2: *b"00000000000000000000", | p2: *b"00000000000000000000", | ||||
}; | }; | ||||
let now = Duration::new(15000000, 0); | let now = Duration::new(15000000, 0); | ||||
let expected = b"1234567891011121314100000000000000000000".to_vec(); | let expected = b"1234567891011121314100000000000000000000".to_vec(); | ||||
assert_eq!( | assert_eq!( | ||||
expected, | expected, | ||||
pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | ||||
); | ); | ||||
assert!(state_map.is_empty()) | assert!(state_map.is_empty()) | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_pack_dirstate_one_entry() { | fn test_pack_dirstate_one_entry() { | ||||
let expected_state_map: StateMap = [( | let expected_state_map: StateMap = [( | ||||
b"f1".to_vec(), | HgPathBuf::from_bytes(b"f1"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: 0, | size: 0, | ||||
mtime: 791231220, | mtime: 791231220, | ||||
}, | }, | ||||
)] | )] | ||||
.iter() | .iter() | ||||
pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | ||||
); | ); | ||||
assert_eq!(expected_state_map, state_map); | assert_eq!(expected_state_map, state_map); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_pack_dirstate_one_entry_with_copy() { | fn test_pack_dirstate_one_entry_with_copy() { | ||||
let expected_state_map: StateMap = [( | let expected_state_map: StateMap = [( | ||||
b"f1".to_vec(), | HgPathBuf::from_bytes(b"f1"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: 0, | size: 0, | ||||
mtime: 791231220, | mtime: 791231220, | ||||
}, | }, | ||||
)] | )] | ||||
.iter() | .iter() | ||||
.cloned() | .cloned() | ||||
.collect(); | .collect(); | ||||
let mut state_map = expected_state_map.clone(); | let mut state_map = expected_state_map.clone(); | ||||
let mut copymap = HashMap::new(); | let mut copymap = HashMap::new(); | ||||
copymap.insert(b"f1".to_vec(), b"copyname".to_vec()); | copymap.insert( | ||||
HgPathBuf::from_bytes(b"f1"), | |||||
HgPathBuf::from_bytes(b"copyname"), | |||||
); | |||||
let parents = DirstateParents { | let parents = DirstateParents { | ||||
p1: *b"12345678910111213141", | p1: *b"12345678910111213141", | ||||
p2: *b"00000000000000000000", | p2: *b"00000000000000000000", | ||||
}; | }; | ||||
let now = Duration::new(15000000, 0); | let now = Duration::new(15000000, 0); | ||||
let expected = [ | let expected = [ | ||||
49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49, | 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, | 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, | 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, | 41, 58, 244, 0, 0, 0, 11, 102, 49, 0, 99, 111, 112, 121, 110, 97, | ||||
109, 101, | 109, 101, | ||||
] | ] | ||||
.to_vec(); | .to_vec(); | ||||
assert_eq!( | assert_eq!( | ||||
expected, | expected, | ||||
pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | pack_dirstate(&mut state_map, ©map, parents, now).unwrap() | ||||
); | ); | ||||
assert_eq!(expected_state_map, state_map); | assert_eq!(expected_state_map, state_map); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_parse_pack_one_entry_with_copy() { | fn test_parse_pack_one_entry_with_copy() { | ||||
let mut state_map: StateMap = [( | let mut state_map: StateMap = [( | ||||
b"f1".to_vec(), | HgPathBuf::from_bytes(b"f1"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: 0, | size: 0, | ||||
mtime: 791231220, | mtime: 791231220, | ||||
}, | }, | ||||
)] | )] | ||||
.iter() | .iter() | ||||
.cloned() | .cloned() | ||||
.collect(); | .collect(); | ||||
let mut copymap = HashMap::new(); | let mut copymap = HashMap::new(); | ||||
copymap.insert(b"f1".to_vec(), b"copyname".to_vec()); | copymap.insert( | ||||
HgPathBuf::from_bytes(b"f1"), | |||||
HgPathBuf::from_bytes(b"copyname"), | |||||
); | |||||
let parents = DirstateParents { | let parents = DirstateParents { | ||||
p1: *b"12345678910111213141", | p1: *b"12345678910111213141", | ||||
p2: *b"00000000000000000000", | p2: *b"00000000000000000000", | ||||
}; | }; | ||||
let now = Duration::new(15000000, 0); | let now = Duration::new(15000000, 0); | ||||
let result = | let result = | ||||
pack_dirstate(&mut state_map, ©map, parents.clone(), now) | pack_dirstate(&mut state_map, ©map, parents.clone(), now) | ||||
.unwrap(); | .unwrap(); | ||||
(new_parents, new_state_map, new_copy_map) | (new_parents, new_state_map, new_copy_map) | ||||
) | ) | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_parse_pack_multiple_entries_with_copy() { | fn test_parse_pack_multiple_entries_with_copy() { | ||||
let mut state_map: StateMap = [ | let mut state_map: StateMap = [ | ||||
( | ( | ||||
b"f1".to_vec(), | HgPathBuf::from_bytes(b"f1"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: 0, | size: 0, | ||||
mtime: 791231220, | mtime: 791231220, | ||||
}, | }, | ||||
), | ), | ||||
( | ( | ||||
b"f2".to_vec(), | HgPathBuf::from_bytes(b"f2"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Merged, | state: EntryState::Merged, | ||||
mode: 0o777, | mode: 0o777, | ||||
size: 1000, | size: 1000, | ||||
mtime: 791231220, | mtime: 791231220, | ||||
}, | }, | ||||
), | ), | ||||
( | ( | ||||
b"f3".to_vec(), | HgPathBuf::from_bytes(b"f3"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Removed, | state: EntryState::Removed, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: 234553, | size: 234553, | ||||
mtime: 791231220, | mtime: 791231220, | ||||
}, | }, | ||||
), | ), | ||||
( | ( | ||||
b"f4\xF6".to_vec(), | HgPathBuf::from_bytes(b"f4\xF6"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Added, | state: EntryState::Added, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: -1, | size: -1, | ||||
mtime: -1, | mtime: -1, | ||||
}, | }, | ||||
), | ), | ||||
] | ] | ||||
.iter() | .iter() | ||||
.cloned() | .cloned() | ||||
.collect(); | .collect(); | ||||
let mut copymap = HashMap::new(); | let mut copymap = HashMap::new(); | ||||
copymap.insert(b"f1".to_vec(), b"copyname".to_vec()); | copymap.insert( | ||||
copymap.insert(b"f4\xF6".to_vec(), b"copyname2".to_vec()); | HgPathBuf::from_bytes(b"f1"), | ||||
HgPathBuf::from_bytes(b"copyname"), | |||||
); | |||||
copymap.insert( | |||||
HgPathBuf::from_bytes(b"f4\xF6"), | |||||
HgPathBuf::from_bytes(b"copyname2"), | |||||
); | |||||
let parents = DirstateParents { | let parents = DirstateParents { | ||||
p1: *b"12345678910111213141", | p1: *b"12345678910111213141", | ||||
p2: *b"00000000000000000000", | p2: *b"00000000000000000000", | ||||
}; | }; | ||||
let now = Duration::new(15000000, 0); | let now = Duration::new(15000000, 0); | ||||
let result = | let result = | ||||
pack_dirstate(&mut state_map, ©map, parents.clone(), now) | pack_dirstate(&mut state_map, ©map, parents.clone(), now) | ||||
.unwrap(); | .unwrap(); | ||||
(new_parents, new_state_map, new_copy_map) | (new_parents, new_state_map, new_copy_map) | ||||
) | ) | ||||
} | } | ||||
#[test] | #[test] | ||||
/// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4 | /// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4 | ||||
fn test_parse_pack_one_entry_with_copy_and_time_conflict() { | fn test_parse_pack_one_entry_with_copy_and_time_conflict() { | ||||
let mut state_map: StateMap = [( | let mut state_map: StateMap = [( | ||||
b"f1".to_vec(), | HgPathBuf::from_bytes(b"f1"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: 0, | size: 0, | ||||
mtime: 15000000, | mtime: 15000000, | ||||
}, | }, | ||||
)] | )] | ||||
.iter() | .iter() | ||||
.cloned() | .cloned() | ||||
.collect(); | .collect(); | ||||
let mut copymap = HashMap::new(); | let mut copymap = HashMap::new(); | ||||
copymap.insert(b"f1".to_vec(), b"copyname".to_vec()); | copymap.insert( | ||||
HgPathBuf::from_bytes(b"f1"), | |||||
HgPathBuf::from_bytes(b"copyname"), | |||||
); | |||||
let parents = DirstateParents { | let parents = DirstateParents { | ||||
p1: *b"12345678910111213141", | p1: *b"12345678910111213141", | ||||
p2: *b"00000000000000000000", | p2: *b"00000000000000000000", | ||||
}; | }; | ||||
let now = Duration::new(15000000, 0); | let now = Duration::new(15000000, 0); | ||||
let result = | let result = | ||||
pack_dirstate(&mut state_map, ©map, parents.clone(), now) | pack_dirstate(&mut state_map, ©map, parents.clone(), now) | ||||
.unwrap(); | .unwrap(); | ||||
let mut new_state_map: StateMap = HashMap::new(); | let mut new_state_map: StateMap = HashMap::new(); | ||||
let mut new_copy_map: CopyMap = HashMap::new(); | let mut new_copy_map: CopyMap = HashMap::new(); | ||||
let new_parents = parse_dirstate( | let new_parents = parse_dirstate( | ||||
&mut new_state_map, | &mut new_state_map, | ||||
&mut new_copy_map, | &mut new_copy_map, | ||||
result.as_slice(), | result.as_slice(), | ||||
) | ) | ||||
.unwrap(); | .unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
( | ( | ||||
parents, | parents, | ||||
[( | [( | ||||
b"f1".to_vec(), | HgPathBuf::from_bytes(b"f1"), | ||||
DirstateEntry { | DirstateEntry { | ||||
state: EntryState::Normal, | state: EntryState::Normal, | ||||
mode: 0o644, | mode: 0o644, | ||||
size: 0, | size: 0, | ||||
mtime: -1 | mtime: -1 | ||||
} | } | ||||
)] | )] | ||||
.iter() | .iter() | ||||
.cloned() | .cloned() | ||||
.collect::<StateMap>(), | .collect::<StateMap>(), | ||||
copymap, | copymap, | ||||
), | ), | ||||
(new_parents, new_state_map, new_copy_map) | (new_parents, new_state_map, new_copy_map) | ||||
) | ) | ||||
} | } | ||||
} | } |
// filepatterns.rs | // filepatterns.rs | ||||
// | // | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
//! Handling of Mercurial-specific patterns. | //! Handling of Mercurial-specific patterns. | ||||
use crate::{ | use crate::{utils::SliceExt, LineNumber, PatternError, PatternFileError}; | ||||
utils::{files::get_path_from_bytes, SliceExt}, | |||||
LineNumber, PatternError, PatternFileError, | |||||
}; | |||||
use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||
use regex::bytes::{NoExpand, Regex}; | use regex::bytes::{NoExpand, Regex}; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::fs::File; | use std::fs::File; | ||||
use std::io::Read; | use std::io::Read; | ||||
use std::path::{Path, PathBuf}; | |||||
use std::vec::Vec; | use std::vec::Vec; | ||||
lazy_static! { | lazy_static! { | ||||
static ref RE_ESCAPE: Vec<Vec<u8>> = { | static ref RE_ESCAPE: Vec<Vec<u8>> = { | ||||
let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect(); | let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect(); | ||||
let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c"; | let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c"; | ||||
for byte in to_escape { | for byte in to_escape { | ||||
v[*byte as usize].insert(0, b'\\'); | v[*byte as usize].insert(0, b'\\'); | ||||
m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref()); | m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref()); | ||||
m.insert(b"include".as_ref(), b"include".as_ref()); | m.insert(b"include".as_ref(), b"include".as_ref()); | ||||
m.insert(b"subinclude".as_ref(), b"subinclude".as_ref()); | m.insert(b"subinclude".as_ref(), b"subinclude".as_ref()); | ||||
m | m | ||||
}; | }; | ||||
} | } | ||||
pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>); | pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>); | ||||
type WarningTuple = (Vec<u8>, Vec<u8>); | type WarningTuple = (PathBuf, Vec<u8>); | ||||
pub fn parse_pattern_file_contents( | pub fn parse_pattern_file_contents<P: AsRef<Path>>( | ||||
lines: &[u8], | lines: &[u8], | ||||
file_path: &[u8], | file_path: P, | ||||
warn: bool, | warn: bool, | ||||
) -> (Vec<PatternTuple>, Vec<WarningTuple>) { | ) -> (Vec<PatternTuple>, Vec<WarningTuple>) { | ||||
let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap(); | let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap(); | ||||
let comment_escape_regex = Regex::new(r"\\#").unwrap(); | let comment_escape_regex = Regex::new(r"\\#").unwrap(); | ||||
let mut inputs: Vec<PatternTuple> = vec![]; | let mut inputs: Vec<PatternTuple> = vec![]; | ||||
let mut warnings: Vec<WarningTuple> = vec![]; | let mut warnings: Vec<WarningTuple> = vec![]; | ||||
let mut current_syntax = b"relre:".as_ref(); | let mut current_syntax = b"relre:".as_ref(); | ||||
} | } | ||||
if line.starts_with(b"syntax:") { | if line.starts_with(b"syntax:") { | ||||
let syntax = line[b"syntax:".len()..].trim(); | let syntax = line[b"syntax:".len()..].trim(); | ||||
if let Some(rel_syntax) = SYNTAXES.get(syntax) { | if let Some(rel_syntax) = SYNTAXES.get(syntax) { | ||||
current_syntax = rel_syntax; | current_syntax = rel_syntax; | ||||
} else if warn { | } else if warn { | ||||
warnings.push((file_path.to_owned(), syntax.to_owned())); | warnings | ||||
.push((file_path.as_ref().to_owned(), syntax.to_owned())); | |||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
let mut line_syntax: &[u8] = ¤t_syntax; | let mut line_syntax: &[u8] = ¤t_syntax; | ||||
for (s, rels) in SYNTAXES.iter() { | for (s, rels) in SYNTAXES.iter() { | ||||
if line.starts_with(rels) { | if line.starts_with(rels) { | ||||
[line_syntax, line].concat(), | [line_syntax, line].concat(), | ||||
line_number, | line_number, | ||||
line.to_owned(), | line.to_owned(), | ||||
)); | )); | ||||
} | } | ||||
(inputs, warnings) | (inputs, warnings) | ||||
} | } | ||||
pub fn read_pattern_file( | pub fn read_pattern_file<P: AsRef<Path>>( | ||||
file_path: &[u8], | file_path: P, | ||||
warn: bool, | warn: bool, | ||||
) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> { | ) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> { | ||||
let mut f = File::open(get_path_from_bytes(file_path))?; | let mut f = File::open(file_path.as_ref())?; | ||||
let mut contents = Vec::new(); | let mut contents = Vec::new(); | ||||
f.read_to_end(&mut contents)?; | f.read_to_end(&mut contents)?; | ||||
Ok(parse_pattern_file_contents(&contents, file_path, warn)) | Ok(parse_pattern_file_contents(&contents, file_path, warn)) | ||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_parse_pattern_file_contents() { | fn test_parse_pattern_file_contents() { | ||||
let lines = b"syntax: glob\n*.elc"; | let lines = b"syntax: glob\n*.elc"; | ||||
assert_eq!( | assert_eq!( | ||||
vec![(b"relglob:*.elc".to_vec(), 2, b"*.elc".to_vec())], | vec![(b"relglob:*.elc".to_vec(), 2, b"*.elc".to_vec())], | ||||
parse_pattern_file_contents(lines, b"file_path", false).0, | parse_pattern_file_contents(lines, Path::new("file_path"), false) | ||||
.0, | |||||
); | ); | ||||
let lines = b"syntax: include\nsyntax: glob"; | let lines = b"syntax: include\nsyntax: glob"; | ||||
assert_eq!( | assert_eq!( | ||||
parse_pattern_file_contents(lines, b"file_path", false).0, | parse_pattern_file_contents(lines, Path::new("file_path"), false) | ||||
.0, | |||||
vec![] | vec![] | ||||
); | ); | ||||
let lines = b"glob:**.o"; | let lines = b"glob:**.o"; | ||||
assert_eq!( | assert_eq!( | ||||
parse_pattern_file_contents(lines, b"file_path", false).0, | parse_pattern_file_contents(lines, Path::new("file_path"), false) | ||||
.0, | |||||
vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())] | vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())] | ||||
); | ); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn test_build_single_regex_shortcut() { | fn test_build_single_regex_shortcut() { | ||||
assert_eq!( | assert_eq!( | ||||
br"(?:/|$)".to_vec(), | br"(?:/|$)".to_vec(), |
dirs_multiset::DirsMultiset, | dirs_multiset::DirsMultiset, | ||||
dirstate_map::DirstateMap, | dirstate_map::DirstateMap, | ||||
parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE}, | parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE}, | ||||
CopyMap, DirstateEntry, DirstateParents, EntryState, StateMap, | CopyMap, DirstateEntry, DirstateParents, EntryState, StateMap, | ||||
}; | }; | ||||
mod filepatterns; | mod filepatterns; | ||||
pub mod utils; | pub mod utils; | ||||
use crate::utils::hg_path::HgPathBuf; | |||||
pub use filepatterns::{ | pub use filepatterns::{ | ||||
build_single_regex, read_pattern_file, PatternSyntax, PatternTuple, | build_single_regex, read_pattern_file, PatternSyntax, PatternTuple, | ||||
}; | }; | ||||
/// Mercurial revision numbers | /// Mercurial revision numbers | ||||
/// | /// | ||||
/// As noted in revlog.c, revision numbers are actually encoded in | /// As noted in revlog.c, revision numbers are actually encoded in | ||||
/// 4 bytes, and are liberally converted to ints, whence the i32 | /// 4 bytes, and are liberally converted to ints, whence the i32 | ||||
impl From<std::io::Error> for DirstatePackError { | impl From<std::io::Error> for DirstatePackError { | ||||
fn from(e: std::io::Error) -> Self { | fn from(e: std::io::Error) -> Self { | ||||
DirstatePackError::CorruptedEntry(e.to_string()) | DirstatePackError::CorruptedEntry(e.to_string()) | ||||
} | } | ||||
} | } | ||||
#[derive(Debug, PartialEq)] | #[derive(Debug, PartialEq)] | ||||
pub enum DirstateMapError { | pub enum DirstateMapError { | ||||
PathNotFound(Vec<u8>), | PathNotFound(HgPathBuf), | ||||
EmptyPath, | EmptyPath, | ||||
} | } | ||||
pub enum DirstateError { | pub enum DirstateError { | ||||
Parse(DirstateParseError), | Parse(DirstateParseError), | ||||
Pack(DirstatePackError), | Pack(DirstatePackError), | ||||
Map(DirstateMapError), | Map(DirstateMapError), | ||||
IO(std::io::Error), | IO(std::io::Error), |
mod copymap; | mod copymap; | ||||
mod dirs_multiset; | mod dirs_multiset; | ||||
mod dirstate_map; | mod dirstate_map; | ||||
use crate::dirstate::{dirs_multiset::Dirs, dirstate_map::DirstateMap}; | use crate::dirstate::{dirs_multiset::Dirs, dirstate_map::DirstateMap}; | ||||
use cpython::{ | use cpython::{ | ||||
exc, PyBytes, PyDict, PyErr, PyModule, PyObject, PyResult, PySequence, | exc, PyBytes, PyDict, PyErr, PyModule, PyObject, PyResult, PySequence, | ||||
Python, | Python, | ||||
}; | }; | ||||
use hg::{DirstateEntry, DirstateParseError, EntryState, StateMap}; | use hg::{ | ||||
utils::hg_path::HgPathBuf, DirstateEntry, DirstateParseError, EntryState, | |||||
StateMap, | |||||
}; | |||||
use libc::{c_char, c_int}; | use libc::{c_char, c_int}; | ||||
#[cfg(feature = "python27")] | #[cfg(feature = "python27")] | ||||
use python27_sys::PyCapsule_Import; | use python27_sys::PyCapsule_Import; | ||||
#[cfg(feature = "python3")] | #[cfg(feature = "python3")] | ||||
use python3_sys::PyCapsule_Import; | use python3_sys::PyCapsule_Import; | ||||
use std::convert::TryFrom; | use std::convert::TryFrom; | ||||
use std::ffi::CStr; | use std::ffi::CStr; | ||||
use std::mem::transmute; | use std::mem::transmute; | ||||
}, | }, | ||||
)?; | )?; | ||||
let mode = stats.get_item(py, 1)?.extract(py)?; | let mode = stats.get_item(py, 1)?.extract(py)?; | ||||
let size = stats.get_item(py, 2)?.extract(py)?; | let size = stats.get_item(py, 2)?.extract(py)?; | ||||
let mtime = stats.get_item(py, 3)?.extract(py)?; | let mtime = stats.get_item(py, 3)?.extract(py)?; | ||||
let filename = filename.extract::<PyBytes>(py)?; | let filename = filename.extract::<PyBytes>(py)?; | ||||
let filename = filename.data(py); | let filename = filename.data(py); | ||||
Ok(( | Ok(( | ||||
filename.to_owned(), | HgPathBuf::from(filename.to_owned()), | ||||
DirstateEntry { | DirstateEntry { | ||||
state, | state, | ||||
mode, | mode, | ||||
size, | size, | ||||
mtime, | mtime, | ||||
}, | }, | ||||
)) | )) | ||||
}) | }) |
// copymap.rs | // copymap.rs | ||||
// | // | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
//! Bindings for `hg::dirstate::dirstate_map::CopyMap` provided by the | //! Bindings for `hg::dirstate::dirstate_map::CopyMap` provided by the | ||||
//! `hg-core` package. | //! `hg-core` package. | ||||
use cpython::{PyBytes, PyClone, PyDict, PyObject, PyResult, Python}; | use cpython::{PyBytes, PyClone, PyDict, PyObject, PyResult, Python}; | ||||
use std::cell::RefCell; | use std::cell::RefCell; | ||||
use crate::dirstate::dirstate_map::{DirstateMap, DirstateMapLeakedRef}; | use crate::dirstate::dirstate_map::{DirstateMap, DirstateMapLeakedRef}; | ||||
use hg::utils::hg_path::HgPathBuf; | |||||
py_class!(pub class CopyMap |py| { | py_class!(pub class CopyMap |py| { | ||||
data dirstate_map: DirstateMap; | data dirstate_map: DirstateMap; | ||||
def __getitem__(&self, key: PyObject) -> PyResult<PyBytes> { | def __getitem__(&self, key: PyObject) -> PyResult<PyBytes> { | ||||
(*self.dirstate_map(py)).copymapgetitem(py, key) | (*self.dirstate_map(py)).copymapgetitem(py, key) | ||||
} | } | ||||
}); | }); | ||||
impl CopyMap { | impl CopyMap { | ||||
pub fn from_inner(py: Python, dm: DirstateMap) -> PyResult<Self> { | pub fn from_inner(py: Python, dm: DirstateMap) -> PyResult<Self> { | ||||
Self::create_instance(py, dm) | Self::create_instance(py, dm) | ||||
} | } | ||||
fn translate_key( | fn translate_key( | ||||
py: Python, | py: Python, | ||||
res: (&Vec<u8>, &Vec<u8>), | res: (&HgPathBuf, &HgPathBuf), | ||||
) -> PyResult<Option<PyBytes>> { | ) -> PyResult<Option<PyBytes>> { | ||||
Ok(Some(PyBytes::new(py, res.0))) | Ok(Some(PyBytes::new(py, res.0.as_ref()))) | ||||
} | } | ||||
fn translate_key_value( | fn translate_key_value( | ||||
py: Python, | py: Python, | ||||
res: (&Vec<u8>, &Vec<u8>), | res: (&HgPathBuf, &HgPathBuf), | ||||
) -> PyResult<Option<(PyBytes, PyBytes)>> { | ) -> PyResult<Option<(PyBytes, PyBytes)>> { | ||||
let (k, v) = res; | let (k, v) = res; | ||||
Ok(Some((PyBytes::new(py, k), PyBytes::new(py, v)))) | Ok(Some(( | ||||
PyBytes::new(py, k.as_ref()), | |||||
PyBytes::new(py, v.as_ref()), | |||||
))) | |||||
} | } | ||||
} | } | ||||
py_shared_mapping_iterator!( | py_shared_mapping_iterator!( | ||||
CopyMapKeysIterator, | CopyMapKeysIterator, | ||||
DirstateMapLeakedRef, | DirstateMapLeakedRef, | ||||
Vec<u8>, | HgPathBuf, | ||||
Vec<u8>, | HgPathBuf, | ||||
CopyMap::translate_key, | CopyMap::translate_key, | ||||
Option<PyBytes> | Option<PyBytes> | ||||
); | ); | ||||
py_shared_mapping_iterator!( | py_shared_mapping_iterator!( | ||||
CopyMapItemsIterator, | CopyMapItemsIterator, | ||||
DirstateMapLeakedRef, | DirstateMapLeakedRef, | ||||
Vec<u8>, | HgPathBuf, | ||||
Vec<u8>, | HgPathBuf, | ||||
CopyMap::translate_key_value, | CopyMap::translate_key_value, | ||||
Option<(PyBytes, PyBytes)> | Option<(PyBytes, PyBytes)> | ||||
); | ); |
use std::convert::TryInto; | use std::convert::TryInto; | ||||
use cpython::{ | use cpython::{ | ||||
exc, ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult, | exc, ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult, | ||||
Python, | Python, | ||||
}; | }; | ||||
use crate::{dirstate::extract_dirstate, ref_sharing::PySharedState}; | use crate::{dirstate::extract_dirstate, ref_sharing::PySharedState}; | ||||
use hg::{DirsMultiset, DirstateMapError, DirstateParseError, EntryState}; | use hg::{ | ||||
utils::hg_path::{HgPath, HgPathBuf}, | |||||
DirsMultiset, DirstateMapError, DirstateParseError, EntryState, | |||||
}; | |||||
py_class!(pub class Dirs |py| { | py_class!(pub class Dirs |py| { | ||||
data inner: RefCell<DirsMultiset>; | data inner: RefCell<DirsMultiset>; | ||||
data py_shared_state: PySharedState; | data py_shared_state: PySharedState; | ||||
// `map` is either a `dict` or a flat iterator (usually a `set`, sometimes | // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes | ||||
// a `list`) | // a `list`) | ||||
def __new__( | def __new__( | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | })?, | ||||
); | ); | ||||
} | } | ||||
let inner = if let Ok(map) = map.cast_as::<PyDict>(py) { | let inner = if let Ok(map) = map.cast_as::<PyDict>(py) { | ||||
let dirstate = extract_dirstate(py, &map)?; | let dirstate = extract_dirstate(py, &map)?; | ||||
DirsMultiset::from_dirstate(&dirstate, skip_state) | DirsMultiset::from_dirstate(&dirstate, skip_state) | ||||
} else { | } else { | ||||
let map: Result<Vec<Vec<u8>>, PyErr> = map | let map: Result<Vec<HgPathBuf>, PyErr> = map | ||||
.iter(py)? | .iter(py)? | ||||
.map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned())) | .map(|o| { | ||||
Ok(HgPathBuf::from_bytes( | |||||
o?.extract::<PyBytes>(py)?.data(py), | |||||
)) | |||||
}) | |||||
.collect(); | .collect(); | ||||
DirsMultiset::from_manifest(&map?) | DirsMultiset::from_manifest(&map?) | ||||
}; | }; | ||||
Self::create_instance( | Self::create_instance( | ||||
py, | py, | ||||
RefCell::new(inner), | RefCell::new(inner), | ||||
PySharedState::default() | PySharedState::default() | ||||
) | ) | ||||
} | } | ||||
def addpath(&self, path: PyObject) -> PyResult<PyObject> { | def addpath(&self, path: PyObject) -> PyResult<PyObject> { | ||||
self.borrow_mut(py)?.add_path( | self.borrow_mut(py)?.add_path( | ||||
path.extract::<PyBytes>(py)?.data(py), | HgPath::new(path.extract::<PyBytes>(py)?.data(py)), | ||||
); | ); | ||||
Ok(py.None()) | Ok(py.None()) | ||||
} | } | ||||
def delpath(&self, path: PyObject) -> PyResult<PyObject> { | def delpath(&self, path: PyObject) -> PyResult<PyObject> { | ||||
self.borrow_mut(py)?.delete_path( | self.borrow_mut(py)?.delete_path( | ||||
path.extract::<PyBytes>(py)?.data(py), | HgPath::new(path.extract::<PyBytes>(py)?.data(py)), | ||||
) | ) | ||||
.and(Ok(py.None())) | .and(Ok(py.None())) | ||||
.or_else(|e| { | .or_else(|e| { | ||||
match e { | match e { | ||||
DirstateMapError::PathNotFound(_p) => { | DirstateMapError::PathNotFound(_p) => { | ||||
Err(PyErr::new::<exc::ValueError, _>( | Err(PyErr::new::<exc::ValueError, _>( | ||||
py, | py, | ||||
"expected a value, found none".to_string(), | "expected a value, found none".to_string(), | ||||
DirsMultisetKeysIterator::create_instance( | DirsMultisetKeysIterator::create_instance( | ||||
py, | py, | ||||
RefCell::new(Some(DirsMultisetLeakedRef::new(py, &self))), | RefCell::new(Some(DirsMultisetLeakedRef::new(py, &self))), | ||||
RefCell::new(Box::new(self.leak_immutable(py)?.iter())), | RefCell::new(Box::new(self.leak_immutable(py)?.iter())), | ||||
) | ) | ||||
} | } | ||||
def __contains__(&self, item: PyObject) -> PyResult<bool> { | def __contains__(&self, item: PyObject) -> PyResult<bool> { | ||||
Ok(self | Ok(self.inner(py).borrow().contains(HgPath::new( | ||||
.inner(py) | item.extract::<PyBytes>(py)?.data(py).as_ref(), | ||||
.borrow() | ))) | ||||
.contains(item.extract::<PyBytes>(py)?.data(py).as_ref())) | |||||
} | } | ||||
}); | }); | ||||
py_shared_ref!(Dirs, DirsMultiset, inner, DirsMultisetLeakedRef,); | py_shared_ref!(Dirs, DirsMultiset, inner, DirsMultisetLeakedRef,); | ||||
impl Dirs { | impl Dirs { | ||||
pub fn from_inner(py: Python, d: DirsMultiset) -> PyResult<Self> { | pub fn from_inner(py: Python, d: DirsMultiset) -> PyResult<Self> { | ||||
Self::create_instance(py, RefCell::new(d), PySharedState::default()) | Self::create_instance(py, RefCell::new(d), PySharedState::default()) | ||||
} | } | ||||
fn translate_key(py: Python, res: &Vec<u8>) -> PyResult<Option<PyBytes>> { | fn translate_key( | ||||
Ok(Some(PyBytes::new(py, res))) | py: Python, | ||||
res: &HgPathBuf, | |||||
) -> PyResult<Option<PyBytes>> { | |||||
Ok(Some(PyBytes::new(py, res.as_ref()))) | |||||
} | } | ||||
} | } | ||||
py_shared_sequence_iterator!( | py_shared_sequence_iterator!( | ||||
DirsMultisetKeysIterator, | DirsMultisetKeysIterator, | ||||
DirsMultisetLeakedRef, | DirsMultisetLeakedRef, | ||||
Vec<u8>, | HgPathBuf, | ||||
Dirs::translate_key, | Dirs::translate_key, | ||||
Option<PyBytes> | Option<PyBytes> | ||||
); | ); |
use libc::c_char; | use libc::c_char; | ||||
use crate::{ | use crate::{ | ||||
dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, | dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, | ||||
dirstate::{decapsule_make_dirstate_tuple, dirs_multiset::Dirs}, | dirstate::{decapsule_make_dirstate_tuple, dirs_multiset::Dirs}, | ||||
ref_sharing::PySharedState, | ref_sharing::PySharedState, | ||||
}; | }; | ||||
use hg::{ | use hg::{ | ||||
utils::hg_path::{HgPath, HgPathBuf}, | |||||
DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap, | DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap, | ||||
DirstateParents, DirstateParseError, EntryState, PARENT_SIZE, | DirstateParents, DirstateParseError, EntryState, PARENT_SIZE, | ||||
}; | }; | ||||
// TODO | // TODO | ||||
// This object needs to share references to multiple members of its Rust | // This object needs to share references to multiple members of its Rust | ||||
// inner struct, namely `copy_map`, `dirs` and `all_dirs`. | // inner struct, namely `copy_map`, `dirs` and `all_dirs`. | ||||
// Right now `CopyMap` is done, but it needs to have an explicit reference | // Right now `CopyMap` is done, but it needs to have an explicit reference | ||||
} | } | ||||
def get( | def get( | ||||
&self, | &self, | ||||
key: PyObject, | key: PyObject, | ||||
default: Option<PyObject> = None | default: Option<PyObject> = None | ||||
) -> PyResult<Option<PyObject>> { | ) -> PyResult<Option<PyObject>> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
match self.inner(py).borrow().get(key.data(py)) { | match self.inner(py).borrow().get(HgPath::new(key.data(py))) { | ||||
Some(entry) => { | Some(entry) => { | ||||
// Explicitly go through u8 first, then cast to | // Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char`. | // platform-specific `c_char`. | ||||
let state: u8 = entry.state.into(); | let state: u8 = entry.state.into(); | ||||
Ok(Some(decapsule_make_dirstate_tuple(py)?( | Ok(Some(decapsule_make_dirstate_tuple(py)?( | ||||
state as c_char, | state as c_char, | ||||
entry.mode, | entry.mode, | ||||
entry.size, | entry.size, | ||||
f: PyObject, | f: PyObject, | ||||
oldstate: PyObject, | oldstate: PyObject, | ||||
state: PyObject, | state: PyObject, | ||||
mode: PyObject, | mode: PyObject, | ||||
size: PyObject, | size: PyObject, | ||||
mtime: PyObject | mtime: PyObject | ||||
) -> PyResult<PyObject> { | ) -> PyResult<PyObject> { | ||||
self.borrow_mut(py)?.add_file( | self.borrow_mut(py)?.add_file( | ||||
f.extract::<PyBytes>(py)?.data(py), | HgPath::new(f.extract::<PyBytes>(py)?.data(py)), | ||||
oldstate.extract::<PyBytes>(py)?.data(py)[0] | oldstate.extract::<PyBytes>(py)?.data(py)[0] | ||||
.try_into() | .try_into() | ||||
.map_err(|e: DirstateParseError| { | .map_err(|e: DirstateParseError| { | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | })?, | ||||
DirstateEntry { | DirstateEntry { | ||||
state: state.extract::<PyBytes>(py)?.data(py)[0] | state: state.extract::<PyBytes>(py)?.data(py)[0] | ||||
.try_into() | .try_into() | ||||
def removefile( | def removefile( | ||||
&self, | &self, | ||||
f: PyObject, | f: PyObject, | ||||
oldstate: PyObject, | oldstate: PyObject, | ||||
size: PyObject | size: PyObject | ||||
) -> PyResult<PyObject> { | ) -> PyResult<PyObject> { | ||||
self.borrow_mut(py)? | self.borrow_mut(py)? | ||||
.remove_file( | .remove_file( | ||||
f.extract::<PyBytes>(py)?.data(py), | HgPath::new(f.extract::<PyBytes>(py)?.data(py)), | ||||
oldstate.extract::<PyBytes>(py)?.data(py)[0] | oldstate.extract::<PyBytes>(py)?.data(py)[0] | ||||
.try_into() | .try_into() | ||||
.map_err(|e: DirstateParseError| { | .map_err(|e: DirstateParseError| { | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | })?, | ||||
size.extract(py)?, | size.extract(py)?, | ||||
) | ) | ||||
.or_else(|_| { | .or_else(|_| { | ||||
Err(PyErr::new::<exc::OSError, _>( | Err(PyErr::new::<exc::OSError, _>( | ||||
py, | py, | ||||
"Dirstate error".to_string(), | "Dirstate error".to_string(), | ||||
)) | )) | ||||
})?; | })?; | ||||
Ok(py.None()) | Ok(py.None()) | ||||
} | } | ||||
def dropfile( | def dropfile( | ||||
&self, | &self, | ||||
f: PyObject, | f: PyObject, | ||||
oldstate: PyObject | oldstate: PyObject | ||||
) -> PyResult<PyBool> { | ) -> PyResult<PyBool> { | ||||
self.borrow_mut(py)? | self.borrow_mut(py)? | ||||
.drop_file( | .drop_file( | ||||
f.extract::<PyBytes>(py)?.data(py), | HgPath::new(f.extract::<PyBytes>(py)?.data(py)), | ||||
oldstate.extract::<PyBytes>(py)?.data(py)[0] | oldstate.extract::<PyBytes>(py)?.data(py)[0] | ||||
.try_into() | .try_into() | ||||
.map_err(|e: DirstateParseError| { | .map_err(|e: DirstateParseError| { | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | })?, | ||||
) | ) | ||||
.and_then(|b| Ok(b.to_py_object(py))) | .and_then(|b| Ok(b.to_py_object(py))) | ||||
.or_else(|_| { | .or_else(|_| { | ||||
Err(PyErr::new::<exc::OSError, _>( | Err(PyErr::new::<exc::OSError, _>( | ||||
py, | py, | ||||
"Dirstate error".to_string(), | "Dirstate error".to_string(), | ||||
)) | )) | ||||
}) | }) | ||||
} | } | ||||
def clearambiguoustimes( | def clearambiguoustimes( | ||||
&self, | &self, | ||||
files: PyObject, | files: PyObject, | ||||
now: PyObject | now: PyObject | ||||
) -> PyResult<PyObject> { | ) -> PyResult<PyObject> { | ||||
let files: PyResult<Vec<Vec<u8>>> = files | let files: PyResult<Vec<HgPathBuf>> = files | ||||
.iter(py)? | .iter(py)? | ||||
.map(|filename| { | .map(|filename| { | ||||
Ok(filename?.extract::<PyBytes>(py)?.data(py).to_owned()) | Ok(HgPathBuf::from_bytes( | ||||
filename?.extract::<PyBytes>(py)?.data(py), | |||||
)) | |||||
}) | }) | ||||
.collect(); | .collect(); | ||||
self.inner(py) | self.inner(py) | ||||
.borrow_mut() | .borrow_mut() | ||||
.clear_ambiguous_times(files?, now.extract(py)?); | .clear_ambiguous_times(files?, now.extract(py)?); | ||||
Ok(py.None()) | Ok(py.None()) | ||||
} | } | ||||
// TODO share the reference | // TODO share the reference | ||||
def nonnormalentries(&self) -> PyResult<PyObject> { | def nonnormalentries(&self) -> PyResult<PyObject> { | ||||
let (non_normal, other_parent) = | let (non_normal, other_parent) = | ||||
self.inner(py).borrow().non_normal_other_parent_entries(); | self.inner(py).borrow().non_normal_other_parent_entries(); | ||||
let locals = PyDict::new(py); | let locals = PyDict::new(py); | ||||
locals.set_item( | locals.set_item( | ||||
py, | py, | ||||
"non_normal", | "non_normal", | ||||
non_normal | non_normal | ||||
.iter() | .iter() | ||||
.map(|v| PyBytes::new(py, &v)) | .map(|v| PyBytes::new(py, v.as_ref())) | ||||
.collect::<Vec<PyBytes>>() | .collect::<Vec<PyBytes>>() | ||||
.to_py_object(py), | .to_py_object(py), | ||||
)?; | )?; | ||||
locals.set_item( | locals.set_item( | ||||
py, | py, | ||||
"other_parent", | "other_parent", | ||||
other_parent | other_parent | ||||
.iter() | .iter() | ||||
.map(|v| PyBytes::new(py, &v)) | .map(|v| PyBytes::new(py, v.as_ref())) | ||||
.collect::<Vec<PyBytes>>() | .collect::<Vec<PyBytes>>() | ||||
.to_py_object(py), | .to_py_object(py), | ||||
)?; | )?; | ||||
py.eval("set(non_normal), set(other_parent)", None, Some(&locals)) | py.eval("set(non_normal), set(other_parent)", None, Some(&locals)) | ||||
} | } | ||||
def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> { | def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> { | ||||
let d = d.extract::<PyBytes>(py)?; | let d = d.extract::<PyBytes>(py)?; | ||||
Ok(self | Ok(self | ||||
.inner(py) | .inner(py) | ||||
.borrow_mut() | .borrow_mut() | ||||
.has_tracked_dir(d.data(py)) | .has_tracked_dir(HgPath::new(d.data(py))) | ||||
.to_py_object(py)) | .to_py_object(py)) | ||||
} | } | ||||
def hasdir(&self, d: PyObject) -> PyResult<PyBool> { | def hasdir(&self, d: PyObject) -> PyResult<PyBool> { | ||||
let d = d.extract::<PyBytes>(py)?; | let d = d.extract::<PyBytes>(py)?; | ||||
Ok(self | Ok(self | ||||
.inner(py) | .inner(py) | ||||
.borrow_mut() | .borrow_mut() | ||||
.has_dir(d.data(py)) | .has_dir(HgPath::new(d.data(py))) | ||||
.to_py_object(py)) | .to_py_object(py)) | ||||
} | } | ||||
def parents(&self, st: PyObject) -> PyResult<PyTuple> { | def parents(&self, st: PyObject) -> PyResult<PyTuple> { | ||||
self.inner(py) | self.inner(py) | ||||
.borrow_mut() | .borrow_mut() | ||||
.parents(st.extract::<PyBytes>(py)?.data(py)) | .parents(st.extract::<PyBytes>(py)?.data(py)) | ||||
.and_then(|d| { | .and_then(|d| { | ||||
py, | py, | ||||
"Dirstate error".to_string(), | "Dirstate error".to_string(), | ||||
)), | )), | ||||
} | } | ||||
} | } | ||||
def filefoldmapasdict(&self) -> PyResult<PyDict> { | def filefoldmapasdict(&self) -> PyResult<PyDict> { | ||||
let dict = PyDict::new(py); | let dict = PyDict::new(py); | ||||
for (key, value) in | for (key, value) in self.borrow_mut(py)?.build_file_fold_map().iter() { | ||||
self.borrow_mut(py)?.build_file_fold_map().iter() | dict.set_item(py, key.as_ref().to_vec(), value.as_ref().to_vec())?; | ||||
{ | |||||
dict.set_item(py, key, value)?; | |||||
} | } | ||||
Ok(dict) | Ok(dict) | ||||
} | } | ||||
def __len__(&self) -> PyResult<usize> { | def __len__(&self) -> PyResult<usize> { | ||||
Ok(self.inner(py).borrow().len()) | Ok(self.inner(py).borrow().len()) | ||||
} | } | ||||
def __contains__(&self, key: PyObject) -> PyResult<bool> { | def __contains__(&self, key: PyObject) -> PyResult<bool> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
Ok(self.inner(py).borrow().contains_key(key.data(py))) | Ok(self.inner(py).borrow().contains_key(HgPath::new(key.data(py)))) | ||||
} | } | ||||
def __getitem__(&self, key: PyObject) -> PyResult<PyObject> { | def __getitem__(&self, key: PyObject) -> PyResult<PyObject> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
let key = key.data(py); | let key = HgPath::new(key.data(py)); | ||||
match self.inner(py).borrow().get(key) { | match self.inner(py).borrow().get(key) { | ||||
Some(entry) => { | Some(entry) => { | ||||
// Explicitly go through u8 first, then cast to | // Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char`. | // platform-specific `c_char`. | ||||
let state: u8 = entry.state.into(); | let state: u8 = entry.state.into(); | ||||
Ok(decapsule_make_dirstate_tuple(py)?( | Ok(decapsule_make_dirstate_tuple(py)?( | ||||
state as c_char, | state as c_char, | ||||
entry.mode, | entry.mode, | ||||
entry.size, | entry.size, | ||||
entry.mtime, | entry.mtime, | ||||
)) | )) | ||||
}, | }, | ||||
None => Err(PyErr::new::<exc::KeyError, _>( | None => Err(PyErr::new::<exc::KeyError, _>( | ||||
py, | py, | ||||
String::from_utf8_lossy(key), | String::from_utf8_lossy(key.as_bytes()), | ||||
)), | )), | ||||
} | } | ||||
} | } | ||||
def keys(&self) -> PyResult<DirstateMapKeysIterator> { | def keys(&self) -> PyResult<DirstateMapKeysIterator> { | ||||
DirstateMapKeysIterator::from_inner( | DirstateMapKeysIterator::from_inner( | ||||
py, | py, | ||||
Some(DirstateMapLeakedRef::new(py, &self)), | Some(DirstateMapLeakedRef::new(py, &self)), | ||||
), | ), | ||||
) | ) | ||||
} | } | ||||
// TODO all copymap* methods, see docstring above | // TODO all copymap* methods, see docstring above | ||||
def copymapcopy(&self) -> PyResult<PyDict> { | def copymapcopy(&self) -> PyResult<PyDict> { | ||||
let dict = PyDict::new(py); | let dict = PyDict::new(py); | ||||
for (key, value) in self.inner(py).borrow().copy_map.iter() { | for (key, value) in self.inner(py).borrow().copy_map.iter() { | ||||
dict.set_item(py, PyBytes::new(py, key), PyBytes::new(py, value))?; | dict.set_item( | ||||
py, | |||||
PyBytes::new(py, key.as_ref()), | |||||
PyBytes::new(py, value.as_ref()), | |||||
)?; | |||||
} | } | ||||
Ok(dict) | Ok(dict) | ||||
} | } | ||||
def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> { | def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
match self.inner(py).borrow().copy_map.get(key.data(py)) { | match self.inner(py).borrow().copy_map.get(HgPath::new(key.data(py))) { | ||||
Some(copy) => Ok(PyBytes::new(py, copy)), | Some(copy) => Ok(PyBytes::new(py, copy.as_ref())), | ||||
None => Err(PyErr::new::<exc::KeyError, _>( | None => Err(PyErr::new::<exc::KeyError, _>( | ||||
py, | py, | ||||
String::from_utf8_lossy(key.data(py)), | String::from_utf8_lossy(key.data(py)), | ||||
)), | )), | ||||
} | } | ||||
} | } | ||||
def copymap(&self) -> PyResult<CopyMap> { | def copymap(&self) -> PyResult<CopyMap> { | ||||
CopyMap::from_inner(py, self.clone_ref(py)) | CopyMap::from_inner(py, self.clone_ref(py)) | ||||
} | } | ||||
def copymaplen(&self) -> PyResult<usize> { | def copymaplen(&self) -> PyResult<usize> { | ||||
Ok(self.inner(py).borrow().copy_map.len()) | Ok(self.inner(py).borrow().copy_map.len()) | ||||
} | } | ||||
def copymapcontains(&self, key: PyObject) -> PyResult<bool> { | def copymapcontains(&self, key: PyObject) -> PyResult<bool> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
Ok(self.inner(py).borrow().copy_map.contains_key(key.data(py))) | Ok(self | ||||
.inner(py) | |||||
.borrow() | |||||
.copy_map | |||||
.contains_key(HgPath::new(key.data(py)))) | |||||
} | } | ||||
def copymapget( | def copymapget( | ||||
&self, | &self, | ||||
key: PyObject, | key: PyObject, | ||||
default: Option<PyObject> | default: Option<PyObject> | ||||
) -> PyResult<Option<PyObject>> { | ) -> PyResult<Option<PyObject>> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
match self.inner(py).borrow().copy_map.get(key.data(py)) { | match self | ||||
Some(copy) => Ok(Some(PyBytes::new(py, copy).into_object())), | .inner(py) | ||||
.borrow() | |||||
.copy_map | |||||
.get(HgPath::new(key.data(py))) | |||||
{ | |||||
Some(copy) => Ok(Some( | |||||
PyBytes::new(py, copy.as_ref()).into_object(), | |||||
)), | |||||
None => Ok(default), | None => Ok(default), | ||||
} | } | ||||
} | } | ||||
def copymapsetitem( | def copymapsetitem( | ||||
&self, | &self, | ||||
key: PyObject, | key: PyObject, | ||||
value: PyObject | value: PyObject | ||||
) -> PyResult<PyObject> { | ) -> PyResult<PyObject> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
let value = value.extract::<PyBytes>(py)?; | let value = value.extract::<PyBytes>(py)?; | ||||
self.inner(py) | self.inner(py).borrow_mut().copy_map.insert( | ||||
.borrow_mut() | HgPathBuf::from_bytes(key.data(py)), | ||||
.copy_map | HgPathBuf::from_bytes(value.data(py)), | ||||
.insert(key.data(py).to_vec(), value.data(py).to_vec()); | ); | ||||
Ok(py.None()) | Ok(py.None()) | ||||
} | } | ||||
def copymappop( | def copymappop( | ||||
&self, | &self, | ||||
key: PyObject, | key: PyObject, | ||||
default: Option<PyObject> | default: Option<PyObject> | ||||
) -> PyResult<Option<PyObject>> { | ) -> PyResult<Option<PyObject>> { | ||||
let key = key.extract::<PyBytes>(py)?; | let key = key.extract::<PyBytes>(py)?; | ||||
match self.inner(py).borrow_mut().copy_map.remove(key.data(py)) { | match self | ||||
.inner(py) | |||||
.borrow_mut() | |||||
.copy_map | |||||
.remove(HgPath::new(key.data(py))) | |||||
{ | |||||
Some(_) => Ok(None), | Some(_) => Ok(None), | ||||
None => Ok(default), | None => Ok(default), | ||||
} | } | ||||
} | } | ||||
def copymapiter(&self) -> PyResult<CopyMapKeysIterator> { | def copymapiter(&self) -> PyResult<CopyMapKeysIterator> { | ||||
CopyMapKeysIterator::from_inner( | CopyMapKeysIterator::from_inner( | ||||
py, | py, | ||||
) | ) | ||||
} | } | ||||
}); | }); | ||||
impl DirstateMap { | impl DirstateMap { | ||||
fn translate_key( | fn translate_key( | ||||
py: Python, | py: Python, | ||||
res: (&Vec<u8>, &DirstateEntry), | res: (&HgPathBuf, &DirstateEntry), | ||||
) -> PyResult<Option<PyBytes>> { | ) -> PyResult<Option<PyBytes>> { | ||||
Ok(Some(PyBytes::new(py, res.0))) | Ok(Some(PyBytes::new(py, res.0.as_ref()))) | ||||
} | } | ||||
fn translate_key_value( | fn translate_key_value( | ||||
py: Python, | py: Python, | ||||
res: (&Vec<u8>, &DirstateEntry), | res: (&HgPathBuf, &DirstateEntry), | ||||
) -> PyResult<Option<(PyBytes, PyObject)>> { | ) -> PyResult<Option<(PyBytes, PyObject)>> { | ||||
let (f, entry) = res; | let (f, entry) = res; | ||||
// Explicitly go through u8 first, then cast to | // Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char`. | // platform-specific `c_char`. | ||||
let state: u8 = entry.state.into(); | let state: u8 = entry.state.into(); | ||||
Ok(Some(( | Ok(Some(( | ||||
PyBytes::new(py, f), | PyBytes::new(py, f.as_ref()), | ||||
decapsule_make_dirstate_tuple(py)?( | decapsule_make_dirstate_tuple(py)?( | ||||
state as c_char, | state as c_char, | ||||
entry.mode, | entry.mode, | ||||
entry.size, | entry.size, | ||||
entry.mtime, | entry.mtime, | ||||
), | ), | ||||
))) | ))) | ||||
} | } | ||||
} | } | ||||
py_shared_ref!(DirstateMap, RustDirstateMap, inner, DirstateMapLeakedRef,); | py_shared_ref!(DirstateMap, RustDirstateMap, inner, DirstateMapLeakedRef,); | ||||
py_shared_mapping_iterator!( | py_shared_mapping_iterator!( | ||||
DirstateMapKeysIterator, | DirstateMapKeysIterator, | ||||
DirstateMapLeakedRef, | DirstateMapLeakedRef, | ||||
Vec<u8>, | HgPathBuf, | ||||
DirstateEntry, | DirstateEntry, | ||||
DirstateMap::translate_key, | DirstateMap::translate_key, | ||||
Option<PyBytes> | Option<PyBytes> | ||||
); | ); | ||||
py_shared_mapping_iterator!( | py_shared_mapping_iterator!( | ||||
DirstateMapItemsIterator, | DirstateMapItemsIterator, | ||||
DirstateMapLeakedRef, | DirstateMapLeakedRef, | ||||
Vec<u8>, | HgPathBuf, | ||||
DirstateEntry, | DirstateEntry, | ||||
DirstateMap::translate_key_value, | DirstateMap::translate_key_value, | ||||
Option<(PyBytes, PyObject)> | Option<(PyBytes, PyObject)> | ||||
); | ); | ||||
fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<[u8; PARENT_SIZE]> { | fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<[u8; PARENT_SIZE]> { | ||||
let bytes = obj.extract::<PyBytes>(py)?; | let bytes = obj.extract::<PyBytes>(py)?; | ||||
match bytes.data(py).try_into() { | match bytes.data(py).try_into() { | ||||
Ok(s) => Ok(s), | Ok(s) => Ok(s), | ||||
Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())), | Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())), | ||||
} | } | ||||
} | } |
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
py_exception!(rustext, PatternError, RuntimeError); | py_exception!(rustext, PatternError, RuntimeError); | ||||
py_exception!(rustext, PatternFileError, RuntimeError); | py_exception!(rustext, PatternFileError, RuntimeError); | ||||
py_exception!(rustext, HgPathPyError, RuntimeError); | |||||
impl PatternError { | impl PatternError { | ||||
pub fn pynew(py: Python, inner: hg::PatternError) -> PyErr { | pub fn pynew(py: Python, inner: hg::PatternError) -> PyErr { | ||||
match inner { | match inner { | ||||
hg::PatternError::UnsupportedSyntax(m) => { | hg::PatternError::UnsupportedSyntax(m) => { | ||||
PatternError::new(py, ("PatternError", m)) | PatternError::new(py, ("PatternError", m)) | ||||
} | } | ||||
} | } |
// filepatterns.rs | // filepatterns.rs | ||||
// | // | ||||
// Copyright 2019, Georges Racinet <gracinet@anybox.fr>, | // Copyright 2019, Georges Racinet <gracinet@anybox.fr>, | ||||
// Raphaël Gomès <rgomes@octobus.net> | // Raphaël Gomès <rgomes@octobus.net> | ||||
// | // | ||||
// This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
//! Bindings for the `hg::filepatterns` module provided by the | //! Bindings for the `hg::filepatterns` module provided by the | ||||
//! `hg-core` crate. From Python, this will be seen as `rustext.filepatterns` | //! `hg-core` crate. From Python, this will be seen as `rustext.filepatterns` | ||||
//! and can be used as replacement for the the pure `filepatterns` Python module. | //! and can be used as replacement for the the pure `filepatterns` Python module. | ||||
//! | //! | ||||
use crate::exceptions::{PatternError, PatternFileError}; | use crate::exceptions::{HgPathPyError, PatternError, PatternFileError}; | ||||
use cpython::{ | use cpython::{ | ||||
PyBytes, PyDict, PyModule, PyObject, PyResult, PyTuple, Python, ToPyObject, | PyBytes, PyDict, PyErr, PyModule, PyObject, PyResult, PyString, PyTuple, | ||||
Python, ToPyObject, | |||||
}; | }; | ||||
use hg::{build_single_regex, read_pattern_file, LineNumber, PatternTuple}; | use hg::{ | ||||
build_single_regex, read_pattern_file, | |||||
utils::hg_path::{hg_path_to_path_buf, HgPath}, | |||||
LineNumber, PatternTuple, | |||||
}; | |||||
use std::path::PathBuf; | |||||
/// Rust does not like functions with different return signatures. | /// Rust does not like functions with different return signatures. | ||||
/// The 3-tuple version is always returned by the hg-core function, | /// The 3-tuple version is always returned by the hg-core function, | ||||
/// the (potential) conversion is handled at this level since it is not likely | /// the (potential) conversion is handled at this level since it is not likely | ||||
/// to have any measurable impact on performance. | /// to have any measurable impact on performance. | ||||
/// | /// | ||||
/// The Python implementation passes a function reference for `warn` instead | /// The Python implementation passes a function reference for `warn` instead | ||||
/// of a boolean that is used to emit warnings while parsing. The Rust | /// of a boolean that is used to emit warnings while parsing. The Rust | ||||
/// implementation chooses to accumulate the warnings and propagate them to | /// implementation chooses to accumulate the warnings and propagate them to | ||||
/// Python upon completion. See the `readpatternfile` function in `match.py` | /// Python upon completion. See the `readpatternfile` function in `match.py` | ||||
/// for more details. | /// for more details. | ||||
fn read_pattern_file_wrapper( | fn read_pattern_file_wrapper( | ||||
py: Python, | py: Python, | ||||
file_path: PyObject, | file_path: PyObject, | ||||
warn: bool, | warn: bool, | ||||
source_info: bool, | source_info: bool, | ||||
) -> PyResult<PyTuple> { | ) -> PyResult<PyTuple> { | ||||
match read_pattern_file(file_path.extract::<PyBytes>(py)?.data(py), warn) { | let bytes = file_path.extract::<PyBytes>(py)?; | ||||
let path = HgPath::new(bytes.data(py)); | |||||
match read_pattern_file( | |||||
hg_path_to_path_buf(&path) | |||||
.map_err(|e| PyErr::new::<HgPathPyError, _>(py, e.to_string()))?, | |||||
warn, | |||||
) { | |||||
Ok((patterns, warnings)) => { | Ok((patterns, warnings)) => { | ||||
if source_info { | if source_info { | ||||
let itemgetter = |x: &PatternTuple| { | let itemgetter = |x: &PatternTuple| { | ||||
(PyBytes::new(py, &x.0), x.1, PyBytes::new(py, &x.2)) | (PyBytes::new(py, &x.0), x.1, PyBytes::new(py, &x.2)) | ||||
}; | }; | ||||
let results: Vec<(PyBytes, LineNumber, PyBytes)> = | let results: Vec<(PyBytes, LineNumber, PyBytes)> = | ||||
patterns.iter().map(itemgetter).collect(); | patterns.iter().map(itemgetter).collect(); | ||||
return Ok((results, warnings_to_py_bytes(py, &warnings)) | return Ok((results, warnings_to_py_bytes(py, &warnings)) | ||||
.to_py_object(py)); | .to_py_object(py)); | ||||
} | } | ||||
let itemgetter = |x: &PatternTuple| PyBytes::new(py, &x.0); | let itemgetter = |x: &PatternTuple| PyBytes::new(py, &x.0); | ||||
let results: Vec<PyBytes> = | let results: Vec<PyBytes> = | ||||
patterns.iter().map(itemgetter).collect(); | patterns.iter().map(itemgetter).collect(); | ||||
Ok( | Ok( | ||||
(results, warnings_to_py_bytes(py, &warnings)) | (results, warnings_to_py_bytes(py, &warnings)) | ||||
.to_py_object(py), | .to_py_object(py), | ||||
) | ) | ||||
} | } | ||||
Err(e) => Err(PatternFileError::pynew(py, e)), | Err(e) => Err(PatternFileError::pynew(py, e)), | ||||
} | } | ||||
} | } | ||||
fn warnings_to_py_bytes( | fn warnings_to_py_bytes( | ||||
py: Python, | py: Python, | ||||
warnings: &[(Vec<u8>, Vec<u8>)], | warnings: &[(PathBuf, Vec<u8>)], | ||||
) -> Vec<(PyBytes, PyBytes)> { | ) -> Vec<(PyString, PyBytes)> { | ||||
warnings | warnings | ||||
.iter() | .iter() | ||||
.map(|(path, syn)| (PyBytes::new(py, path), PyBytes::new(py, syn))) | .map(|(path, syn)| { | ||||
( | |||||
PyString::new(py, &path.to_string_lossy()), | |||||
PyBytes::new(py, syn), | |||||
) | |||||
}) | |||||
.collect() | .collect() | ||||
} | } | ||||
fn build_single_regex_wrapper( | fn build_single_regex_wrapper( | ||||
py: Python, | py: Python, | ||||
kind: PyObject, | kind: PyObject, | ||||
pat: PyObject, | pat: PyObject, | ||||
globsuffix: PyObject, | globsuffix: PyObject, |
//! | //! | ||||
//! From Python, this will be seen as `mercurial.rustext.parsers` | //! From Python, this will be seen as `mercurial.rustext.parsers` | ||||
//! | //! | ||||
use cpython::{ | use cpython::{ | ||||
exc, PyBytes, PyDict, PyErr, PyInt, PyModule, PyResult, PyTuple, Python, | exc, PyBytes, PyDict, PyErr, PyInt, PyModule, PyResult, PyTuple, Python, | ||||
PythonObject, ToPyObject, | PythonObject, ToPyObject, | ||||
}; | }; | ||||
use hg::{ | use hg::{ | ||||
pack_dirstate, parse_dirstate, DirstateEntry, DirstatePackError, | pack_dirstate, parse_dirstate, utils::hg_path::HgPathBuf, DirstateEntry, | ||||
DirstateParents, DirstateParseError, PARENT_SIZE, | DirstatePackError, DirstateParents, DirstateParseError, PARENT_SIZE, | ||||
}; | }; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::convert::TryInto; | use std::convert::TryInto; | ||||
use libc::c_char; | use libc::c_char; | ||||
use crate::dirstate::{decapsule_make_dirstate_tuple, extract_dirstate}; | use crate::dirstate::{decapsule_make_dirstate_tuple, extract_dirstate}; | ||||
use std::time::Duration; | use std::time::Duration; | ||||
// Explicitly go through u8 first, then cast to | // Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char` because Into<u8> has a specific | // platform-specific `c_char` because Into<u8> has a specific | ||||
// implementation while `as c_char` would just do a naive enum | // implementation while `as c_char` would just do a naive enum | ||||
// cast. | // cast. | ||||
let state: u8 = entry.state.into(); | let state: u8 = entry.state.into(); | ||||
dmap.set_item( | dmap.set_item( | ||||
py, | py, | ||||
PyBytes::new(py, &filename), | PyBytes::new(py, filename.as_ref()), | ||||
decapsule_make_dirstate_tuple(py)?( | decapsule_make_dirstate_tuple(py)?( | ||||
state as c_char, | state as c_char, | ||||
entry.mode, | entry.mode, | ||||
entry.size, | entry.size, | ||||
entry.mtime, | entry.mtime, | ||||
), | ), | ||||
)?; | )?; | ||||
} | } | ||||
for (path, copy_path) in copies { | for (path, copy_path) in copies { | ||||
copymap.set_item( | copymap.set_item( | ||||
py, | py, | ||||
PyBytes::new(py, &path), | PyBytes::new(py, path.as_ref()), | ||||
PyBytes::new(py, ©_path), | PyBytes::new(py, copy_path.as_ref()), | ||||
)?; | )?; | ||||
} | } | ||||
Ok( | Ok( | ||||
(PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2)) | (PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2)) | ||||
.to_py_object(py), | .to_py_object(py), | ||||
) | ) | ||||
} | } | ||||
Err(e) => Err(PyErr::new::<exc::ValueError, _>( | Err(e) => Err(PyErr::new::<exc::ValueError, _>( | ||||
) -> PyResult<PyBytes> { | ) -> PyResult<PyBytes> { | ||||
let p1 = pl.get_item(py, 0).extract::<PyBytes>(py)?; | let p1 = pl.get_item(py, 0).extract::<PyBytes>(py)?; | ||||
let p1: &[u8] = p1.data(py); | let p1: &[u8] = p1.data(py); | ||||
let p2 = pl.get_item(py, 1).extract::<PyBytes>(py)?; | let p2 = pl.get_item(py, 1).extract::<PyBytes>(py)?; | ||||
let p2: &[u8] = p2.data(py); | let p2: &[u8] = p2.data(py); | ||||
let mut dirstate_map = extract_dirstate(py, &dmap)?; | let mut dirstate_map = extract_dirstate(py, &dmap)?; | ||||
let copies: Result<HashMap<Vec<u8>, Vec<u8>>, PyErr> = copymap | let copies: Result<HashMap<HgPathBuf, HgPathBuf>, PyErr> = copymap | ||||
.items(py) | .items(py) | ||||
.iter() | .iter() | ||||
.map(|(key, value)| { | .map(|(key, value)| { | ||||
Ok(( | Ok(( | ||||
key.extract::<PyBytes>(py)?.data(py).to_owned(), | HgPathBuf::from_bytes(key.extract::<PyBytes>(py)?.data(py)), | ||||
value.extract::<PyBytes>(py)?.data(py).to_owned(), | HgPathBuf::from_bytes(value.extract::<PyBytes>(py)?.data(py)), | ||||
)) | )) | ||||
}) | }) | ||||
.collect(); | .collect(); | ||||
if p1.len() != PARENT_SIZE || p2.len() != PARENT_SIZE { | if p1.len() != PARENT_SIZE || p2.len() != PARENT_SIZE { | ||||
return Err(PyErr::new::<exc::ValueError, _>( | return Err(PyErr::new::<exc::ValueError, _>( | ||||
py, | py, | ||||
"expected a 20-byte hash".to_string(), | "expected a 20-byte hash".to_string(), | ||||
{ | { | ||||
// Explicitly go through u8 first, then cast to | // Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char` because Into<u8> has a specific | // platform-specific `c_char` because Into<u8> has a specific | ||||
// implementation while `as c_char` would just do a naive enum | // implementation while `as c_char` would just do a naive enum | ||||
// cast. | // cast. | ||||
let state: u8 = state.into(); | let state: u8 = state.into(); | ||||
dmap.set_item( | dmap.set_item( | ||||
py, | py, | ||||
PyBytes::new(py, &filename[..]), | PyBytes::new(py, filename.as_ref()), | ||||
decapsule_make_dirstate_tuple(py)?( | decapsule_make_dirstate_tuple(py)?( | ||||
state as c_char, | state as c_char, | ||||
mode, | mode, | ||||
size, | size, | ||||
mtime, | mtime, | ||||
), | ), | ||||
)?; | )?; | ||||
} | } |
Is this used? Can you either use, delete or add tests.