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:
Jun Wu 2020-08-26 15:26:49 -07:00 committed by Facebook GitHub Bot
parent e12b6c81de
commit f4021486ab
2 changed files with 142 additions and 133 deletions

View File

@ -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()? {

View File

@ -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.