Skip to content

Commit 036e60a

Browse files
committed
Merge branch 'gix-revision-graph'
2 parents 71efcbb + 452ed6b commit 036e60a

File tree

24 files changed

+474
-160
lines changed

24 files changed

+474
-160
lines changed

Cargo.lock

Lines changed: 44 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ resolver = "2"
1212

1313
[[bin]]
1414
name = "ein"
15+
doc = false
1516
path = "src/ein.rs"
1617
test = false
1718
doctest = false
1819

1920
[[bin]]
2021
name = "gix"
2122
path = "src/gix.rs"
23+
doc = false
2224
test = false
2325
doctest = false
2426

@@ -182,7 +184,7 @@ sha1_smol = { opt-level = 3 }
182184

183185
[profile.release]
184186
overflow-checks = false
185-
lto = "fat"
187+
#lto = "fat"
186188
# this bloats files but assures destructors are called, important for tempfiles. One day I hope we
187189
# can wire up the 'abrt' signal handler so tempfiles will be removed in case of panics.
188190
panic = 'unwind'

gitoxide-core/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ smallvec = { version = "1.10.0", optional = true }
6969
# for 'query'
7070
rusqlite = { version = "0.29.0", optional = true, features = ["bundled"] }
7171

72+
# for svg graph output
73+
layout-rs = "0.1.1"
74+
open = "4.1.0"
75+
7276
document-features = { version = "0.2.0", optional = true }
7377

7478
[package.metadata.docs.rs]

gitoxide-core/src/repository/clone.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,20 @@ pub(crate) mod function {
8888
}
8989

9090
match fetch_outcome.status {
91-
Status::NoPackReceived { .. } => {
91+
Status::NoPackReceived { dry_run, .. } => {
92+
assert!(!dry_run, "dry-run unsupported");
9293
writeln!(err, "The cloned repository appears to be empty")?;
9394
}
94-
Status::DryRun { .. } => unreachable!("dry-run unsupported"),
9595
Status::Change {
96-
update_refs,
97-
negotiation_rounds,
98-
..
96+
update_refs, negotiate, ..
9997
} => {
10098
let remote = repo
10199
.find_default_remote(gix::remote::Direction::Fetch)
102100
.expect("one origin remote")?;
103101
let ref_specs = remote.refspecs(gix::remote::Direction::Fetch);
104102
print_updates(
105103
&repo,
106-
negotiation_rounds,
104+
&negotiate,
107105
update_refs,
108106
ref_specs,
109107
fetch_outcome.ref_map,

gitoxide-core/src/repository/fetch.rs

Lines changed: 115 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@ pub struct Options {
1010
pub ref_specs: Vec<BString>,
1111
pub shallow: gix::remote::fetch::Shallow,
1212
pub handshake_info: bool,
13+
pub negotiation_info: bool,
14+
pub open_negotiation_graph: Option<std::path::PathBuf>,
1315
}
1416

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

1719
pub(crate) mod function {
1820
use anyhow::bail;
1921
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};
2027

2128
use super::Options;
2229
use crate::OutputFormat;
@@ -31,6 +38,8 @@ pub(crate) mod function {
3138
dry_run,
3239
remote,
3340
handshake_info,
41+
negotiation_info,
42+
open_negotiation_graph,
3443
shallow,
3544
ref_specs,
3645
}: Options,
@@ -62,41 +71,49 @@ pub(crate) mod function {
6271

6372
let ref_specs = remote.refspecs(gix::remote::Direction::Fetch);
6473
match res.status {
65-
Status::NoPackReceived { update_refs } => {
66-
print_updates(&repo, 1, update_refs, ref_specs, res.ref_map, &mut out, err)
67-
}
68-
Status::DryRun {
69-
update_refs,
70-
negotiation_rounds,
71-
} => print_updates(
72-
&repo,
73-
negotiation_rounds,
74-
update_refs,
75-
ref_specs,
76-
res.ref_map,
77-
&mut out,
78-
err,
79-
),
80-
Status::Change {
74+
Status::NoPackReceived {
8175
update_refs,
82-
write_pack_bundle,
83-
negotiation_rounds,
76+
negotiate,
77+
dry_run: _,
8478
} => {
79+
let negotiate_default = Default::default();
8580
print_updates(
8681
&repo,
87-
negotiation_rounds,
82+
negotiate.as_ref().unwrap_or(&negotiate_default),
8883
update_refs,
8984
ref_specs,
9085
res.ref_map,
9186
&mut out,
9287
err,
9388
)?;
89+
if negotiation_info {
90+
print_negotiate_info(&mut out, negotiate.as_ref())?;
91+
}
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+
}
97+
Ok::<_, anyhow::Error>(())
98+
}
99+
Status::Change {
100+
update_refs,
101+
write_pack_bundle,
102+
negotiate,
103+
} => {
104+
print_updates(&repo, &negotiate, update_refs, ref_specs, res.ref_map, &mut out, err)?;
94105
if let Some(data_path) = write_pack_bundle.data_path {
95106
writeln!(out, "pack file: \"{}\"", data_path.display()).ok();
96107
}
97108
if let Some(index_path) = write_pack_bundle.index_path {
98109
writeln!(out, "index file: \"{}\"", index_path.display()).ok();
99110
}
111+
if negotiation_info {
112+
print_negotiate_info(&mut out, Some(&negotiate))?;
113+
}
114+
if let Some(path) = open_negotiation_graph {
115+
render_graph(&repo, &negotiate.graph, &path, progress)?;
116+
}
100117
Ok(())
101118
}
102119
}?;
@@ -106,9 +123,83 @@ pub(crate) mod function {
106123
Ok(())
107124
}
108125

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+
186+
fn print_negotiate_info(
187+
mut out: impl std::io::Write,
188+
negotiate: Option<&gix::remote::fetch::outcome::Negotiate>,
189+
) -> std::io::Result<()> {
190+
writeln!(out, "Negotiation Phase Information")?;
191+
match negotiate {
192+
Some(negotiate) => {
193+
writeln!(out, "\t{:?}", negotiate.rounds)?;
194+
writeln!(out, "\tnum commits traversed in graph: {}", negotiate.graph.len())
195+
}
196+
None => writeln!(out, "\tno negotiation performed"),
197+
}
198+
}
199+
109200
pub(crate) fn print_updates(
110201
repo: &gix::Repository,
111-
negotiation_rounds: usize,
202+
negotiate: &gix::remote::fetch::outcome::Negotiate,
112203
update_refs: gix::remote::fetch::refs::update::Outcome,
113204
refspecs: &[gix::refspec::RefSpec],
114205
mut map: gix::remote::fetch::RefMap,
@@ -212,8 +303,10 @@ pub(crate) mod function {
212303
refspecs.len()
213304
)?;
214305
}
215-
if negotiation_rounds != 1 {
216-
writeln!(err, "needed {negotiation_rounds} rounds of pack-negotiation")?;
306+
match negotiate.rounds.len() {
307+
0 => writeln!(err, "no negotiation was necessary")?,
308+
1 => {}
309+
rounds => writeln!(err, "needed {rounds} rounds of pack-negotiation")?,
217310
}
218311
Ok(())
219312
}

0 commit comments

Comments
 (0)