Skip to content

Commit b82edc8

Browse files
committed
feat!: list commit-graph entries by graph traversal, move commit-graph up to gix level.
This is merely a debug tool to learn about generation numbers. All commit-graph commands now operate on a repository.
1 parent 574e0f4 commit b82edc8

File tree

10 files changed

+213
-134
lines changed

10 files changed

+213
-134
lines changed

Diff for: gitoxide-core/src/commitgraph/list.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
pub(crate) mod function {
2+
use std::borrow::Cow;
3+
use std::ffi::OsString;
4+
5+
use anyhow::{bail, Context};
6+
use gix::prelude::ObjectIdExt;
7+
use gix::traverse::commit::Sorting;
8+
9+
use crate::OutputFormat;
10+
11+
pub fn list(
12+
mut repo: gix::Repository,
13+
spec: OsString,
14+
mut out: impl std::io::Write,
15+
format: OutputFormat,
16+
) -> anyhow::Result<()> {
17+
if format != OutputFormat::Human {
18+
bail!("Only human output is currently supported");
19+
}
20+
let graph = repo
21+
.commit_graph()
22+
.context("a commitgraph is required, but none was found")?;
23+
repo.object_cache_size_if_unset(4 * 1024 * 1024);
24+
25+
let spec = gix::path::os_str_into_bstr(&spec)?;
26+
let id = repo
27+
.rev_parse_single(spec)
28+
.context("Only single revisions are currently supported")?;
29+
let commits = id
30+
.object()?
31+
.peel_to_kind(gix::object::Kind::Commit)
32+
.context("Need commitish as starting point")?
33+
.id()
34+
.ancestors()
35+
.sorting(Sorting::ByCommitTimeNewestFirst)
36+
.all()?;
37+
for commit in commits {
38+
let commit = commit?;
39+
writeln!(
40+
out,
41+
"{} {} {} {}",
42+
commit.id().shorten_or_id(),
43+
commit.commit_time.expect("traversal with date"),
44+
commit.parent_ids.len(),
45+
graph.commit_by_id(commit.id).map_or_else(
46+
|| Cow::Borrowed("<NOT IN GRAPH-CACHE>"),
47+
|c| Cow::Owned(format!(
48+
"{} {}",
49+
c.root_tree_id().to_owned().attach(&repo).shorten_or_id(),
50+
c.generation()
51+
))
52+
)
53+
)?;
54+
}
55+
Ok(())
56+
}
57+
}

Diff for: gitoxide-core/src/commitgraph/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
pub mod list;
2+
pub use list::function::list;
3+
14
pub mod verify;
5+
pub use verify::function::verify;

Diff for: gitoxide-core/src/commitgraph/verify.rs

+50-57
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,70 @@
1-
use std::{io, path::Path};
2-
3-
use anyhow::{Context as AnyhowContext, Result};
4-
use gix::commitgraph::Graph;
5-
61
use crate::OutputFormat;
72

