Skip to content

Commit

Permalink
feat: gix fetch --open-negotiation-graph[=limit]
Browse files Browse the repository at this point in the history
Open the negotiation graph as SVG, after optionally specifying a limit
as rendering/layouting can be very slow.

It's useful to see how the negotiation algorithm is reasoning about each commit.
  • Loading branch information
Byron committed Jun 12, 2023
1 parent 096838f commit 452ed6b
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 13 deletions.
75 changes: 75 additions & 0 deletions gitoxide-core/src/repository/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ pub struct Options {
pub shallow: gix::remote::fetch::Shallow,
pub handshake_info: bool,
pub negotiation_info: bool,
pub open_negotiation_graph: Option<std::path::PathBuf>,
}

pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;

pub(crate) mod function {
use anyhow::bail;
use gix::{prelude::ObjectIdExt, refspec::match_group::validate::Fix, remote::fetch::Status};
use layout::backends::svg::SVGWriter;
use layout::core::base::Orientation;
use layout::core::geometry::Point;
use layout::core::style::StyleAttr;
use layout::std_shapes::shapes::{Arrow, Element, ShapeKind};

use super::Options;
use crate::OutputFormat;
Expand All @@ -33,6 +39,7 @@ pub(crate) mod function {
remote,
handshake_info,
negotiation_info,
open_negotiation_graph,
shallow,
ref_specs,
}: Options,
Expand Down Expand Up @@ -82,6 +89,11 @@ pub(crate) mod function {
if negotiation_info {
print_negotiate_info(&mut out, negotiate.as_ref())?;
}
if let Some((negotiate, path)) =
open_negotiation_graph.and_then(|path| negotiate.as_ref().map(|n| (n, path)))
{
render_graph(&repo, &negotiate.graph, &path, progress)?;
}
Ok::<_, anyhow::Error>(())
}
Status::Change {
Expand All @@ -99,6 +111,9 @@ pub(crate) mod function {
if negotiation_info {
print_negotiate_info(&mut out, Some(&negotiate))?;
}
if let Some(path) = open_negotiation_graph {
render_graph(&repo, &negotiate.graph, &path, progress)?;
}
Ok(())
}
}?;
Expand All @@ -108,6 +123,66 @@ pub(crate) mod function {
Ok(())
}

fn render_graph(
repo: &gix::Repository,
graph: &gix::negotiate::IdMap,
path: &std::path::Path,
mut progress: impl gix::Progress,
) -> anyhow::Result<()> {
progress.init(Some(graph.len()), gix::progress::count("commits"));
progress.set_name("building graph");

let mut map = gix::hashtable::HashMap::default();
let mut vg = layout::topo::layout::VisualGraph::new(Orientation::TopToBottom);

for (id, commit) in graph.iter().inspect(|_| progress.inc()) {
let source = match map.get(id) {
Some(handle) => *handle,
None => {
let handle = vg.add_node(new_node(id.attach(repo), commit.data.flags));
map.insert(*id, handle);
handle
}
};

for parent_id in &commit.parents {
let dest = match map.get(parent_id) {
Some(handle) => *handle,
None => {
let flags = match graph.get(parent_id) {
Some(c) => c.data.flags,
None => continue,
};
let dest = vg.add_node(new_node(parent_id.attach(repo), flags));
map.insert(*parent_id, dest);
dest
}
};
let arrow = Arrow::simple("");
vg.add_edge(arrow, source, dest);
}
}

let start = std::time::Instant::now();
progress.set_name("layout graph");
progress.info(format!("writing {path:?}…"));
let mut svg = SVGWriter::new();
vg.do_it(false, false, false, &mut svg);
std::fs::write(path, svg.finalize().as_bytes())?;
open::that(path)?;
progress.show_throughput(start);

return Ok(());

fn new_node(id: gix::Id<'_>, flags: gix::negotiate::Flags) -> Element {
let pt = Point::new(250., 50.);
let name = format!("{}\n\n{flags:?}", id.shorten_or_id());
let shape = ShapeKind::new_box(name.as_str());
let style = StyleAttr::simple();
Element::create(shape, style, Orientation::LeftToRight, pt)
}
}

fn print_negotiate_info(
mut out: impl std::io::Write,
negotiate: Option<&gix::remote::fetch::outcome::Negotiate>,
Expand Down
27 changes: 14 additions & 13 deletions gitoxide-core/src/repository/revision/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 0..=2;

pub(crate) mod function {
use anyhow::{bail, Context};
use gix::hashtable::HashMap;
use gix::traverse::commit::Sorting;
use std::collections::HashMap;

use gix::Progress;
use layout::backends::svg::SVGWriter;
Expand Down Expand Up @@ -63,7 +63,7 @@ pub(crate) mod function {
Format::Svg { path } => (
layout::topo::layout::VisualGraph::new(Orientation::TopToBottom),
path,
HashMap::new(),
HashMap::default(),
)
.into(),
Format::Text => None,
Expand All @@ -79,14 +79,10 @@ pub(crate) mod function {
let commit = commit?;
match vg.as_mut() {
Some((vg, _path, map)) => {
let pt = Point::new(100., 30.);
let source = match map.get(&commit.id) {
Some(handle) => *handle,
None => {
let name = commit.id().shorten_or_id().to_string();
let shape = ShapeKind::new_box(name.as_str());
let style = StyleAttr::simple();
let handle = vg.add_node(Element::create(shape, style, Orientation::LeftToRight, pt));
let handle = vg.add_node(new_node(commit.id()));
map.insert(commit.id, handle);
handle
}
Expand All @@ -96,10 +92,7 @@ pub(crate) mod function {
let dest = match map.get(parent_id.as_ref()) {
Some(handle) => *handle,
None => {
let name = parent_id.shorten_or_id().to_string();
let shape = ShapeKind::new_box(name.as_str());
let style = StyleAttr::simple();
let dest = vg.add_node(Element::create(shape, style, Orientation::LeftToRight, pt));
let dest = vg.add_node(new_node(parent_id));
map.insert(parent_id.detach(), dest);
dest
}
Expand Down Expand Up @@ -127,14 +120,22 @@ pub(crate) mod function {
progress.show_throughput(start);
if let Some((mut vg, path, _)) = vg {
let start = std::time::Instant::now();
progress.set_name("computing graph");
progress.set_name("layout graph");
progress.info(format!("writing {path:?}…"));
let mut svg = SVGWriter::new();
vg.do_it(false, false, false, &mut svg);
std::fs::write(&path, svg.finalize().as_bytes())?;
open::that(path)?;
progress.show_throughput(start);
}
Ok(())
return Ok(());

fn new_node(id: gix::Id<'_>) -> Element {
let pt = Point::new(100., 30.);
let name = id.shorten_or_id().to_string();
let shape = ShapeKind::new_box(name.as_str());
let style = StyleAttr::simple();
Element::create(shape, style, Orientation::LeftToRight, pt)
}
}
}
2 changes: 2 additions & 0 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ pub fn main() -> Result<()> {
dry_run,
handshake_info,
negotiation_info,
open_negotiation_graph,
remote,
shallow,
ref_spec,
Expand All @@ -200,6 +201,7 @@ pub fn main() -> Result<()> {
remote,
handshake_info,
negotiation_info,
open_negotiation_graph,
shallow: shallow.into(),
ref_specs: ref_spec,
};
Expand Down
4 changes: 4 additions & 0 deletions src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ pub mod fetch {
#[clap(long, short = 's')]
pub negotiation_info: bool,

/// Open the commit graph used for negotiation and write an SVG file to PATH.
#[clap(long, value_name = "PATH", short = 'g')]
pub open_negotiation_graph: Option<std::path::PathBuf>,

#[clap(flatten)]
pub shallow: ShallowOptions,

Expand Down

0 comments on commit 452ed6b

Please sign in to comment.