Skip to content

Commit

Permalink
Merge branch 'gix-revision-graph'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Jun 12, 2023
2 parents 71efcbb + 452ed6b commit 036e60a
Show file tree
Hide file tree
Showing 24 changed files with 474 additions and 160 deletions.
45 changes: 44 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ resolver = "2"

[[bin]]
name = "ein"
doc = false
path = "src/ein.rs"
test = false
doctest = false

[[bin]]
name = "gix"
path = "src/gix.rs"
doc = false
test = false
doctest = false

Expand Down Expand Up @@ -182,7 +184,7 @@ sha1_smol = { opt-level = 3 }

[profile.release]
overflow-checks = false
lto = "fat"
#lto = "fat"
# this bloats files but assures destructors are called, important for tempfiles. One day I hope we
# can wire up the 'abrt' signal handler so tempfiles will be removed in case of panics.
panic = 'unwind'
Expand Down
4 changes: 4 additions & 0 deletions gitoxide-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ smallvec = { version = "1.10.0", optional = true }
# for 'query'
rusqlite = { version = "0.29.0", optional = true, features = ["bundled"] }

# for svg graph output
layout-rs = "0.1.1"
open = "4.1.0"

document-features = { version = "0.2.0", optional = true }

[package.metadata.docs.rs]
Expand Down
10 changes: 4 additions & 6 deletions gitoxide-core/src/repository/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,20 @@ pub(crate) mod function {
}

match fetch_outcome.status {
Status::NoPackReceived { .. } => {
Status::NoPackReceived { dry_run, .. } => {
assert!(!dry_run, "dry-run unsupported");
writeln!(err, "The cloned repository appears to be empty")?;
}
Status::DryRun { .. } => unreachable!("dry-run unsupported"),
Status::Change {
update_refs,
negotiation_rounds,
..
update_refs, negotiate, ..
} => {
let remote = repo
.find_default_remote(gix::remote::Direction::Fetch)
.expect("one origin remote")?;
let ref_specs = remote.refspecs(gix::remote::Direction::Fetch);
print_updates(
&repo,
negotiation_rounds,
&negotiate,
update_refs,
ref_specs,
fetch_outcome.ref_map,
Expand Down
137 changes: 115 additions & 22 deletions gitoxide-core/src/repository/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ pub struct Options {
pub ref_specs: Vec<BString>,
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 @@ -31,6 +38,8 @@ pub(crate) mod function {
dry_run,
remote,
handshake_info,
negotiation_info,
open_negotiation_graph,
shallow,
ref_specs,
}: Options,
Expand Down Expand Up @@ -62,41 +71,49 @@ pub(crate) mod function {

let ref_specs = remote.refspecs(gix::remote::Direction::Fetch);
match res.status {
Status::NoPackReceived { update_refs } => {
print_updates(&repo, 1, update_refs, ref_specs, res.ref_map, &mut out, err)
}
Status::DryRun {
update_refs,
negotiation_rounds,
} => print_updates(
&repo,
negotiation_rounds,
update_refs,
ref_specs,
res.ref_map,
&mut out,
err,
),
Status::Change {
Status::NoPackReceived {
update_refs,
write_pack_bundle,
negotiation_rounds,
negotiate,
dry_run: _,
} => {
let negotiate_default = Default::default();
print_updates(
&repo,
negotiation_rounds,
negotiate.as_ref().unwrap_or(&negotiate_default),
update_refs,
ref_specs,
res.ref_map,
&mut out,
err,
)?;
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 {
update_refs,
write_pack_bundle,
negotiate,
} => {
print_updates(&repo, &negotiate, update_refs, ref_specs, res.ref_map, &mut out, err)?;
if let Some(data_path) = write_pack_bundle.data_path {
writeln!(out, "pack file: \"{}\"", data_path.display()).ok();
}
if let Some(index_path) = write_pack_bundle.index_path {
writeln!(out, "index file: \"{}\"", index_path.display()).ok();
}
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 @@ -106,9 +123,83 @@ 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>,
) -> std::io::Result<()> {
writeln!(out, "Negotiation Phase Information")?;
match negotiate {
Some(negotiate) => {
writeln!(out, "\t{:?}", negotiate.rounds)?;
writeln!(out, "\tnum commits traversed in graph: {}", negotiate.graph.len())
}
None => writeln!(out, "\tno negotiation performed"),
}
}

pub(crate) fn print_updates(
repo: &gix::Repository,
negotiation_rounds: usize,
negotiate: &gix::remote::fetch::outcome::Negotiate,
update_refs: gix::remote::fetch::refs::update::Outcome,
refspecs: &[gix::refspec::RefSpec],
mut map: gix::remote::fetch::RefMap,
Expand Down Expand Up @@ -212,8 +303,10 @@ pub(crate) mod function {
refspecs.len()
)?;
}
if negotiation_rounds != 1 {
writeln!(err, "needed {negotiation_rounds} rounds of pack-negotiation")?;
match negotiate.rounds.len() {
0 => writeln!(err, "no negotiation was necessary")?,
1 => {}
rounds => writeln!(err, "needed {rounds} rounds of pack-negotiation")?,
}
Ok(())
}
Expand Down
Loading

0 comments on commit 036e60a

Please sign in to comment.