From 452ed6b79ed0e12ae214af3c8cd49a87dba91c73 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 12 Jun 2023 18:40:45 +0200 Subject: [PATCH] feat: `gix fetch --open-negotiation-graph[=limit]` 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. --- gitoxide-core/src/repository/fetch.rs | 75 +++++++++++++++++++ gitoxide-core/src/repository/revision/list.rs | 27 +++---- src/plumbing/main.rs | 2 + src/plumbing/options/mod.rs | 4 + 4 files changed, 95 insertions(+), 13 deletions(-) diff --git a/gitoxide-core/src/repository/fetch.rs b/gitoxide-core/src/repository/fetch.rs index b8987d4f099..72c1f0c5a07 100644 --- a/gitoxide-core/src/repository/fetch.rs +++ b/gitoxide-core/src/repository/fetch.rs @@ -11,6 +11,7 @@ pub struct Options { pub shallow: gix::remote::fetch::Shallow, pub handshake_info: bool, pub negotiation_info: bool, + pub open_negotiation_graph: Option, } pub const PROGRESS_RANGE: std::ops::RangeInclusive = 1..=3; @@ -18,6 +19,11 @@ pub const PROGRESS_RANGE: std::ops::RangeInclusive = 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; @@ -33,6 +39,7 @@ pub(crate) mod function { remote, handshake_info, negotiation_info, + open_negotiation_graph, shallow, ref_specs, }: Options, @@ -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 { @@ -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(()) } }?; @@ -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>, diff --git a/gitoxide-core/src/repository/revision/list.rs b/gitoxide-core/src/repository/revision/list.rs index 9cca8b3f4c3..fc787a972c7 100644 --- a/gitoxide-core/src/repository/revision/list.rs +++ b/gitoxide-core/src/repository/revision/list.rs @@ -17,8 +17,8 @@ pub const PROGRESS_RANGE: std::ops::RangeInclusive = 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; @@ -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, @@ -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 } @@ -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 } @@ -127,7 +120,7 @@ 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); @@ -135,6 +128,14 @@ pub(crate) mod function { 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) + } } } diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 1253c031601..115253dd178 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -190,6 +190,7 @@ pub fn main() -> Result<()> { dry_run, handshake_info, negotiation_info, + open_negotiation_graph, remote, shallow, ref_spec, @@ -200,6 +201,7 @@ pub fn main() -> Result<()> { remote, handshake_info, negotiation_info, + open_negotiation_graph, shallow: shallow.into(), ref_specs: ref_spec, }; diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index 5122f6002c6..4a9ce607ae2 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -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, + #[clap(flatten)] pub shallow: ShallowOptions,