Skip to content

Commit

Permalink
feat!: list commit-graph entries by graph traversal, move commit-grap…
Browse files Browse the repository at this point in the history
…h up to `gix` level.

This is merely a debug tool to learn about generation numbers.
All commit-graph commands now operate on a repository.
  • Loading branch information
Byron committed Jun 11, 2023
1 parent 574e0f4 commit b82edc8
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 134 deletions.
57 changes: 57 additions & 0 deletions gitoxide-core/src/commitgraph/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
pub(crate) mod function {
use std::borrow::Cow;
use std::ffi::OsString;

use anyhow::{bail, Context};
use gix::prelude::ObjectIdExt;
use gix::traverse::commit::Sorting;

use crate::OutputFormat;

pub fn list(
mut repo: gix::Repository,
spec: OsString,
mut out: impl std::io::Write,
format: OutputFormat,
) -> anyhow::Result<()> {
if format != OutputFormat::Human {
bail!("Only human output is currently supported");
}
let graph = repo
.commit_graph()
.context("a commitgraph is required, but none was found")?;
repo.object_cache_size_if_unset(4 * 1024 * 1024);

let spec = gix::path::os_str_into_bstr(&spec)?;
let id = repo
.rev_parse_single(spec)
.context("Only single revisions are currently supported")?;
let commits = id
.object()?
.peel_to_kind(gix::object::Kind::Commit)
.context("Need commitish as starting point")?
.id()
.ancestors()
.sorting(Sorting::ByCommitTimeNewestFirst)
.all()?;
for commit in commits {
let commit = commit?;
writeln!(
out,
"{} {} {} {}",
commit.id().shorten_or_id(),
commit.commit_time.expect("traversal with date"),
commit.parent_ids.len(),
graph.commit_by_id(commit.id).map_or_else(
|| Cow::Borrowed("<NOT IN GRAPH-CACHE>"),
|c| Cow::Owned(format!(
"{} {}",
c.root_tree_id().to_owned().attach(&repo).shorten_or_id(),
c.generation()
))
)
)?;
}
Ok(())
}
}
4 changes: 4 additions & 0 deletions gitoxide-core/src/commitgraph/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pub mod list;
pub use list::function::list;

pub mod verify;
pub use verify::function::verify;
107 changes: 50 additions & 57 deletions gitoxide-core/src/commitgraph/verify.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,70 @@
use std::{io, path::Path};

use anyhow::{Context as AnyhowContext, Result};
use gix::commitgraph::Graph;

use crate::OutputFormat;

