mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
dag: move beautify to default_impl
Summary: This makes `ops.rs` look simpler. Reviewed By: sfilipco Differential Revision: D23269863 fbshipit-source-id: ddb55ab8eb3b2d3e7c4b2ccbc2252395d62317a1
This commit is contained in:
parent
e12b6c81de
commit
f4021486ab
@ -6,12 +6,153 @@
|
||||
*/
|
||||
|
||||
use crate::errors::programming;
|
||||
use crate::namedag::MemNameDag;
|
||||
use crate::ops::DagAddHeads;
|
||||
use crate::DagAlgorithm;
|
||||
use crate::NameSet;
|
||||
use crate::Result;
|
||||
use crate::VertexName;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// Re-create the graph so it looks better when rendered.
|
||||
///
|
||||
/// For example, the left-side graph will be rewritten to the right-side:
|
||||
///
|
||||
/// 1. Linearize.
|
||||
///
|
||||
/// ```plain,ignore
|
||||
/// A A # Linearize is done by IdMap::assign_heads,
|
||||
/// | | # as long as the heads provided are the heads
|
||||
/// | C B # of the whole graph ("A", "C", not "B", "D").
|
||||
/// | | |
|
||||
/// B | -> | C
|
||||
/// | | | |
|
||||
/// | D | D
|
||||
/// |/ |/
|
||||
/// E E
|
||||
/// ```
|
||||
///
|
||||
/// 2. Reorder branches (at different branching points) to reduce columns.
|
||||
///
|
||||
/// ```plain,ignore
|
||||
/// D B
|
||||
/// | | # Assuming the main branch is B-C-E.
|
||||
/// B | | A # Branching point of the D branch is "C"
|
||||
/// | | |/ # Branching point of the A branch is "C"
|
||||
/// | | A -> C # The D branch should be moved to below
|
||||
/// | |/ | # the A branch.
|
||||
/// | | | D
|
||||
/// |/| |/
|
||||
/// C / E
|
||||
/// |/
|
||||
/// E
|
||||
/// ```
|
||||
///
|
||||
/// 3. Reorder branches (at a same branching point) to reduce length of
|
||||
/// edges.
|
||||
///
|
||||
/// ```plain,ignore
|
||||
/// D A
|
||||
/// | | # This is done by picking the longest
|
||||
/// | A B # branch (A-B-C-E) as the "main branch"
|
||||
/// | | | # and work on the remaining branches
|
||||
/// | B -> C # recursively.
|
||||
/// | | |
|
||||
/// | C | D
|
||||
/// |/ |/
|
||||
/// E E
|
||||
/// ```
|
||||
///
|
||||
/// `main_branch` optionally defines how to sort the heads. A head `x` will
|
||||
/// be emitted first during iteration, if `ancestors(x) & main_branch`
|
||||
/// contains larger vertexes. For example, if `main_branch` is `[C, D, E]`,
|
||||
/// then `C` will be emitted first, and the returned DAG will have `all()`
|
||||
/// output `[C, D, A, B, E]`. Practically, `main_branch` usually contains
|
||||
/// "public" commits.
|
||||
///
|
||||
/// This function is expensive. Only run on small graphs.
|
||||
///
|
||||
/// This function is currently more optimized for "forking" cases. It is
|
||||
/// not yet optimized for graphs with many merges.
|
||||
pub(crate) fn beautify(
|
||||
this: &(impl DagAlgorithm + ?Sized),
|
||||
main_branch: Option<NameSet>,
|
||||
) -> Result<MemNameDag> {
|
||||
// Find the "largest" branch.
|
||||
fn find_main_branch(
|
||||
get_ancestors: &impl Fn(&VertexName) -> Result<NameSet>,
|
||||
heads: &[VertexName],
|
||||
) -> Result<NameSet> {
|
||||
let mut best_branch = NameSet::empty();
|
||||
let mut best_count = best_branch.count()?;
|
||||
for head in heads {
|
||||
let branch = get_ancestors(head)?;
|
||||
let count = branch.count()?;
|
||||
if count > best_count {
|
||||
best_count = count;
|
||||
best_branch = branch;
|
||||
}
|
||||
}
|
||||
Ok(best_branch)
|
||||
};
|
||||
|
||||
// Sort heads recursively.
|
||||
fn sort(
|
||||
get_ancestors: &impl Fn(&VertexName) -> Result<NameSet>,
|
||||
heads: &mut [VertexName],
|
||||
main_branch: NameSet,
|
||||
) -> Result<()> {
|
||||
if heads.len() <= 1 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Sort heads by "branching point" on the main branch.
|
||||
let mut branching_points: HashMap<VertexName, usize> = HashMap::with_capacity(heads.len());
|
||||
for head in heads.iter() {
|
||||
let count = (get_ancestors(head)? & main_branch.clone()).count()?;
|
||||
branching_points.insert(head.clone(), count);
|
||||
}
|
||||
heads.sort_by_key(|v| branching_points.get(v));
|
||||
|
||||
// For heads with a same branching point, sort them recursively
|
||||
// using a different "main branch".
|
||||
let mut start = 0;
|
||||
let mut start_branching_point: Option<usize> = None;
|
||||
for end in 0..=heads.len() {
|
||||
let branching_point = heads
|
||||
.get(end)
|
||||
.and_then(|h| branching_points.get(&h).cloned());
|
||||
if branching_point != start_branching_point {
|
||||
if start + 1 < end {
|
||||
let heads = &mut heads[start..end];
|
||||
let main_branch = find_main_branch(get_ancestors, heads)?;
|
||||
sort(get_ancestors, heads, main_branch)?;
|
||||
}
|
||||
start = end;
|
||||
start_branching_point = branching_point;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let main_branch = main_branch.unwrap_or_else(NameSet::empty);
|
||||
let mut heads: Vec<_> = this
|
||||
.heads_ancestors(this.all()?)?
|
||||
.iter()?
|
||||
.collect::<Result<_>>()?;
|
||||
let get_ancestors = |head: &VertexName| this.ancestors(head.into());
|
||||
// Stabilize output if the sort key conflicts.
|
||||
heads.sort();
|
||||
sort(&get_ancestors, &mut heads[..], main_branch)?;
|
||||
|
||||
let mut dag = MemNameDag::new();
|
||||
let get_parents = |v| this.parent_names(v);
|
||||
dag.add_heads(get_parents, &heads)?;
|
||||
Ok(dag)
|
||||
}
|
||||
|
||||
pub(crate) fn parents(this: &(impl DagAlgorithm + ?Sized), set: NameSet) -> Result<NameSet> {
|
||||
let mut result: Vec<VertexName> = Vec::new();
|
||||
for vertex in set.iter()? {
|
||||
|
@ -17,7 +17,6 @@ use crate::nameset::id_static::IdStaticSet;
|
||||
use crate::nameset::NameSet;
|
||||
use crate::IdSet;
|
||||
use crate::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// DAG related read-only algorithms.
|
||||
@ -26,139 +25,8 @@ pub trait DagAlgorithm {
|
||||
fn sort(&self, set: &NameSet) -> Result<NameSet>;
|
||||
|
||||
/// Re-create the graph so it looks better when rendered.
|
||||
///
|
||||
/// For example, the left-side graph will be rewritten to the right-side:
|
||||
///
|
||||
/// 1. Linearize.
|
||||
///
|
||||
/// ```plain,ignore
|
||||
/// A A # Linearize is done by IdMap::assign_heads,
|
||||
/// | | # as long as the heads provided are the heads
|
||||
/// | C B # of the whole graph ("A", "C", not "B", "D").
|
||||
/// | | |
|
||||
/// B | -> | C
|
||||
/// | | | |
|
||||
/// | D | D
|
||||
/// |/ |/
|
||||
/// E E
|
||||
/// ```
|
||||
///
|
||||
/// 2. Reorder branches (at different branching points) to reduce columns.
|
||||
///
|
||||
/// ```plain,ignore
|
||||
/// D B
|
||||
/// | | # Assuming the main branch is B-C-E.
|
||||
/// B | | A # Branching point of the D branch is "C"
|
||||
/// | | |/ # Branching point of the A branch is "C"
|
||||
/// | | A -> C # The D branch should be moved to below
|
||||
/// | |/ | # the A branch.
|
||||
/// | | | D
|
||||
/// |/| |/
|
||||
/// C / E
|
||||
/// |/
|
||||
/// E
|
||||
/// ```
|
||||
///
|
||||
/// 3. Reorder branches (at a same branching point) to reduce length of
|
||||
/// edges.
|
||||
///
|
||||
/// ```plain,ignore
|
||||
/// D A
|
||||
/// | | # This is done by picking the longest
|
||||
/// | A B # branch (A-B-C-E) as the "main branch"
|
||||
/// | | | # and work on the remaining branches
|
||||
/// | B -> C # recursively.
|
||||
/// | | |
|
||||
/// | C | D
|
||||
/// |/ |/
|
||||
/// E E
|
||||
/// ```
|
||||
///
|
||||
/// `main_branch` optionally defines how to sort the heads. A head `x` will
|
||||
/// be emitted first during iteration, if `ancestors(x) & main_branch`
|
||||
/// contains larger vertexes. For example, if `main_branch` is `[C, D, E]`,
|
||||
/// then `C` will be emitted first, and the returned DAG will have `all()`
|
||||
/// output `[C, D, A, B, E]`. Practically, `main_branch` usually contains
|
||||
/// "public" commits.
|
||||
///
|
||||
/// This function is expensive. Only run on small graphs.
|
||||
///
|
||||
/// This function is currently more optimized for "forking" cases. It is
|
||||
/// not yet optimized for graphs with many merges.
|
||||
fn beautify(&self, main_branch: Option<NameSet>) -> Result<MemNameDag> {
|
||||
// Find the "largest" branch.
|
||||
fn find_main_branch(
|
||||
get_ancestors: &impl Fn(&VertexName) -> Result<NameSet>,
|
||||
heads: &[VertexName],
|
||||
) -> Result<NameSet> {
|
||||
let mut best_branch = NameSet::empty();
|
||||
let mut best_count = best_branch.count()?;
|
||||
for head in heads {
|
||||
let branch = get_ancestors(head)?;
|
||||
let count = branch.count()?;
|
||||
if count > best_count {
|
||||
best_count = count;
|
||||
best_branch = branch;
|
||||
}
|
||||
}
|
||||
Ok(best_branch)
|
||||
};
|
||||
|
||||
// Sort heads recursively.
|
||||
fn sort(
|
||||
get_ancestors: &impl Fn(&VertexName) -> Result<NameSet>,
|
||||
heads: &mut [VertexName],
|
||||
main_branch: NameSet,
|
||||
) -> Result<()> {
|
||||
if heads.len() <= 1 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Sort heads by "branching point" on the main branch.
|
||||
let mut branching_points: HashMap<VertexName, usize> =
|
||||
HashMap::with_capacity(heads.len());
|
||||
for head in heads.iter() {
|
||||
let count = (get_ancestors(head)? & main_branch.clone()).count()?;
|
||||
branching_points.insert(head.clone(), count);
|
||||
}
|
||||
heads.sort_by_key(|v| branching_points.get(v));
|
||||
|
||||
// For heads with a same branching point, sort them recursively
|
||||
// using a different "main branch".
|
||||
let mut start = 0;
|
||||
let mut start_branching_point: Option<usize> = None;
|
||||
for end in 0..=heads.len() {
|
||||
let branching_point = heads
|
||||
.get(end)
|
||||
.and_then(|h| branching_points.get(&h).cloned());
|
||||
if branching_point != start_branching_point {
|
||||
if start + 1 < end {
|
||||
let heads = &mut heads[start..end];
|
||||
let main_branch = find_main_branch(get_ancestors, heads)?;
|
||||
sort(get_ancestors, heads, main_branch)?;
|
||||
}
|
||||
start = end;
|
||||
start_branching_point = branching_point;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let main_branch = main_branch.unwrap_or_else(|| NameSet::empty());
|
||||
let mut heads: Vec<_> = self
|
||||
.heads_ancestors(self.all()?)?
|
||||
.iter()?
|
||||
.collect::<Result<_>>()?;
|
||||
let get_ancestors = |head: &VertexName| self.ancestors(head.into());
|
||||
// Stabilize output if the sort key conflicts.
|
||||
heads.sort();
|
||||
sort(&get_ancestors, &mut heads[..], main_branch)?;
|
||||
|
||||
let mut dag = MemNameDag::new();
|
||||
let get_parents = |v| self.parent_names(v);
|
||||
dag.add_heads(get_parents, &heads)?;
|
||||
Ok(dag)
|
||||
default_impl::beautify(self, main_branch)
|
||||
}
|
||||
|
||||
/// Get ordered parent vertexes.
|
||||
|
Loading…
Reference in New Issue
Block a user