diff --git a/gitoxide-core/src/repository/log.rs b/gitoxide-core/src/repository/log.rs new file mode 100644 index 00000000000..c3eee8ec47d --- /dev/null +++ b/gitoxide-core/src/repository/log.rs @@ -0,0 +1,50 @@ +use anyhow::bail; +use gix::bstr::{BString, ByteSlice}; + +pub fn log(mut repo: gix::Repository, out: &mut dyn std::io::Write, path: Option) -> anyhow::Result<()> { + repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?)); + + if let Some(path) = path { + log_file(repo, out, path) + } else { + log_all(repo, out) + } +} + +fn log_all(repo: gix::Repository, out: &mut dyn std::io::Write) -> Result<(), anyhow::Error> { + let head = repo.head()?.peel_to_commit_in_place()?; + let topo = gix::traverse::commit::topo::Builder::from_iters(&repo.objects, [head.id], None::>) + .build()?; + + for info in topo { + let info = info?; + + write_info(&repo, &mut *out, &info)?; + } + + Ok(()) +} + +fn log_file(_repo: gix::Repository, _out: &mut dyn std::io::Write, _path: BString) -> anyhow::Result<()> { + bail!("File-based lookup isn't yet implemented in a way that is competitively fast"); +} + +fn write_info( + repo: &gix::Repository, + mut out: impl std::io::Write, + info: &gix::traverse::commit::Info, +) -> Result<(), std::io::Error> { + let commit = repo.find_commit(info.id).unwrap(); + + let message = commit.message_raw_sloppy(); + let title = message.lines().next(); + + writeln!( + out, + "{} {}", + info.id.to_hex_with_len(8), + title.map_or_else(|| "".into(), BString::from) + )?; + + Ok(()) +} diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index 489d5c32e66..c9044f99cd9 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -46,6 +46,7 @@ pub mod commitgraph; mod fsck; pub use fsck::function as fsck; pub mod index; +pub mod log; pub mod mailmap; mod merge_base; pub use merge_base::merge_base; diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index b0a69339c29..2391dd14cd3 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -269,6 +269,15 @@ pub fn main() -> Result<()> { }, ), }, + Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run( + "log", + trace, + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::log::log(repository(Mode::Lenient)?, out, pathspec), + ), Subcommands::Worktree(crate::plumbing::options::worktree::Platform { cmd }) => match cmd { crate::plumbing::options::worktree::SubCommands::List => prepare_and_run( "worktree-list", diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index c1a3ec670d7..b0928c0d426 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -146,6 +146,7 @@ pub enum Subcommands { MergeBase(merge_base::Command), Merge(merge::Platform), Diff(diff::Platform), + Log(log::Platform), Worktree(worktree::Platform), /// Subcommands that need no git repository to run. #[clap(subcommand)] @@ -499,6 +500,18 @@ pub mod diff { } } +pub mod log { + use gix::bstr::BString; + + /// List all commits in a repository, optionally limited to those that change a given path + #[derive(Debug, clap::Parser)] + pub struct Platform { + /// The git path specification to show a log for. + #[clap(value_parser = crate::shared::AsBString)] + pub pathspec: Option, + } +} + pub mod config { use gix::bstr::BString;