Skip to content

Commit 452ed6b

Browse files
committed
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.
1 parent 096838f commit 452ed6b

File tree

4 files changed

+95
-13
lines changed

4 files changed

+95
-13
lines changed

Diff for: gitoxide-core/src/repository/fetch.rs

+75
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ pub struct Options {
1111
pub shallow: gix::remote::fetch::Shallow,
1212
pub handshake_info: bool,
1313
pub negotiation_info: bool,
14+
pub open_negotiation_graph: Option<std::path::PathBuf>,
1415
}
1516

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

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

2228
use super::Options;
2329
use crate::OutputFormat;
@@ -33,6 +39,7 @@ pub(crate) mod function {
3339
remote,
3440
handshake_info,
3541
negotiation_info,
42+
open_negotiation_graph,
3643
shallow,
3744
ref_specs,
3845
}: Options,
@@ -82,6 +89,11 @@ pub(crate) mod function {
8289
if negotiation_info {
8390
print_negotiate_info(&mut out, negotiate.as_ref())?;
8491
}
92+
if let Some((negotiate, path)) =
93+
open_negotiation_graph.and_then(|path| negotiate.as_ref().map(|n| (n, path)))
94+
{
95+
render_graph(&repo, &negotiate.graph, &path, progress)?;
96+
}
8597
Ok::<_, anyhow::Error>(())
8698
}
8799
Status::Change {
@@ -99,6 +111,9 @@ pub(crate) mod function {
99111
if negotiation_info {
100112
print_negotiate_info(&mut out, Some(&negotiate))?;
101113
}
114+
if let Some(path) = open_negotiation_graph {
115+
render_graph(&repo, &negotiate.graph, &path, progress)?;
116+
}
102117
Ok(())
103118
}
104119
}?;
@@ -108,6 +123,66 @@ pub(crate) mod function {
108123
Ok(())
109124
}
110125