/// A general purpose context for many operations provided here
pub struct Context<W1: io::Write, W2: io::Write> {
pub struct Context<W1: std::io::Write, W2: std::io::Write> {
/// A stream to which to output errors
pub err: W2,
/// A stream to which to output operation results
pub out: W1,
pub output_statistics: Option<OutputFormat>,
}

impl Default for Context<Vec<u8>, Vec<u8>> {
fn default() -> Self {
pub(crate) mod function {
use std::io;

use crate::commitgraph::verify::Context;
use crate::OutputFormat;
use anyhow::{Context as AnyhowContext, Result};

pub fn verify<W1, W2>(
repo: gix::Repository,
Context {
err: Vec::new(),
out: Vec::new(),
output_statistics: None,
}
}
}
err: _err,
mut out,
output_statistics,
}: Context<W1, W2>,
) -> Result<gix::commitgraph::verify::Outcome>
where
W1: io::Write,
W2: io::Write,
{
let g = repo.commit_graph()?;

pub fn graph_or_file<W1, W2>(
path: impl AsRef<Path>,
Context {
err: _err,
mut out,
output_statistics,
}: Context<W1, W2>,
) -> Result<gix::commitgraph::verify::Outcome>
where
W1: io::Write,
W2: io::Write,
{
let g = Graph::at(path).with_context(|| "Could not open commit graph")?;
#[allow(clippy::unnecessary_wraps, unknown_lints)]
fn noop_processor(_commit: &gix::commitgraph::file::Commit<'_>) -> std::result::Result<(), std::fmt::Error> {
Ok(())
}
let stats = g
.verify_integrity(noop_processor)
.with_context(|| "Verification failure")?;

#[allow(clippy::unnecessary_wraps, unknown_lints)]
fn noop_processor(_commit: &gix::commitgraph::file::Commit<'_>) -> std::result::Result<(), std::fmt::Error> {
Ok(())
}
let stats = g
.verify_integrity(noop_processor)
.with_context(|| "Verification failure")?;
#[cfg_attr(not(feature = "serde"), allow(clippy::single_match))]
match output_statistics {
Some(OutputFormat::Human) => drop(print_human_output(&mut out, &stats)),
#[cfg(feature = "serde")]
Some(OutputFormat::Json) => serde_json::to_writer_pretty(out, &stats)?,
_ => {}
}

#[cfg_attr(not(feature = "serde"), allow(clippy::single_match))]
match output_statistics {
Some(OutputFormat::Human) => drop(print_human_output(&mut out, &stats)),
#[cfg(feature = "serde")]
Some(OutputFormat::Json) => serde_json::to_writer_pretty(out, &stats)?,
_ => {}
Ok(stats)
}

Ok(stats)
}
fn print_human_output(out: &mut impl io::Write, stats: &gix::commitgraph::verify::Outcome) -> io::Result<()> {
writeln!(out, "number of commits with the given number of parents")?;
let mut parent_counts: Vec<_> = stats.parent_counts.iter().map(|(a, b)| (*a, *b)).collect();
parent_counts.sort_by_key(|e| e.0);
for (parent_count, commit_count) in parent_counts.into_iter() {
writeln!(out, "\t{parent_count:>2}: {commit_count}")?;
}
writeln!(out, "\t->: {}", stats.num_commits)?;

fn print_human_output(out: &mut impl io::Write, stats: &gix::commitgraph::verify::Outcome) -> io::Result<()> {
writeln!(out, "number of commits with the given number of parents")?;
let mut parent_counts: Vec<_> = stats.parent_counts.iter().map(|(a, b)| (*a, *b)).collect();
parent_counts.sort_by_key(|e| e.0);
for (parent_count, commit_count) in parent_counts.into_iter() {
writeln!(out, "\t{parent_count:>2}: {commit_count}")?;
}
writeln!(out, "\t->: {}", stats.num_commits)?;
write!(out, "\nlongest path length between two commits: ")?;
if let Some(n) = stats.longest_path_length {
writeln!(out, "{n}")?;
} else {
writeln!(out, "unknown")?;
}

write!(out, "\nlongest path length between two commits: ")?;
if let Some(n) = stats.longest_path_length {
writeln!(out, "{n}")?;
} else {
writeln!(out, "unknown")?;
Ok(())
}

Ok(())
}
21 changes: 15 additions & 6 deletions gitoxide-core/src/repository/revision/list.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::ffi::OsString;

use anyhow::{bail, Context};
use gix::prelude::ObjectIdExt;
use gix::traverse::commit::Sorting;

use crate::OutputFormat;

Expand All @@ -20,14 +20,23 @@ pub fn list(
let id = repo
.rev_parse_single(spec)
.context("Only single revisions are currently supported")?;
let commit_id = id
let commits = id
.object()?
.peel_to_kind(gix::object::Kind::Commit)
.context("Need commitish as starting point")?
.id
.attach(&repo);
for commit in commit_id.ancestors().all()? {
writeln!(out, "{}", commit?.id().to_hex())?;
.id()
.ancestors()
.sorting(Sorting::ByCommitTimeNewestFirst)
.all()?;
for commit in commits {
let commit = commit?;
writeln!(
out,
"{} {} {}",
commit.id().shorten_or_id(),
commit.commit_time.expect("traversal with date"),
commit.parent_ids.len()
)?;
}
Ok(())
}
52 changes: 31 additions & 21 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use gitoxide_core as core;
use gitoxide_core::pack::verify;
use gix::bstr::io::BufReadExt;

use crate::plumbing::options::commitgraph;
use crate::{
plumbing::{
options::{
Expand Down Expand Up @@ -128,6 +129,36 @@ pub fn main() -> Result<()> {
})?;

match cmd {
Subcommands::CommitGraph(cmd) => match cmd {
commitgraph::Subcommands::List { spec } => prepare_and_run(
"commitgraph-list",
auto_verbose,
progress,
progress_keep_open,
None,
move |_progress, out, _err| core::commitgraph::list(repository(Mode::Lenient)?, spec, out, format),
)
.map(|_| ()),
commitgraph::Subcommands::Verify { statistics } => prepare_and_run(
"commitgraph-verify",
auto_verbose,
progress,
progress_keep_open,
None,
move |_progress, out, err| {
let output_statistics = if statistics { Some(format) } else { None };
core::commitgraph::verify(
repository(Mode::Lenient)?,
core::commitgraph::verify::Context {
err,
out,
output_statistics,
},
)
},
)
.map(|_| ()),
},
#[cfg(feature = "gitoxide-core-blocking-client")]
Subcommands::Clone(crate::plumbing::options::clone::Platform {
handshake_info,
Expand Down Expand Up @@ -270,27 +301,6 @@ pub fn main() -> Result<()> {
)
.map(|_| ()),
Subcommands::Free(subcommands) => match subcommands {
free::Subcommands::CommitGraph(subcommands) => match subcommands {
free::commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run(
"commitgraph-verify",
auto_verbose,
progress,
progress_keep_open,
None,
move |_progress, out, err| {
let output_statistics = if statistics { Some(format) } else { None };
core::commitgraph::verify::graph_or_file(
path,
core::commitgraph::verify::Context {
err,
out,
output_statistics,
},
)
},
)
.map(|_| ()),
},
free::Subcommands::Index(free::index::Platform {
object_hash,
index_path,
Expand Down
20 changes: 0 additions & 20 deletions src/plumbing/options/free.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#[derive(Debug, clap::Subcommand)]
#[clap(visible_alias = "no-repo")]
pub enum Subcommands {
/// Subcommands for interacting with commit-graphs
#[clap(subcommand)]
CommitGraph(commitgraph::Subcommands),
/// Subcommands for interacting with mailmaps
Mailmap {
#[clap(flatten)]
Expand All @@ -16,23 +13,6 @@ pub enum Subcommands {
Index(index::Platform),
}

///
pub mod commitgraph {
use std::path::PathBuf;

#[derive(Debug, clap::Subcommand)]
pub enum Subcommands {
/// Verify the integrity of a commit graph
Verify {
/// The path to '.git/objects/info/', '.git/objects/info/commit-graphs/', or '.git/objects/info/commit-graph' to validate.
path: PathBuf,
/// output statistical information about the pack
#[clap(long, short = 's')]
statistics: bool,
},
}
}

pub mod index {
use std::path::PathBuf;

Expand Down
28 changes: 27 additions & 1 deletion src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ pub struct Args {

#[derive(Debug, clap::Subcommand)]
pub enum Subcommands {
/// Subcommands for interacting with commit-graphs
#[clap(subcommand)]
CommitGraph(commitgraph::Subcommands),
/// Interact with the object database.
#[clap(subcommand)]
Odb(odb::Subcommands),
Expand Down Expand Up @@ -412,13 +415,36 @@ pub mod credential {
}
}

///
pub mod commitgraph {
#[derive(Debug, clap::Subcommand)]
pub enum Subcommands {
/// Verify the integrity of a commit graph
Verify {
/// output statistical information about the pack
#[clap(long, short = 's')]
statistics: bool,
},
/// List all entries in the commit-graph as reachable by starting from `HEAD`.
List {
/// The rev-spec to list reachable commits from.
#[clap(default_value = "@")]
spec: std::ffi::OsString,
},
}
}

pub mod revision {
#[derive(Debug, clap::Subcommand)]
#[clap(visible_alias = "rev", visible_alias = "r")]
pub enum Subcommands {
/// List all commits reachable from the given rev-spec.
#[clap(visible_alias = "l")]
List { spec: std::ffi::OsString },
List {
/// The rev-spec to list reachable commits from.
#[clap(default_value = "@")]
spec: std::ffi::OsString,
},
/// Provide the revision specification like `@~1` to explain.
#[clap(visible_alias = "e")]
Explain { spec: std::ffi::OsString },
Expand Down
Loading

0 comments on commit b82edc8

Please sign in to comment.