83
/// A general purpose context for many operations provided here
9-
pub struct Context<W1: io::Write, W2: io::Write> {
4+
pub struct Context<W1: std::io::Write, W2: std::io::Write> {
105
/// A stream to which to output errors
116
pub err: W2,
127
/// A stream to which to output operation results
138
pub out: W1,
149
pub output_statistics: Option<OutputFormat>,
1510
}
1611

17-
impl Default for Context<Vec<u8>, Vec<u8>> {
18-
fn default() -> Self {
12+
pub(crate) mod function {
13+
use std::io;
14+
15+
use crate::commitgraph::verify::Context;
16+
use crate::OutputFormat;
17+
use anyhow::{Context as AnyhowContext, Result};
18+
19+
pub fn verify<W1, W2>(
20+
repo: gix::Repository,
1921
Context {
20-
err: Vec::new(),
21-
out: Vec::new(),
22-
output_statistics: None,
23-
}
24-
}
25-
}
22+
err: _err,
23+
mut out,
24+
output_statistics,
25+
}: Context<W1, W2>,
26+
) -> Result<gix::commitgraph::verify::Outcome>
27+
where
28+
W1: io::Write,
29+
W2: io::Write,
30+
{
31+
let g = repo.commit_graph()?;
2632

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

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

49-
#[cfg_attr(not(feature = "serde"), allow(clippy::single_match))]
50-
match output_statistics {
51-
Some(OutputFormat::Human) => drop(print_human_output(&mut out, &stats)),
52-
#[cfg(feature = "serde")]
53-
Some(OutputFormat::Json) => serde_json::to_writer_pretty(out, &stats)?,
54-
_ => {}
49+
Ok(stats)
5550
}
5651

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

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

69-
write!(out, "\nlongest path length between two commits: ")?;
70-
if let Some(n) = stats.longest_path_length {
71-
writeln!(out, "{n}")?;
72-
} else {
73-
writeln!(out, "unknown")?;
68+
Ok(())
7469
}
75-
76-
Ok(())
7770
}

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

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::ffi::OsString;
22

33
use anyhow::{bail, Context};
4-
use gix::prelude::ObjectIdExt;
4+
use gix::traverse::commit::Sorting;
55

66
use crate::OutputFormat;
77

@@ -20,14 +20,23 @@ pub fn list(
2020
let id = repo
2121
.rev_parse_single(spec)
2222
.context("Only single revisions are currently supported")?;
23-
let commit_id = id
23+
let commits = id
2424
.object()?
2525
.peel_to_kind(gix::object::Kind::Commit)
2626
.context("Need commitish as starting point")?
27-
.id
28-
.attach(&repo);
29-
for commit in commit_id.ancestors().all()? {
30-
writeln!(out, "{}", commit?.id().to_hex())?;
27+
.id()
28+
.ancestors()
29+
.sorting(Sorting::ByCommitTimeNewestFirst)
30+
.all()?;
31+
for commit in commits {
32+
let commit = commit?;
33+
writeln!(
34+
out,
35+
"{} {} {}",
36+
commit.id().shorten_or_id(),
37+
commit.commit_time.expect("traversal with date"),
38+
commit.parent_ids.len()
39+
)?;
3140
}
3241
Ok(())
3342
}

Diff for: src/plumbing/main.rs

+31-21
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use gitoxide_core as core;
1313
use gitoxide_core::pack::verify;
1414
use gix::bstr::io::BufReadExt;
1515

16+
use crate::plumbing::options::commitgraph;
1617
use crate::{
1718
plumbing::{
1819
options::{
@@ -128,6 +129,36 @@ pub fn main() -> Result<()> {
128129
})?;
129130

130131
match cmd {
132+
Subcommands::CommitGraph(cmd) => match cmd {
133+
commitgraph::Subcommands::List { spec } => prepare_and_run(
134+
"commitgraph-list",
135+
auto_verbose,
136+
progress,
137+
progress_keep_open,
138+
None,
139+
move |_progress, out, _err| core::commitgraph::list(repository(Mode::Lenient)?, spec, out, format),
140+
)
141+
.map(|_| ()),
142+
commitgraph::Subcommands::Verify { statistics } => prepare_and_run(
143+
"commitgraph-verify",
144+
auto_verbose,
145+
progress,
146+
progress_keep_open,
147+
None,
148+
move |_progress, out, err| {
149+
let output_statistics = if statistics { Some(format) } else { None };
150+
core::commitgraph::verify(
151+
repository(Mode::Lenient)?,
152+
core::commitgraph::verify::Context {
153+
err,
154+
out,
155+
output_statistics,
156+
},
157+
)
158+
},
159+
)
160+
.map(|_| ()),
161+
},
131162
#[cfg(feature = "gitoxide-core-blocking-client")]
132163
Subcommands::Clone(crate::plumbing::options::clone::Platform {
133164
handshake_info,
@@ -270,27 +301,6 @@ pub fn main() -> Result<()> {
270301
)
271302
.map(|_| ()),
272303
Subcommands::Free(subcommands) => match subcommands {
273-
free::Subcommands::CommitGraph(subcommands) => match subcommands {
274-
free::commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run(
275-
"commitgraph-verify",
276-
auto_verbose,
277-
progress,
278-
progress_keep_open,
279-
None,
280-
move |_progress, out, err| {
281-
let output_statistics = if statistics { Some(format) } else { None };
282-
core::commitgraph::verify::graph_or_file(
283-
path,
284-
core::commitgraph::verify::Context {
285-
err,
286-
out,
287-
output_statistics,
288-
},
289-
)
290-
},
291-
)
292-
.map(|_| ()),
293-
},
294304
free::Subcommands::Index(free::index::Platform {
295305
object_hash,
296306
index_path,

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

-20
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
#[derive(Debug, clap::Subcommand)]
22
#[clap(visible_alias = "no-repo")]
33
pub enum Subcommands {
4-
/// Subcommands for interacting with commit-graphs
5-
#[clap(subcommand)]
6-
CommitGraph(commitgraph::Subcommands),
74
/// Subcommands for interacting with mailmaps
85
Mailmap {
96
#[clap(flatten)]
@@ -16,23 +13,6 @@ pub enum Subcommands {
1613
Index(index::Platform),
1714
}
1815

19-
///
20-
pub mod commitgraph {
21-
use std::path::PathBuf;
22-
23-
#[derive(Debug, clap::Subcommand)]
24-
pub enum Subcommands {
25-
/// Verify the integrity of a commit graph
26-
Verify {
27-
/// The path to '.git/objects/info/', '.git/objects/info/commit-graphs/', or '.git/objects/info/commit-graph' to validate.
28-
path: PathBuf,
29-
/// output statistical information about the pack
30-
#[clap(long, short = 's')]
31-
statistics: bool,
32-
},
33-
}
34-
}
35-
3616
pub mod index {
3717
use std::path::PathBuf;
3818

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ pub struct Args {
7272

7373
#[derive(Debug, clap::Subcommand)]
7474
pub enum Subcommands {
75+
/// Subcommands for interacting with commit-graphs
76+
#[clap(subcommand)]
77+
CommitGraph(commitgraph::Subcommands),
7578
/// Interact with the object database.
7679
#[clap(subcommand)]
7780
Odb(odb::Subcommands),
@@ -412,13 +415,36 @@ pub mod credential {
412415
}
413416
}
414417

418+
///
419+
pub mod commitgraph {
420+
#[derive(Debug, clap::Subcommand)]
421+
pub enum Subcommands {
422+
/// Verify the integrity of a commit graph
423+
Verify {
424+
/// output statistical information about the pack
425+
#[clap(long, short = 's')]
426+
statistics: bool,
427+
},
428+
/// List all entries in the commit-graph as reachable by starting from `HEAD`.
429+
List {
430+
/// The rev-spec to list reachable commits from.
431+
#[clap(default_value = "@")]
432+
spec: std::ffi::OsString,
433+
},
434+
}
435+
}
436+
415437
pub mod revision {
416438
#[derive(Debug, clap::Subcommand)]
417439
#[clap(visible_alias = "rev", visible_alias = "r")]
418440
pub enum Subcommands {
419441
/// List all commits reachable from the given rev-spec.
420442
#[clap(visible_alias = "l")]
421-
List { spec: std::ffi::OsString },
443+
List {
444+
/// The rev-spec to list reachable commits from.
445+
#[clap(default_value = "@")]
446+
spec: std::ffi::OsString,
447+
},
422448
/// Provide the revision specification like `@~1` to explain.
423449
#[clap(visible_alias = "e")]
424450
Explain { spec: std::ffi::OsString },

0 commit comments

Comments
 (0)