126+
fn render_graph(
127+
repo: &gix::Repository,
128+
graph: &gix::negotiate::IdMap,
129+
path: &std::path::Path,
130+
mut progress: impl gix::Progress,
131+
) -> anyhow::Result<()> {
132+
progress.init(Some(graph.len()), gix::progress::count("commits"));
133+
progress.set_name("building graph");
134+
135+
let mut map = gix::hashtable::HashMap::default();
136+
let mut vg = layout::topo::layout::VisualGraph::new(Orientation::TopToBottom);
137+
138+
for (id, commit) in graph.iter().inspect(|_| progress.inc()) {
139+
let source = match map.get(id) {
140+
Some(handle) => *handle,
141+
None => {
142+
let handle = vg.add_node(new_node(id.attach(repo), commit.data.flags));
143+
map.insert(*id, handle);
144+
handle
145+
}
146+
};
147+
148+
for parent_id in &commit.parents {
149+
let dest = match map.get(parent_id) {
150+
Some(handle) => *handle,
151+
None => {
152+
let flags = match graph.get(parent_id) {
153+
Some(c) => c.data.flags,
154+
None => continue,
155+
};
156+
let dest = vg.add_node(new_node(parent_id.attach(repo), flags));
157+
map.insert(*parent_id, dest);
158+
dest
159+
}
160+
};
161+
let arrow = Arrow::simple("");
162+
vg.add_edge(arrow, source, dest);
163+
}
164+
}
165+
166+
let start = std::time::Instant::now();
167+
progress.set_name("layout graph");
168+
progress.info(format!("writing {path:?}…"));
169+
let mut svg = SVGWriter::new();
170+
vg.do_it(false, false, false, &mut svg);
171+
std::fs::write(path, svg.finalize().as_bytes())?;
172+
open::that(path)?;
173+
progress.show_throughput(start);
174+
175+
return Ok(());
176+
177+
fn new_node(id: gix::Id<'_>, flags: gix::negotiate::Flags) -> Element {
178+
let pt = Point::new(250., 50.);
179+
let name = format!("{}\n\n{flags:?}", id.shorten_or_id());
180+
let shape = ShapeKind::new_box(name.as_str());
181+
let style = StyleAttr::simple();
182+
Element::create(shape, style, Orientation::LeftToRight, pt)
183+
}
184+
}
185+
111186
fn print_negotiate_info(
112187
mut out: impl std::io::Write,
113188
negotiate: Option<&gix::remote::fetch::outcome::Negotiate>,

Diff for: gitoxide-core/src/repository/revision/list.rs

+14-13
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 0..=2;
1717

1818
pub(crate) mod function {
1919
use anyhow::{bail, Context};
20+
use gix::hashtable::HashMap;
2021
use gix::traverse::commit::Sorting;
21-
use std::collections::HashMap;
2222

2323
use gix::Progress;
2424
use layout::backends::svg::SVGWriter;
@@ -63,7 +63,7 @@ pub(crate) mod function {
6363
Format::Svg { path } => (
6464
layout::topo::layout::VisualGraph::new(Orientation::TopToBottom),
6565
path,
66-
HashMap::new(),
66+
HashMap::default(),
6767
)
6868
.into(),
6969
Format::Text => None,
@@ -79,14 +79,10 @@ pub(crate) mod function {
7979
let commit = commit?;
8080
match vg.as_mut() {
8181
Some((vg, _path, map)) => {
82-
let pt = Point::new(100., 30.);
8382
let source = match map.get(&commit.id) {
8483
Some(handle) => *handle,
8584
None => {
86-
let name = commit.id().shorten_or_id().to_string();
87-
let shape = ShapeKind::new_box(name.as_str());
88-
let style = StyleAttr::simple();
89-
let handle = vg.add_node(Element::create(shape, style, Orientation::LeftToRight, pt));
85+
let handle = vg.add_node(new_node(commit.id()));
9086
map.insert(commit.id, handle);
9187
handle
9288
}
@@ -96,10 +92,7 @@ pub(crate) mod function {
9692
let dest = match map.get(parent_id.as_ref()) {
9793
Some(handle) => *handle,
9894
None => {
99-
let name = parent_id.shorten_or_id().to_string();
100-
let shape = ShapeKind::new_box(name.as_str());
101-
let style = StyleAttr::simple();
102-
let dest = vg.add_node(Element::create(shape, style, Orientation::LeftToRight, pt));
95+
let dest = vg.add_node(new_node(parent_id));
10396
map.insert(parent_id.detach(), dest);
10497
dest
10598
}
@@ -127,14 +120,22 @@ pub(crate) mod function {
127120
progress.show_throughput(start);
128121
if let Some((mut vg, path, _)) = vg {
129122
let start = std::time::Instant::now();
130-
progress.set_name("computing graph");
123+
progress.set_name("layout graph");
131124
progress.info(format!("writing {path:?}…"));
132125
let mut svg = SVGWriter::new();
133126
vg.do_it(false, false, false, &mut svg);
134127
std::fs::write(&path, svg.finalize().as_bytes())?;
135128
open::that(path)?;
136129
progress.show_throughput(start);
137130
}
138-
Ok(())
131+
return Ok(());
132+
133+
fn new_node(id: gix::Id<'_>) -> Element {
134+
let pt = Point::new(100., 30.);
135+
let name = id.shorten_or_id().to_string();
136+
let shape = ShapeKind::new_box(name.as_str());
137+
let style = StyleAttr::simple();
138+
Element::create(shape, style, Orientation::LeftToRight, pt)
139+
}
139140
}
140141
}

Diff for: src/plumbing/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ pub fn main() -> Result<()> {
190190
dry_run,
191191
handshake_info,
192192
negotiation_info,
193+
open_negotiation_graph,
193194
remote,
194195
shallow,
195196
ref_spec,
@@ -200,6 +201,7 @@ pub fn main() -> Result<()> {
200201
remote,
201202
handshake_info,
202203
negotiation_info,
204+
open_negotiation_graph,
203205
shallow: shallow.into(),
204206
ref_specs: ref_spec,
205207
};

Diff for: src/plumbing/options/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ pub mod fetch {
159159
#[clap(long, short = 's')]
160160
pub negotiation_info: bool,
161161

162+
/// Open the commit graph used for negotiation and write an SVG file to PATH.
163+
#[clap(long, value_name = "PATH", short = 'g')]
164+
pub open_negotiation_graph: Option<std::path::PathBuf>,
165+
162166
#[clap(flatten)]
163167
pub shallow: ShallowOptions,
164168

0 commit comments

Comments
 (0)