##// END OF EJS Templates
rhg: add support for narrow clones and sparse checkouts...
rhg: add support for narrow clones and sparse checkouts This adds a minimal support that can be implemented without parsing the narrowspec. We can parse the narrowspec and add support for more operations later. The reason we need so few code changes is as follows: Most operations need no special treatment of sparse because some of them only read dirstate (`rhg files` without `-r`), which bakes in the filtering, some of them only read store (`rhg files -r`, `rhg cat`), and some of them read no data at all (`rhg root`, `rhg debugrequirements`). `status` is the command that might care about sparse, so we just disable rhg on it. For narrow clones, `rhg files` clearly needs the narrowspec to work correctly, so we fall back. `rhg cat` seems to work consistently with `hg cat` if the file exists. If the file is hidden by narrow spec, the error message is different and confusing, so that's something that we should improve in follow-up patches. Differential Revision: https://phab.mercurial-scm.org/D11764

File last commit:

r47405:b92083ad default
r49238:005ae1a3 default
Show More
copy_tracing.rs
680 lines | 22.5 KiB | application/rls-services+xml | RustLexer
Simon Sapin
copies-rust: add a macro-based unit-testing framework...
r47414 #[cfg(test)]
#[macro_use]
mod tests_support;
#[cfg(test)]
mod tests;
copies-rust: combine the iteration over remove and copies into one...
r46587 use crate::utils::hg_path::HgPath;
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 use crate::utils::hg_path::HgPathBuf;
use crate::Revision;
copies: iterate over children directly (instead of parents)...
r46764 use crate::NULL_REVISION;
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 use bytes_cast::{unaligned, BytesCast};
copies-rust: use the `entry` API for copy information too...
r46768 use im_rc::ordmap::Entry;
copies-rust: use immutable "OrdMap" to store copies information...
r46577 use im_rc::ordmap::OrdMap;
copies-rust: use imrs::OrdSet instead of imrs::HashSet...
r47327 use im_rc::OrdSet;
copies-rust: use immutable "OrdMap" to store copies information...
r46577
copies-rust: parse the changed-file sidedata directly in rust...
r46674 use std::cmp::Ordering;
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 use std::collections::HashMap;
pub type PathCopies = HashMap<HgPathBuf, HgPathBuf>;
copies-rust: tokenize all paths into integer...
r46766 type PathToken = usize;
copies-rust: pre-introduce a PathToken type and use it where applicable...
r46745
copies-rust: implement PartialEqual manually...
r47324 #[derive(Clone, Debug)]
copies-rust: rename TimeStampedPathCopy to CopySource...
r47312 struct CopySource {
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 /// revision at which the copy information was added
rev: Revision,
/// the copy source, (Set to None in case of deletion of the associated
/// key)
copies-rust: pre-introduce a PathToken type and use it where applicable...
r46745 path: Option<PathToken>,
copies-rust: track "overwrites" directly within CopySource...
r47315 /// a set of previous `CopySource.rev` value directly or indirectly
/// overwritten by this one.
copies-rust: use imrs::OrdSet instead of imrs::HashSet...
r47327 overwritten: OrdSet<Revision>,
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
copies-rust: add methods to build and update CopySource...
r47314 impl CopySource {
/// create a new CopySource
///
/// Use this when no previous copy source existed.
fn new(rev: Revision, path: Option<PathToken>) -> Self {
copies-rust: track "overwrites" directly within CopySource...
r47315 Self {
rev,
path,
copies-rust: use imrs::OrdSet instead of imrs::HashSet...
r47327 overwritten: OrdSet::new(),
copies-rust: track "overwrites" directly within CopySource...
r47315 }
copies-rust: add methods to build and update CopySource...
r47314 }
/// create a new CopySource from merging two others
///
/// Use this when merging two InternalPathCopies requires active merging of
/// some entries.
fn new_from_merge(rev: Revision, winner: &Self, loser: &Self) -> Self {
copies-rust: use imrs::OrdSet instead of imrs::HashSet...
r47327 let mut overwritten = OrdSet::new();
copies-rust: track "overwrites" directly within CopySource...
r47315 overwritten.extend(winner.overwritten.iter().copied());
overwritten.extend(loser.overwritten.iter().copied());
overwritten.insert(winner.rev);
overwritten.insert(loser.rev);
copies-rust: add methods to build and update CopySource...
r47314 Self {
rev,
path: winner.path,
copies-rust: track "overwrites" directly within CopySource...
r47315 overwritten: overwritten,
copies-rust: add methods to build and update CopySource...
r47314 }
}
/// Update the value of a pre-existing CopySource
///
/// Use this when recording copy information from parent → child edges
fn overwrite(&mut self, rev: Revision, path: Option<PathToken>) {
copies-rust: track "overwrites" directly within CopySource...
r47315 self.overwritten.insert(self.rev);
copies-rust: add methods to build and update CopySource...
r47314 self.rev = rev;
self.path = path;
}
/// Mark pre-existing copy information as "dropped" by a file deletion
///
/// Use this when recording copy information from parent → child edges
fn mark_delete(&mut self, rev: Revision) {
copies-rust: track "overwrites" directly within CopySource...
r47315 self.overwritten.insert(self.rev);
copies-rust: add methods to build and update CopySource...
r47314 self.rev = rev;
self.path = None;
}
copies-rust: track "overwrites" directly within CopySource...
r47315
copies-rust: record "overwritten" information from both side on delete...
r47323 /// Mark pre-existing copy information as "dropped" by a file deletion
///
/// Use this when recording copy information from parent → child edges
fn mark_delete_with_pair(&mut self, rev: Revision, other: &Self) {
self.overwritten.insert(self.rev);
if other.rev != rev {
self.overwritten.insert(other.rev);
}
self.overwritten.extend(other.overwritten.iter().copied());
self.rev = rev;
self.path = None;
}
copies-rust: track "overwrites" directly within CopySource...
r47315 fn is_overwritten_by(&self, other: &Self) -> bool {
other.overwritten.contains(&self.rev)
}
copies-rust: add methods to build and update CopySource...
r47314 }
copies-rust: implement PartialEqual manually...
r47324 // For the same "dest", content generated for a given revision will always be
// the same.
impl PartialEq for CopySource {
fn eq(&self, other: &Self) -> bool {
#[cfg(debug_assertions)]
{
if self.rev == other.rev {
debug_assert!(self.path == other.path);
debug_assert!(self.overwritten == other.overwritten);
}
}
self.rev == other.rev
}
}
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 /// maps CopyDestination to Copy Source (+ a "timestamp" for the operation)
copies-rust: rename TimeStampedPathCopy to CopySource...
r47312 type InternalPathCopies = OrdMap<PathToken, CopySource>;
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556
copies-rust: combine the iteration over remove and copies into one...
r46587 /// Represent active changes that affect the copy tracing.
enum Action<'a> {
/// The parent ? children edge is removing a file
///
/// (actually, this could be the edge from the other parent, but it does
/// not matters)
Removed(&'a HgPath),
/// The parent ? children edge introduce copy information between (dest,
/// source)
copies-rust: yield both p1 and p2 copies in `ChangedFiles.actions()`...
r47320 CopiedFromP1(&'a HgPath, &'a HgPath),
CopiedFromP2(&'a HgPath, &'a HgPath),
copies-rust: combine the iteration over remove and copies into one...
r46587 }
copies-rust: encapsulate internal sets on `changes`...
r46589 /// This express the possible "special" case we can get in a merge
///
/// See mercurial/metadata.py for details on these values.
#[derive(PartialEq)]
enum MergeCase {
/// Merged: file had history on both side that needed to be merged
Merged,
/// Salvaged: file was candidate for deletion, but survived the merge
Salvaged,
/// Normal: Not one of the two cases above
Normal,
}
copies-rust: parse the changed-file sidedata directly in rust...
r46674 const COPY_MASK: u8 = 3;
const P1_COPY: u8 = 2;
const P2_COPY: u8 = 3;
const ACTION_MASK: u8 = 28;
const REMOVED: u8 = 12;
const MERGED: u8 = 8;
const SALVAGED: u8 = 16;
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 #[derive(BytesCast)]
#[repr(C)]
struct ChangedFilesIndexEntry {
flags: u8,
copies-rust: parse the changed-file sidedata directly in rust...
r46674
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 /// Only the end position is stored. The start is at the end of the
/// previous entry.
destination_path_end_position: unaligned::U32Be,
copies-rust: parse the changed-file sidedata directly in rust...
r46674
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 source_index_entry_position: unaligned::U32Be,
}
fn _static_assert_size_of() {
let _ = std::mem::transmute::<ChangedFilesIndexEntry, [u8; 9]>;
}
copies-rust: parse the changed-file sidedata directly in rust...
r46674
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 /// Represents the files affected by a changeset.
///
/// This holds a subset of `mercurial.metadata.ChangingFiles` as we do not need
/// all the data categories tracked by it.
pub struct ChangedFiles<'a> {
index: &'a [ChangedFilesIndexEntry],
paths: &'a [u8],
}
copies-rust: parse the changed-file sidedata directly in rust...
r46674
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 impl<'a> ChangedFiles<'a> {
pub fn new(data: &'a [u8]) -> Self {
let (header, rest) = unaligned::U32Be::from_bytes(data).unwrap();
let nb_index_entries = header.get() as usize;
let (index, paths) =
ChangedFilesIndexEntry::slice_from_bytes(rest, nb_index_entries)
.unwrap();
Self { index, paths }
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
pub fn new_empty() -> Self {
ChangedFiles {
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 index: &[],
paths: &[],
copies-rust: parse the changed-file sidedata directly in rust...
r46674 }
}
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 /// Internal function to return the filename of the entry at a given index
fn path(&self, idx: usize) -> &HgPath {
let start = if idx == 0 {
0
copies-rust: parse the changed-file sidedata directly in rust...
r46674 } else {
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 self.index[idx - 1].destination_path_end_position.get() as usize
};
let end = self.index[idx].destination_path_end_position.get() as usize;
HgPath::new(&self.paths[start..end])
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
copies-rust: combine the iteration over remove and copies into one...
r46587
/// Return an iterator over all the `Action` in this instance.
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 fn iter_actions(&self) -> impl Iterator<Item = Action> {
self.index.iter().enumerate().flat_map(move |(idx, entry)| {
let path = self.path(idx);
if (entry.flags & ACTION_MASK) == REMOVED {
Some(Action::Removed(path))
} else if (entry.flags & COPY_MASK) == P1_COPY {
let source_idx =
entry.source_index_entry_position.get() as usize;
Some(Action::CopiedFromP1(path, self.path(source_idx)))
} else if (entry.flags & COPY_MASK) == P2_COPY {
let source_idx =
entry.source_index_entry_position.get() as usize;
Some(Action::CopiedFromP2(path, self.path(source_idx)))
} else {
None
}
})
copies-rust: combine the iteration over remove and copies into one...
r46587 }
copies-rust: encapsulate internal sets on `changes`...
r46589
/// return the MergeCase value associated with a filename
fn get_merge_case(&self, path: &HgPath) -> MergeCase {
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 if self.index.is_empty() {
copies-rust: encapsulate internal sets on `changes`...
r46589 return MergeCase::Normal;
}
copies-rust: parse the changed-file sidedata directly in rust...
r46674 let mut low_part = 0;
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 let mut high_part = self.index.len();
copies-rust: parse the changed-file sidedata directly in rust...
r46674
while low_part < high_part {
let cursor = (low_part + high_part - 1) / 2;
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 match path.cmp(self.path(cursor)) {
copies-rust: parse the changed-file sidedata directly in rust...
r46674 Ordering::Less => low_part = cursor + 1,
Ordering::Greater => high_part = cursor,
Ordering::Equal => {
Simon Sapin
copies-rust: rewrite ChangedFiles binary parsing...
r47413 return match self.index[cursor].flags & ACTION_MASK {
copies-rust: parse the changed-file sidedata directly in rust...
r46674 MERGED => MergeCase::Merged,
SALVAGED => MergeCase::Salvaged,
_ => MergeCase::Normal,
};
}
}
}
MergeCase::Normal
copies-rust: encapsulate internal sets on `changes`...
r46589 }
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
copies-rust: tokenize all paths into integer...
r46766 /// A small "tokenizer" responsible of turning full HgPath into lighter
/// PathToken
///
/// Dealing with small object, like integer is much faster, so HgPath input are
/// turned into integer "PathToken" and converted back in the end.
#[derive(Clone, Debug, Default)]
struct TwoWayPathMap {
token: HashMap<HgPathBuf, PathToken>,
path: Vec<HgPathBuf>,
}
impl TwoWayPathMap {
fn tokenize(&mut self, path: &HgPath) -> PathToken {
match self.token.get(path) {
Some(a) => *a,
None => {
let a = self.token.len();
let buf = path.to_owned();
self.path.push(buf.clone());
self.token.insert(buf, a);
a
}
}
}
fn untokenize(&self, token: PathToken) -> &HgPathBuf {
Martin von Zweigbergk
copies-rust: remove an unnecessary format!() inside assert!()...
r47363 assert!(token < self.path.len(), "Unknown token: {}", token);
copies-rust: tokenize all paths into integer...
r46766 &self.path[token]
}
}
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 /// Same as mercurial.copies._combine_changeset_copies, but in Rust.
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 pub struct CombineChangesetCopies {
all_copies: HashMap<Revision, InternalPathCopies>,
path_map: TwoWayPathMap,
children_count: HashMap<Revision, usize>,
}
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 impl CombineChangesetCopies {
pub fn new(children_count: HashMap<Revision, usize>) -> Self {
Self {
all_copies: HashMap::new(),
path_map: TwoWayPathMap::default(),
children_count,
}
}
copies-rust: tokenize all paths into integer...
r46766
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 /// Combined the given `changes` data specific to `rev` with the data
/// previously given for its parents (and transitively, its ancestors).
pub fn add_revision(
&mut self,
rev: Revision,
p1: Revision,
p2: Revision,
changes: ChangedFiles<'_>,
) {
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 self.add_revision_inner(rev, p1, p2, changes.iter_actions(), |path| {
changes.get_merge_case(path)
})
}
/// Separated out from `add_revsion` so that unit tests can call this
/// without synthetizing a `ChangedFiles` in binary format.
fn add_revision_inner<'a>(
&mut self,
rev: Revision,
p1: Revision,
p2: Revision,
copy_actions: impl Iterator<Item = Action<'a>>,
get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
) {
copies-rust: get the parents' copies earlier...
r47317 // Retrieve data computed in a previous iteration
let p1_copies = match p1 {
NULL_REVISION => None,
_ => get_and_clean_parent_copies(
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 &mut self.all_copies,
&mut self.children_count,
copies: iterate over children directly (instead of parents)...
r46764 p1,
copies-rust: get the parents' copies earlier...
r47317 ), // will be None if the vertex is not to be traversed
};
let p2_copies = match p2 {
NULL_REVISION => None,
_ => get_and_clean_parent_copies(
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 &mut self.all_copies,
&mut self.children_count,
copies: iterate over children directly (instead of parents)...
r46764 p2,
copies-rust: get the parents' copies earlier...
r47317 ), // will be None if the vertex is not to be traversed
};
copies-rust: use matching to select the final copies information...
r47318 // combine it with data for that revision
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 let (p1_copies, p2_copies) = chain_changes(
&mut self.path_map,
p1_copies,
p2_copies,
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 copy_actions,
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 rev,
);
copies-rust: use matching to select the final copies information...
r47318 let copies = match (p1_copies, p2_copies) {
(None, None) => None,
(c, None) => c,
(None, c) => c,
(Some(p1_copies), Some(p2_copies)) => Some(merge_copies_dict(
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 &self.path_map,
rev,
p2_copies,
p1_copies,
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 get_merge_case,
copies-rust: use matching to select the final copies information...
r47318 )),
};
if let Some(c) = copies {
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 self.all_copies.insert(rev, c);
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
}
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 /// Drop intermediate data (such as which revision a copy was from) and
/// return the final mapping.
pub fn finish(mut self, target_rev: Revision) -> PathCopies {
let tt_result = self
.all_copies
.remove(&target_rev)
.expect("target revision was not processed");
let mut result = PathCopies::default();
for (dest, tt_source) in tt_result {
if let Some(path) = tt_source.path {
let path_dest = self.path_map.untokenize(dest).to_owned();
let path_path = self.path_map.untokenize(path).to_owned();
result.insert(path_dest, path_path);
}
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
Simon Sapin
copies-rust: split up combine_changeset_copies function into a struct...
r47329 result
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
}
copies: iterate over children directly (instead of parents)...
r46764 /// fetch previous computed information
///
/// If no other children are expected to need this information, we drop it from
/// the cache.
///
/// If parent is not part of the set we are expected to walk, return None.
fn get_and_clean_parent_copies(
copies-rust: rename TimeStampedPathCopies to InternalPathCopies...
r47311 all_copies: &mut HashMap<Revision, InternalPathCopies>,
copies: iterate over children directly (instead of parents)...
r46764 children_count: &mut HashMap<Revision, usize>,
parent_rev: Revision,
copies-rust: rename TimeStampedPathCopies to InternalPathCopies...
r47311 ) -> Option<InternalPathCopies> {
copies: iterate over children directly (instead of parents)...
r46764 let count = children_count.get_mut(&parent_rev)?;
*count -= 1;
if *count == 0 {
match all_copies.remove(&parent_rev) {
Some(c) => Some(c),
copies-rust: rename TimeStampedPathCopies to InternalPathCopies...
r47311 None => Some(InternalPathCopies::default()),
copies: iterate over children directly (instead of parents)...
r46764 }
} else {
match all_copies.get(&parent_rev) {
Some(c) => Some(c.clone()),
copies-rust: rename TimeStampedPathCopies to InternalPathCopies...
r47311 None => Some(InternalPathCopies::default()),
copies: iterate over children directly (instead of parents)...
r46764 }
}
}
copies-rust: extract the processing of a ChangedFiles in its own function...
r46676 /// Combine ChangedFiles with some existing PathCopies information and return
/// the result
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 fn chain_changes<'a>(
copies-rust: tokenize all paths into integer...
r46766 path_map: &mut TwoWayPathMap,
copies-rust: process copy information of both parent at the same time...
r47321 base_p1_copies: Option<InternalPathCopies>,
base_p2_copies: Option<InternalPathCopies>,
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 copy_actions: impl Iterator<Item = Action<'a>>,
copies-rust: extract the processing of a ChangedFiles in its own function...
r46676 current_rev: Revision,
copies-rust: process copy information of both parent at the same time...
r47321 ) -> (Option<InternalPathCopies>, Option<InternalPathCopies>) {
// Fast path the "nothing to do" case.
if let (None, None) = (&base_p1_copies, &base_p2_copies) {
return (None, None);
}
let mut p1_copies = base_p1_copies.clone();
let mut p2_copies = base_p2_copies.clone();
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 for action in copy_actions {
copies-rust: extract the processing of a ChangedFiles in its own function...
r46676 match action {
copies-rust: yield both p1 and p2 copies in `ChangedFiles.actions()`...
r47320 Action::CopiedFromP1(path_dest, path_source) => {
copies-rust: process copy information of both parent at the same time...
r47321 match &mut p1_copies {
None => (), // This is not a vertex we should proceed.
Some(copies) => add_one_copy(
copies-rust: yield both p1 and p2 copies in `ChangedFiles.actions()`...
r47320 current_rev,
path_map,
copies-rust: process copy information of both parent at the same time...
r47321 copies,
base_p1_copies.as_ref().unwrap(),
copies-rust: yield both p1 and p2 copies in `ChangedFiles.actions()`...
r47320 path_dest,
path_source,
),
copies-rust: process copy information of both parent at the same time...
r47321 }
copies-rust: yield both p1 and p2 copies in `ChangedFiles.actions()`...
r47320 }
Action::CopiedFromP2(path_dest, path_source) => {
copies-rust: process copy information of both parent at the same time...
r47321 match &mut p2_copies {
None => (), // This is not a vertex we should proceed.
Some(copies) => add_one_copy(
copies-rust: yield both p1 and p2 copies in `ChangedFiles.actions()`...
r47320 current_rev,
path_map,
copies-rust: process copy information of both parent at the same time...
r47321 copies,
base_p2_copies.as_ref().unwrap(),
copies-rust: yield both p1 and p2 copies in `ChangedFiles.actions()`...
r47320 path_dest,
path_source,
),
copies-rust: process copy information of both parent at the same time...
r47321 }
copies-rust: extract the processing of a ChangedFiles in its own function...
r46676 }
copies-rust: tokenize all paths into integer...
r46766 Action::Removed(deleted_path) => {
copies-rust: extract the processing of a ChangedFiles in its own function...
r46676 // We must drop copy information for removed file.
//
// We need to explicitly record them as dropped to
// propagate this information when merging two
copies-rust: rename TimeStampedPathCopies to InternalPathCopies...
r47311 // InternalPathCopies object.
copies-rust: tokenize all paths into integer...
r46766 let deleted = path_map.tokenize(deleted_path);
copies-rust: refactor the "deletion" case...
r47322
let p1_entry = match &mut p1_copies {
None => None,
Some(copies) => match copies.entry(deleted) {
Entry::Occupied(e) => Some(e),
Entry::Vacant(_) => None,
},
};
let p2_entry = match &mut p2_copies {
None => None,
Some(copies) => match copies.entry(deleted) {
Entry::Occupied(e) => Some(e),
Entry::Vacant(_) => None,
},
copies-rust: process copy information of both parent at the same time...
r47321 };
copies-rust: refactor the "deletion" case...
r47322
match (p1_entry, p2_entry) {
(None, None) => (),
(Some(mut e), None) => {
e.get_mut().mark_delete(current_rev)
copies-rust: process copy information of both parent at the same time...
r47321 }
copies-rust: refactor the "deletion" case...
r47322 (None, Some(mut e)) => {
e.get_mut().mark_delete(current_rev)
}
(Some(mut e1), Some(mut e2)) => {
copies-rust: record "overwritten" information from both side on delete...
r47323 let cs1 = e1.get_mut();
let cs2 = e2.get();
copies-rust: use simpler overwrite when value on both side are identical...
r47326 if cs1 == cs2 {
cs1.mark_delete(current_rev);
} else {
cs1.mark_delete_with_pair(current_rev, &cs2);
}
copies-rust: record "overwritten" information from both side on delete...
r47323 e2.insert(cs1.clone());
copies-rust: refactor the "deletion" case...
r47322 }
}
copies-rust: extract the processing of a ChangedFiles in its own function...
r46676 }
}
}
copies-rust: process copy information of both parent at the same time...
r47321 (p1_copies, p2_copies)
copies-rust: extract the processing of a ChangedFiles in its own function...
r46676 }
copies-rust: extract the processing of a single copy information...
r47319 // insert one new copy information in an InternalPathCopies
//
// This deal with chaining and overwrite.
fn add_one_copy(
current_rev: Revision,
path_map: &mut TwoWayPathMap,
copies: &mut InternalPathCopies,
base_copies: &InternalPathCopies,
path_dest: &HgPath,
path_source: &HgPath,
) {
let dest = path_map.tokenize(path_dest);
let source = path_map.tokenize(path_source);
let entry;
if let Some(v) = base_copies.get(&source) {
entry = match &v.path {
Some(path) => Some((*(path)).to_owned()),
None => Some(source.to_owned()),
}
} else {
entry = Some(source.to_owned());
}
// Each new entry is introduced by the children, we
// record this information as we will need it to take
// the right decision when merging conflicting copy
// information. See merge_copies_dict for details.
match copies.entry(dest) {
Entry::Vacant(slot) => {
let ttpc = CopySource::new(current_rev, entry);
slot.insert(ttpc);
}
Entry::Occupied(mut slot) => {
let ttpc = slot.get_mut();
ttpc.overwrite(current_rev, entry);
}
}
}
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 /// merge two copies-mapping together, minor and major
///
/// In case of conflict, value from "major" will be picked, unless in some
/// cases. See inline documentation for details.
copies-rust: remove the ancestor Oracle logic...
r47316 fn merge_copies_dict(
copies-rust: tokenize all paths into integer...
r46766 path_map: &TwoWayPathMap,
copies-rust: make the comparison aware of the revision being current merged...
r46771 current_merge: Revision,
Simon Sapin
copies-rust: extract generic map merge logic from merge_copies_dict...
r47328 minor: InternalPathCopies,
major: InternalPathCopies,
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy,
copies-rust: rename TimeStampedPathCopies to InternalPathCopies...
r47311 ) -> InternalPathCopies {
Simon Sapin
copies-rust: extract generic map merge logic from merge_copies_dict...
r47328 use crate::utils::{ordmap_union_with_merge, MergeResult};
Simon Sapin
copies-rust: pass `PathToken` around by value...
r47355 ordmap_union_with_merge(minor, major, |&dest, src_minor, src_major| {
Simon Sapin
copies-rust: extract generic map merge logic from merge_copies_dict...
r47328 let (pick, overwrite) = compare_value(
current_merge,
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 || get_merge_case(path_map.untokenize(dest)),
Simon Sapin
copies-rust: extract generic map merge logic from merge_copies_dict...
r47328 src_minor,
src_major,
);
if overwrite {
let (winner, loser) = match pick {
MergePick::Major | MergePick::Any => (src_major, src_minor),
MergePick::Minor => (src_minor, src_major),
};
MergeResult::UseNewValue(CopySource::new_from_merge(
copies-rust: remove the ancestor Oracle logic...
r47316 current_merge,
Simon Sapin
copies-rust: extract generic map merge logic from merge_copies_dict...
r47328 winner,
loser,
))
} else {
match pick {
MergePick::Any | MergePick::Major => {
MergeResult::UseRightValue
copies: detect case when a merge decision overwrite previous data...
r47310 }
Simon Sapin
copies-rust: extract generic map merge logic from merge_copies_dict...
r47328 MergePick::Minor => MergeResult::UseLeftValue,
copies-rust: move the mapping merging into a else clause...
r46742 }
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
Simon Sapin
copies-rust: extract generic map merge logic from merge_copies_dict...
r47328 })
copies: introduce a basic Rust function for `combine_changeset_copies`...
r46556 }
copies-rust: extract conflicting value comparison in its own function...
r46741
/// represent the side that should prevail when merging two
copies-rust: rename TimeStampedPathCopies to InternalPathCopies...
r47311 /// InternalPathCopies
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 #[derive(Debug, PartialEq)]
copies-rust: extract conflicting value comparison in its own function...
r46741 enum MergePick {
/// The "major" (p1) side prevails
Major,
/// The "minor" (p2) side prevails
Minor,
/// Any side could be used (because they are the same)
Any,
}
/// decide which side prevails in case of conflicting values
#[allow(clippy::if_same_then_else)]
copies-rust: remove the ancestor Oracle logic...
r47316 fn compare_value(
copies-rust: make the comparison aware of the revision being current merged...
r46771 current_merge: Revision,
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 merge_case_for_dest: impl Fn() -> MergeCase,
copies-rust: rename TimeStampedPathCopy to CopySource...
r47312 src_minor: &CopySource,
src_major: &CopySource,
copies: detect case when a merge decision overwrite previous data...
r47310 ) -> (MergePick, bool) {
copies-rust: make more use of the new comparison property...
r47325 if src_major == src_minor {
(MergePick::Any, false)
} else if src_major.rev == current_merge {
// minor is different according to per minor == major check earlier
debug_assert!(src_minor.rev != current_merge);
// The last value comes the current merge, this value -will- win
// eventually.
(MergePick::Major, true)
copies-rust: make the comparison aware of the revision being current merged...
r46771 } else if src_minor.rev == current_merge {
// The last value comes the current merge, this value -will- win
// eventually.
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Minor, true)
copies-rust: make the comparison aware of the revision being current merged...
r46771 } else if src_major.path == src_minor.path {
copies-rust: make more use of the new comparison property...
r47325 debug_assert!(src_major.rev != src_major.rev);
copies-rust: extract conflicting value comparison in its own function...
r46741 // we have the same value, but from other source;
copies-rust: make more use of the new comparison property...
r47325 if src_major.is_overwritten_by(src_minor) {
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Minor, false)
copies-rust: track "overwrites" directly within CopySource...
r47315 } else if src_minor.is_overwritten_by(src_major) {
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Major, false)
copies-rust: extract conflicting value comparison in its own function...
r46741 } else {
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Any, true)
copies-rust: extract conflicting value comparison in its own function...
r46741 }
} else {
copies-rust: make more use of the new comparison property...
r47325 debug_assert!(src_major.rev != src_major.rev);
Simon Sapin
copies-rust: pass closures and iterators instead of `&ChangedFiles`...
r47356 let action = merge_case_for_dest();
copies: rearrange all value comparison conditional...
r47309 if src_minor.path.is_some()
&& src_major.path.is_none()
&& action == MergeCase::Salvaged
{
copies-rust: extract conflicting value comparison in its own function...
r46741 // If the file is "deleted" in the major side but was
// salvaged by the merge, we keep the minor side alive
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Minor, true)
copies: rearrange all value comparison conditional...
r47309 } else if src_major.path.is_some()
&& src_minor.path.is_none()
&& action == MergeCase::Salvaged
{
copies-rust: extract conflicting value comparison in its own function...
r46741 // If the file is "deleted" in the minor side but was
// salvaged by the merge, unconditionnaly preserve the
// major side.
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Major, true)
copies-rust: track "overwrites" directly within CopySource...
r47315 } else if src_minor.is_overwritten_by(src_major) {
copies: rearrange all value comparison conditional...
r47309 // The information from the minor version are strictly older than
// the major version
if action == MergeCase::Merged {
// If the file was actively merged, its means some non-copy
// activity happened on the other branch. It
// mean the older copy information are still relevant.
//
// The major side wins such conflict.
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Major, true)
copies: rearrange all value comparison conditional...
r47309 } else {
// No activity on the minor branch, pick the newer one.
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Major, false)
copies: rearrange all value comparison conditional...
r47309 }
copies-rust: track "overwrites" directly within CopySource...
r47315 } else if src_major.is_overwritten_by(src_minor) {
copies: rearrange all value comparison conditional...
r47309 if action == MergeCase::Merged {
// If the file was actively merged, its means some non-copy
// activity happened on the other branch. It
// mean the older copy information are still relevant.
//
// The major side wins such conflict.
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Major, true)
copies: rearrange all value comparison conditional...
r47309 } else {
// No activity on the minor branch, pick the newer one.
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Minor, false)
copies: rearrange all value comparison conditional...
r47309 }
} else if src_minor.path.is_none() {
// the minor side has no relevant information, pick the alive one
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Major, true)
copies: rearrange all value comparison conditional...
r47309 } else if src_major.path.is_none() {
// the major side has no relevant information, pick the alive one
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Minor, true)
copies-rust: extract conflicting value comparison in its own function...
r46741 } else {
copies: rearrange all value comparison conditional...
r47309 // by default the major side wins
copies: detect case when a merge decision overwrite previous data...
r47310 (MergePick::Major, true)
copies-rust: extract conflicting value comparison in its own function...
r46741 }
}
}