mirror of
https://github.com/facebook/sapling.git
synced 2024-12-26 22:47:26 +03:00
mutationstore: copy entries automatically on flush
Summary:
Similar to D7121487 (af8ecd5f80
) but works for mutation store. This makes sure at the Rust
layer, mutation entries won't get lost after rebasing or metaeditting a set of
commits where a subset of the commits being edited has mutation relations.
Unlike the Python layer, the Rust layer works for mutation chains. Therefore
some of the tests changes.
Reviewed By: markbt
Differential Revision: D22174991
fbshipit-source-id: d62f7c1071fc71f939ec8771ac5968b992aa253c
This commit is contained in:
parent
2f1d35b06e
commit
868c2b0108
@ -141,15 +141,19 @@ def createsyntheticentry(repo, preds, succ, op, splitting=None, user=None, date=
|
||||
)
|
||||
|
||||
|
||||
def recordentries(repo, entries, skipexisting=True):
|
||||
def recordentries(repo, entries, skipexisting=True, raw=False):
|
||||
count = 0
|
||||
with repo.transaction("record-mutation") as tr:
|
||||
ms = repo._mutationstore
|
||||
if raw:
|
||||
add = ms.addraw
|
||||
else:
|
||||
add = ms.add
|
||||
tr.addfinalize("mutation", lambda _tr: ms.flush())
|
||||
for entry in entries:
|
||||
if skipexisting and ms.has(entry.succ()):
|
||||
continue
|
||||
ms.add(entry)
|
||||
add(entry)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@ -656,7 +660,7 @@ def toposort(repo, items, nodefn=None):
|
||||
def unbundle(repo, bundledata):
|
||||
if enabled(repo):
|
||||
entries = mutationstore.unbundle(bundledata)
|
||||
recordentries(repo, entries, skipexisting=True)
|
||||
recordentries(repo, entries, skipexisting=True, raw=True)
|
||||
|
||||
|
||||
def entriesforbundle(repo, nodes):
|
||||
@ -811,7 +815,7 @@ def convertfromobsmarkers(repo):
|
||||
)
|
||||
|
||||
with repo.lock():
|
||||
count = recordentries(repo, entries, skipexisting=False)
|
||||
count = recordentries(repo, entries, skipexisting=False, raw=True)
|
||||
|
||||
return (len(entries), len(newmut), count)
|
||||
|
||||
|
@ -186,6 +186,12 @@ py_class!(class mutationstore |py| {
|
||||
Ok(PyNone)
|
||||
}
|
||||
|
||||
def addraw(&self, entry: &mutationentry) -> PyResult<PyNone> {
|
||||
let mut ms = self.mut_store(py).borrow_mut();
|
||||
ms.add_raw(entry.entry(py)).map_pyerr(py)?;
|
||||
Ok(PyNone)
|
||||
}
|
||||
|
||||
def flush(&self) -> PyResult<PyNone> {
|
||||
let mut ms = self.mut_store(py).borrow_mut();
|
||||
ms.flush().map_pyerr(py)?;
|
||||
|
@ -5,6 +5,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.20"
|
||||
bitflags = "1"
|
||||
dag = { path = "../dag" }
|
||||
indexedlog = { path = "../indexedlog" }
|
||||
thiserror = "1.0.5"
|
||||
|
@ -28,13 +28,17 @@
|
||||
//! be ignored.
|
||||
|
||||
use anyhow::Result;
|
||||
use bitflags::bitflags;
|
||||
use dag::namedag::MemNameDag;
|
||||
use dag::ops::DagAddHeads;
|
||||
use dag::DagAlgorithm;
|
||||
use dag::Set;
|
||||
use dag::VertexName;
|
||||
use indexedlog::{
|
||||
log::{self as ilog, IndexDef, IndexOutput, Log},
|
||||
DefaultOpenOptions,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
@ -46,6 +50,17 @@ pub use indexedlog::Repair;
|
||||
|
||||
pub struct MutationStore {
|
||||
log: Log,
|
||||
pending: Vec<MutationEntry>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct DagFlags: u8 {
|
||||
/// Include successors.
|
||||
const SUCCESSORS = 0b1;
|
||||
|
||||
/// Include predecessors.
|
||||
const PREDECESSORS = 0b10;
|
||||
}
|
||||
}
|
||||
|
||||
const INDEX_PRED: usize = 0;
|
||||
@ -105,10 +120,20 @@ impl DefaultOpenOptions<ilog::OpenOptions> for MutationStore {
|
||||
impl MutationStore {
|
||||
pub fn open(path: impl AsRef<Path>) -> Result<MutationStore> {
|
||||
let log = Self::default_open_options().open(path.as_ref())?;
|
||||
Ok(MutationStore { log })
|
||||
let pending = Vec::new();
|
||||
Ok(MutationStore { log, pending })
|
||||
}
|
||||
|
||||
/// Add an entry. Consider adding automatic entries based on this entry.
|
||||
/// See `flush` for automatic entries.
|
||||
pub fn add(&mut self, entry: &MutationEntry) -> Result<()> {
|
||||
self.add_raw(entry)?;
|
||||
self.pending.push(entry.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add an entry. Do not consider adding automatic entries.
|
||||
pub fn add_raw(&mut self, entry: &MutationEntry) -> Result<()> {
|
||||
let mut buf = Vec::with_capacity(types::mutation::DEFAULT_ENTRY_SIZE);
|
||||
entry.serialize(&mut buf)?;
|
||||
self.log.append(buf.as_slice())?;
|
||||
@ -116,7 +141,72 @@ impl MutationStore {
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> Result<()> {
|
||||
// If P -> Q, X -> Y are being added, and there is an existing chain P
|
||||
// -> ... -> X, add a Q -> Y marker automatically.
|
||||
// Note: P must not equal to X or Y.
|
||||
//
|
||||
// See also D7121487.
|
||||
|
||||
// Prepare for calculation.
|
||||
let mut pred_map = HashMap::with_capacity(self.pending.len()); // node -> index
|
||||
let mut pred_nodes = Vec::with_capacity(self.pending.len());
|
||||
for (i, entry) in self.pending.iter().enumerate() {
|
||||
let pred = entry.preds[0];
|
||||
pred_map.insert(pred, i);
|
||||
pred_nodes.push(pred);
|
||||
}
|
||||
let pred_set =
|
||||
Set::from_static_names(pred_nodes.iter().map(|p| VertexName::copy_from(p.as_ref())));
|
||||
let dag = self.get_dag_advanced(pred_nodes, DagFlags::SUCCESSORS)?;
|
||||
let mut new_entries = Vec::new();
|
||||
|
||||
// Scan through "X"s.
|
||||
for entry in &self.pending {
|
||||
let x = entry.preds[0];
|
||||
// Find all "P"s, as in P -> ... -> X, and X -> Y.
|
||||
let x_set = VertexName::copy_from(x.as_ref()).into();
|
||||
let ps = dag.ancestors(x_set)? & pred_set.clone();
|
||||
for p in ps.iter()? {
|
||||
let p = Node::from_slice(p?.as_ref())?;
|
||||
let y = entry.succ;
|
||||
if p == x || p == y {
|
||||
continue;
|
||||
}
|
||||
let q = self.pending[pred_map[&p]].succ;
|
||||
if q == x || q == y || q == p {
|
||||
continue;
|
||||
}
|
||||
// Copy P -> X to Q -> Y.
|
||||
let copy_entry = match self.get(x)? {
|
||||
Some(entry) => entry,
|
||||
_ => continue,
|
||||
};
|
||||
let op = if copy_entry.op.ends_with("-copy") {
|
||||
copy_entry.op.clone()
|
||||
} else {
|
||||
format!("{}-copy", ©_entry.op)
|
||||
};
|
||||
// The new entry will be the one returned by `get(y)`.
|
||||
// It overrides the "x -> y" entry.
|
||||
let new_entry = MutationEntry {
|
||||
succ: y,
|
||||
preds: vec![x, q],
|
||||
op,
|
||||
..copy_entry
|
||||
};
|
||||
new_entries.push(new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = Vec::with_capacity(types::mutation::DEFAULT_ENTRY_SIZE);
|
||||
for entry in new_entries {
|
||||
buf.clear();
|
||||
entry.serialize(&mut buf)?;
|
||||
self.log.append(buf.as_slice())?;
|
||||
}
|
||||
|
||||
self.log.flush()?;
|
||||
self.pending.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -167,6 +257,12 @@ impl MutationStore {
|
||||
/// operations like common ancestors, heads, roots, etc. Parents in the
|
||||
/// graph are predecessors.
|
||||
pub fn get_dag(&self, nodes: Vec<Node>) -> Result<MemNameDag> {
|
||||
self.get_dag_advanced(nodes, DagFlags::SUCCESSORS | DagFlags::PREDECESSORS)
|
||||
}
|
||||
|
||||
/// Advanced version of `get_dag`. Specify whether to include successors or
|
||||
/// predecessors explicitly.
|
||||
pub fn get_dag_advanced(&self, nodes: Vec<Node>, flags: DagFlags) -> Result<MemNameDag> {
|
||||
// Include successors recursively.
|
||||
let mut to_visit = nodes;
|
||||
let mut connected = HashSet::new();
|
||||
@ -174,23 +270,36 @@ impl MutationStore {
|
||||
if !connected.insert(node.clone()) {
|
||||
continue;
|
||||
}
|
||||
for entry in self.log.lookup(INDEX_PRED, &node)? {
|
||||
let entry = MutationEntry::deserialize(&mut Cursor::new(entry?))?;
|
||||
to_visit.push(entry.succ);
|
||||
if flags.contains(DagFlags::SUCCESSORS) {
|
||||
for entry in self.log.lookup(INDEX_PRED, &node)? {
|
||||
let entry = MutationEntry::deserialize(&mut Cursor::new(entry?))?;
|
||||
to_visit.push(entry.succ);
|
||||
}
|
||||
}
|
||||
for entry in self.log.lookup(INDEX_SUCC, &node)? {
|
||||
let entry = MutationEntry::deserialize(&mut Cursor::new(entry?))?;
|
||||
for pred in entry.preds {
|
||||
to_visit.push(pred);
|
||||
if flags.contains(DagFlags::PREDECESSORS) {
|
||||
for entry in self.log.lookup(INDEX_SUCC, &node)? {
|
||||
let entry = MutationEntry::deserialize(&mut Cursor::new(entry?))?;
|
||||
for pred in entry.preds {
|
||||
to_visit.push(pred);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let parent_func = |node| -> Result<Vec<VertexName>> {
|
||||
let mut heads = connected
|
||||
.iter()
|
||||
.map(|s| VertexName::copy_from(s.as_ref()))
|
||||
.collect::<Vec<_>>();
|
||||
let parent_func = move |node| -> Result<Vec<VertexName>> {
|
||||
let mut result = Vec::new();
|
||||
for entry in self.log.lookup(INDEX_SUCC, &node)? {
|
||||
let entry = MutationEntry::deserialize(&mut Cursor::new(entry?))?;
|
||||
for pred in entry.preds {
|
||||
result.push(VertexName::copy_from(pred.as_ref()));
|
||||
if connected.contains(&pred) {
|
||||
let parent_node = VertexName::copy_from(pred.as_ref());
|
||||
if parent_node != node && !result.contains(&parent_node) {
|
||||
result.push(parent_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
@ -198,10 +307,6 @@ impl MutationStore {
|
||||
let parent_func = dag::utils::break_parent_func_cycle(parent_func);
|
||||
|
||||
let mut dag = MemNameDag::new();
|
||||
let mut heads = connected
|
||||
.into_iter()
|
||||
.map(|s| VertexName::copy_from(s.as_ref()))
|
||||
.collect::<Vec<_>>();
|
||||
heads.sort();
|
||||
dag.add_heads(parent_func, &heads)?;
|
||||
Ok(dag)
|
||||
@ -406,6 +511,54 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_entries() -> Result<()> {
|
||||
let dir = TempDir::new("mutationstore")?;
|
||||
let mut ms = MutationStore::open(dir.path())?;
|
||||
|
||||
for (pred, succ) in [("P", "E"), ("E", "X")].iter() {
|
||||
add(&mut ms, pred, succ)?;
|
||||
}
|
||||
ms.flush()?;
|
||||
|
||||
for (pred, succ) in [("P", "Q"), ("X", "Y")].iter() {
|
||||
add(&mut ms, pred, succ)?;
|
||||
}
|
||||
|
||||
// Before flush, Q -> Y is not connected.
|
||||
assert_eq!(
|
||||
render(&ms, "P")?,
|
||||
r#"
|
||||
o 5959595959595959595959595959595959595959 (Y)
|
||||
│
|
||||
o 5858585858585858585858585858585858585858 (X)
|
||||
│
|
||||
│ o 5151515151515151515151515151515151515151 (Q)
|
||||
│ │
|
||||
o │ 4545454545454545454545454545454545454545 (E)
|
||||
├─╯
|
||||
o 5050505050505050505050505050505050505050 (P)"#
|
||||
);
|
||||
|
||||
// After flush, Q -> Y is auto created.
|
||||
ms.flush()?;
|
||||
assert_eq!(
|
||||
render(&ms, "P")?,
|
||||
r#"
|
||||
o 5959595959595959595959595959595959595959 (Y)
|
||||
├─╮
|
||||
o │ 5858585858585858585858585858585858585858 (X)
|
||||
│ │
|
||||
│ o 5151515151515151515151515151515151515151 (Q)
|
||||
│ │
|
||||
o │ 4545454545454545454545454545454545454545 (E)
|
||||
├─╯
|
||||
o 5050505050505050505050505050505050505050 (P)"#
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a node from a single-char string.
|
||||
fn n(s: impl ToString) -> Node {
|
||||
Node::from_slice(s.to_string().repeat(Node::len()).as_bytes()).unwrap()
|
||||
|
@ -391,14 +391,13 @@ Test copying obsmarkers
|
||||
* 5577c14fa08d51a4644b9b4b6e001835594cadd2 amend by test at 1970-01-01T00:00:00 from:
|
||||
26805aba1e600a82e93661149f2313866a221a7b
|
||||
|
||||
* 1be7301b35ae8ac3543a07a5d0ce5ca615be709f metaedit-copy by test at 1970-01-01T00:00:00 from:
|
||||
* 1be7301b35ae8ac3543a07a5d0ce5ca615be709f amend-copy by test at 1970-01-01T00:00:00 from:
|
||||
|- 5577c14fa08d51a4644b9b4b6e001835594cadd2 amend by test at 1970-01-01T00:00:00 from:
|
||||
| 26805aba1e600a82e93661149f2313866a221a7b
|
||||
'- 19437442f9e42aa92f504afb1a352caa3e6040f5 metaedit by test at 1970-01-01T00:00:00 from:
|
||||
26805aba1e600a82e93661149f2313866a221a7b
|
||||
|
||||
Slightly more complex: with double amends
|
||||
FIXME: This does not work yet.
|
||||
|
||||
$ newrepo autorel1
|
||||
$ setconfig mutation.proxy-obsstore=off experimental.evolution=obsolete
|
||||
@ -418,7 +417,7 @@ FIXME: This does not work yet.
|
||||
|
|
||||
| o 8:52bc6136aa97@default(draft) D
|
||||
| |
|
||||
| o 7:19437442f9e4@default(draft) C
|
||||
| x 7:19437442f9e4@default(draft) C
|
||||
|/
|
||||
o 6:888bb4818188@default(draft) B1
|
||||
|
|
||||
@ -426,6 +425,7 @@ FIXME: This does not work yet.
|
||||
|
||||
|
||||
$ hg log -r 'successors(19437442f9e4)-19437442f9e4' -T '{node}\n'
|
||||
1be7301b35ae8ac3543a07a5d0ce5ca615be709f
|
||||
|
||||
$ hg log -r 'precursors(19437442f9e4)-19437442f9e4' -T '{desc} {node}\n' --hidden
|
||||
C 26805aba1e600a82e93661149f2313866a221a7b
|
||||
@ -435,10 +435,12 @@ FIXME: This does not work yet.
|
||||
bf080f2103efc214ac3a4638254d4c5370a9294b amend by test at 1970-01-01T00:00:00 from:
|
||||
26805aba1e600a82e93661149f2313866a221a7b
|
||||
|
||||
* 1be7301b35ae8ac3543a07a5d0ce5ca615be709f metaedit by test at 1970-01-01T00:00:00 from:
|
||||
5577c14fa08d51a4644b9b4b6e001835594cadd2 amend by test at 1970-01-01T00:00:00 from:
|
||||
bf080f2103efc214ac3a4638254d4c5370a9294b amend by test at 1970-01-01T00:00:00 from:
|
||||
26805aba1e600a82e93661149f2313866a221a7b
|
||||
* 1be7301b35ae8ac3543a07a5d0ce5ca615be709f amend-copy by test at 1970-01-01T00:00:00 from:
|
||||
|- 5577c14fa08d51a4644b9b4b6e001835594cadd2 amend by test at 1970-01-01T00:00:00 from:
|
||||
| bf080f2103efc214ac3a4638254d4c5370a9294b amend by test at 1970-01-01T00:00:00 from:
|
||||
| 26805aba1e600a82e93661149f2313866a221a7b
|
||||
'- 19437442f9e42aa92f504afb1a352caa3e6040f5 metaedit by test at 1970-01-01T00:00:00 from:
|
||||
26805aba1e600a82e93661149f2313866a221a7b
|
||||
|
||||
|
||||
Test empty commit
|
||||
@ -452,7 +454,7 @@ Test empty commit
|
||||
|
|
||||
| o 8:52bc6136aa97@default(draft) D
|
||||
| |
|
||||
| o 7:19437442f9e4@default(draft) C
|
||||
| x 7:19437442f9e4@default(draft) C
|
||||
|/
|
||||
o 6:888bb4818188@default(draft) B1
|
||||
|
|
||||
|
@ -1126,7 +1126,6 @@ Many splits and folds:
|
||||
O
|
||||
|
||||
Metaedit with descendant amended commits
|
||||
FIXME: metaedit-copy records are not written
|
||||
|
||||
$ cd ..
|
||||
$ newrepo
|
||||
@ -1148,11 +1147,11 @@ FIXME: metaedit-copy records are not written
|
||||
|
|
||||
| o E
|
||||
| |
|
||||
| o C2
|
||||
| x C2 (Rewritten using rewrite into C4)
|
||||
|/
|
||||
| o D
|
||||
| |
|
||||
| o C
|
||||
| x C (Rewritten using amend-copy into C4) (Rewritten using amend-copy into C2)
|
||||
|/
|
||||
o B
|
||||
|
|
||||
@ -1194,7 +1193,7 @@ Metaedit with descendant folded commits
|
||||
|
|
||||
| o D
|
||||
| |
|
||||
| x C (Rewritten using metaedit-copy into F)
|
||||
| x C (Rewritten using fold-copy into F)
|
||||
|/
|
||||
o B
|
||||
|
|
||||
@ -1223,7 +1222,7 @@ Metaedit automatic rebase of amended commit
|
||||
|
|
||||
| o D
|
||||
| |
|
||||
| o C
|
||||
| x C (Rewritten using amend-copy into C2)
|
||||
|/
|
||||
o B1
|
||||
|
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
$ cp -R ../repo ../repob
|
||||
|
||||
FIXME: This does not quite work yet with singletransaction.
|
||||
C -> C2 relation is copied with singletransaction.
|
||||
|
||||
$ hg rebase -s $B -d $Z --config rebase.singletransaction=true
|
||||
rebasing 112478962961 "B"
|
||||
@ -28,7 +28,7 @@ FIXME: This does not quite work yet with singletransaction.
|
||||
|
|
||||
| o f7f4f5b9173a D
|
||||
| |
|
||||
| o e709467ba6ed C
|
||||
| x e709467ba6ed C
|
||||
|/
|
||||
o d74d19e598c8 B
|
||||
|
|
||||
|
Loading…
Reference in New Issue
Block a user