1use std::{
2 io::{stdin, BufReader},
3 path::PathBuf,
4 sync::{
5 atomic::{AtomicBool, Ordering},
6 Arc,
7 },
8};
9
10use anyhow::{anyhow, Context, Result};
11use clap::{CommandFactory, Parser};
12use gitoxide_core as core;
13use gitoxide_core::{pack::verify, repository::PathsOrPatterns};
14use gix::bstr::{io::BufReadExt, BString};
15
16use crate::{
17 plumbing::{
18 options::{
19 attributes, branch, commit, commitgraph, config, credential, exclude, free, fsck, index, mailmap, merge,
20 odb, revision, tag, tree, Args, Subcommands,
21 },
22 show_progress,
23 },
24 shared::pretty::prepare_and_run,
25};
26
27#[cfg(feature = "gitoxide-core-async-client")]
28pub mod async_util {
29 use crate::shared::ProgressRange;
30
31 #[cfg(not(feature = "prodash-render-line"))]
32 compile_error!("BUG: Need at least a line renderer in async mode");
33
34 pub fn prepare(
35 verbose: bool,
36 trace: bool,
37 name: &str,
38 range: impl Into<Option<ProgressRange>>,
39 ) -> (
40 Option<prodash::render::line::JoinHandle>,
41 gix_features::progress::DoOrDiscard<prodash::tree::Item>,
42 ) {
43 use crate::shared::{self, STANDARD_RANGE};
44 shared::init_env_logger();
45
46 if verbose {
47 let progress = shared::progress_tree(trace);
48 let sub_progress = progress.add_child(name);
49 let ui_handle = shared::setup_line_renderer_range(&progress, range.into().unwrap_or(STANDARD_RANGE));
50 (Some(ui_handle), Some(sub_progress).into())
51 } else {
52 (None, None.into())
53 }
54 }
55}
56
57pub fn main() -> Result<()> {
58 let args: Args = Args::parse_from(gix::env::args_os());
59 let thread_limit = args.threads;
60 let verbose = args.verbose;
61 let format = args.format;
62 let cmd = args.cmd;
63 #[cfg_attr(not(feature = "tracing"), allow(unused_mut))]
64 #[cfg_attr(feature = "tracing", allow(unused_assignments))]
65 let mut trace = false;
66 #[cfg(feature = "tracing")]
67 {
68 trace = args.trace;
69 }
70 let object_hash = args.object_hash;
71 let config = args.config;
72 let repository = args.repository;
73 let repository_path = repository.clone();
74 enum Mode {
75 Strict,
76 StrictWithGitInstallConfig,
77 Lenient,
78 LenientWithGitInstallConfig,
79 }
80
81 let repository = {
82 let config = config.clone();
83 move |mut mode: Mode| -> Result<gix::Repository> {
84 let mut mapping: gix::sec::trust::Mapping<gix::open::Options> = Default::default();
85 if !config.is_empty() {
86 mode = match mode {
87 Mode::Lenient => Mode::Strict,
88 Mode::LenientWithGitInstallConfig => Mode::StrictWithGitInstallConfig,
89 _ => mode,
90 };
91 }
92 let strict_toggle = matches!(mode, Mode::Strict | Mode::StrictWithGitInstallConfig) || args.strict;
93 mapping.full = mapping.full.strict_config(strict_toggle);
94 mapping.reduced = mapping.reduced.strict_config(strict_toggle);
95 let git_installation = matches!(
96 mode,
97 Mode::StrictWithGitInstallConfig | Mode::LenientWithGitInstallConfig
98 );
99 let to_match_settings = |mut opts: gix::open::Options| {
100 opts.permissions.config.git_binary = git_installation;
101 opts.permissions.attributes.git_binary = git_installation;
102 if config.is_empty() {
103 opts
104 } else {
105 opts.cli_overrides(config.clone())
106 }
107 };
108 mapping.full.modify(to_match_settings);
109 mapping.reduced.modify(to_match_settings);
110 let mut repo = gix::ThreadSafeRepository::discover_with_environment_overrides_opts(
111 repository,
112 Default::default(),
113 mapping,
114 )
115 .map(gix::Repository::from)?;
116 if !config.is_empty() {
117 repo.config_snapshot_mut()
118 .append_config(config.iter(), gix::config::Source::Cli)
119 .context("Unable to parse command-line configuration")?;
120 }
121 {
122 let mut config_mut = repo.config_snapshot_mut();
123 if config_mut
125 .boolean(gix::config::tree::Gitoxide::PARSE_PRECIOUS)
126 .is_none()
127 {
128 config_mut.set_raw_value(&gix::config::tree::Gitoxide::PARSE_PRECIOUS, "true")?;
129 }
130 }
131 Ok(repo)
132 }
133 };
134
135 let progress;
136 let progress_keep_open;
137 #[cfg(feature = "prodash-render-tui")]
138 {
139 progress = args.progress;
140 progress_keep_open = args.progress_keep_open;
141 }
142 #[cfg(not(feature = "prodash-render-tui"))]
143 {
144 progress = false;
145 progress_keep_open = false;
146 }
147 let auto_verbose = !progress && !args.no_verbose;
148
149 let should_interrupt = Arc::new(AtomicBool::new(false));
150 #[allow(unsafe_code)]
151 unsafe {
152 gix::interrupt::init_handler(1, {
154 let should_interrupt = Arc::clone(&should_interrupt);
155 move || should_interrupt.store(true, Ordering::SeqCst)
156 })?;
157 }
158
159 match cmd {
160 Subcommands::Env => prepare_and_run(
161 "env",
162 trace,
163 verbose,
164 progress,
165 progress_keep_open,
166 None,
167 move |_progress, out, _err| core::env(out, format),
168 ),
169 Subcommands::Merge(merge::Platform { cmd }) => match cmd {
170 merge::SubCommands::File {
171 resolve_with,
172 ours,
173 base,
174 theirs,
175 } => prepare_and_run(
176 "merge-file",
177 trace,
178 verbose,
179 progress,
180 progress_keep_open,
181 None,
182 move |_progress, out, _err| {
183 core::repository::merge::file(
184 repository(Mode::Lenient)?,
185 out,
186 format,
187 resolve_with.map(Into::into),
188 base,
189 ours,
190 theirs,
191 )
192 },
193 ),
194 merge::SubCommands::Tree {
195 opts:
196 merge::SharedOptions {
197 in_memory,
198 file_favor,
199 tree_favor,
200 debug,
201 },
202 ours,
203 base,
204 theirs,
205 } => prepare_and_run(
206 "merge-tree",
207 trace,
208 verbose,
209 progress,
210 progress_keep_open,
211 None,
212 move |_progress, out, err| {
213 core::repository::merge::tree(
214 repository(Mode::Lenient)?,
215 out,
216 err,
217 base,
218 ours,
219 theirs,
220 core::repository::merge::tree::Options {
221 format,
222 file_favor: file_favor.map(Into::into),
223 in_memory,
224 tree_favor: tree_favor.map(Into::into),
225 debug,
226 },
227 )
228 },
229 ),
230 merge::SubCommands::Commit {
231 opts:
232 merge::SharedOptions {
233 in_memory,
234 file_favor,
235 tree_favor,
236 debug,
237 },
238 ours,
239 theirs,
240 } => prepare_and_run(
241 "merge-commit",
242 trace,
243 verbose,
244 progress,
245 progress_keep_open,
246 None,
247 move |_progress, out, err| {
248 core::repository::merge::commit(
249 repository(Mode::Lenient)?,
250 out,
251 err,
252 ours,
253 theirs,
254 core::repository::merge::tree::Options {
255 format,
256 file_favor: file_favor.map(Into::into),
257 tree_favor: tree_favor.map(Into::into),
258 in_memory,
259 debug,
260 },
261 )
262 },
263 ),
264 },
265 Subcommands::MergeBase(crate::plumbing::options::merge_base::Command { first, others }) => prepare_and_run(
266 "merge-base",
267 trace,
268 verbose,
269 progress,
270 progress_keep_open,
271 None,
272 move |_progress, out, _err| {
273 core::repository::merge_base(repository(Mode::Lenient)?, first, others, out, format)
274 },
275 ),
276 Subcommands::Diff(crate::plumbing::options::diff::Platform { cmd }) => match cmd {
277 crate::plumbing::options::diff::SubCommands::Tree {
278 old_treeish,
279 new_treeish,
280 } => prepare_and_run(
281 "diff-tree",
282 trace,
283 verbose,
284 progress,
285 progress_keep_open,
286 None,
287 move |_progress, out, _err| {
288 core::repository::diff::tree(repository(Mode::Lenient)?, out, old_treeish, new_treeish)
289 },
290 ),
291 crate::plumbing::options::diff::SubCommands::File {
292 old_revspec,
293 new_revspec,
294 } => prepare_and_run(
295 "diff-file",
296 trace,
297 verbose,
298 progress,
299 progress_keep_open,
300 None,
301 move |_progress, out, _err| {
302 core::repository::diff::file(repository(Mode::Lenient)?, out, old_revspec, new_revspec)
303 },
304 ),
305 },
306 Subcommands::Log(crate::plumbing::options::log::Platform { pathspec }) => prepare_and_run(
307 "log",
308 trace,
309 verbose,
310 progress,
311 progress_keep_open,
312 None,
313 move |_progress, out, _err| core::repository::log::log(repository(Mode::Lenient)?, out, pathspec),
314 ),
315 Subcommands::Worktree(crate::plumbing::options::worktree::Platform { cmd }) => match cmd {
316 crate::plumbing::options::worktree::SubCommands::List => prepare_and_run(
317 "worktree-list",
318 trace,
319 verbose,
320 progress,
321 progress_keep_open,
322 None,
323 move |_progress, out, _err| core::repository::worktree::list(repository(Mode::Lenient)?, out, format),
324 ),
325 },
326 Subcommands::IsClean | Subcommands::IsChanged => {
327 let mode = if matches!(cmd, Subcommands::IsClean) {
328 core::repository::dirty::Mode::IsClean
329 } else {
330 core::repository::dirty::Mode::IsDirty
331 };
332 prepare_and_run(
333 "clean",
334 trace,
335 verbose,
336 progress,
337 progress_keep_open,
338 None,
339 move |_progress, out, _err| {
340 core::repository::dirty::check(repository(Mode::Lenient)?, mode, out, format)
341 },
342 )
343 }
344 #[cfg(feature = "gitoxide-core-tools-clean")]
345 Subcommands::Clean(crate::plumbing::options::clean::Command {
346 debug,
347 dry_run: _,
348 execute,
349 ignored,
350 precious,
351 directories,
352 pathspec,
353 repositories,
354 pathspec_matches_result,
355 skip_hidden_repositories,
356 find_untracked_repositories,
357 }) => prepare_and_run(
358 "clean",
359 trace,
360 verbose,
361 progress,
362 progress_keep_open,
363 None,
364 move |_progress, out, err| {
365 core::repository::clean(
366 repository(Mode::Lenient)?,
367 out,
368 err,
369 pathspec,
370 core::repository::clean::Options {
371 debug,
372 format,
373 execute,
374 ignored,
375 precious,
376 directories,
377 repositories,
378 pathspec_matches_result,
379 skip_hidden_repositories: skip_hidden_repositories.map(Into::into),
380 find_untracked_repositories: find_untracked_repositories.into(),
381 },
382 )
383 },
384 ),
385 Subcommands::Status(crate::plumbing::options::status::Platform {
386 ignored,
387 format: status_format,
388 statistics,
389 submodules,
390 no_write,
391 pathspec,
392 index_worktree_renames,
393 }) => prepare_and_run(
394 "status",
395 trace,
396 auto_verbose,
397 progress,
398 progress_keep_open,
399 None,
400 move |progress, out, err| {
401 use crate::plumbing::options::status::Submodules;
402 core::repository::status::show(
403 repository(Mode::Lenient)?,
404 pathspec,
405 out,
406 err,
407 progress,
408 core::repository::status::Options {
409 format: match status_format.unwrap_or_default() {
410 crate::plumbing::options::status::Format::Simplified => {
411 core::repository::status::Format::Simplified
412 }
413 crate::plumbing::options::status::Format::PorcelainV2 => {
414 core::repository::status::Format::PorcelainV2
415 }
416 },
417 ignored: ignored.map(|ignored| match ignored.unwrap_or_default() {
418 crate::plumbing::options::status::Ignored::Matching => {
419 core::repository::status::Ignored::Matching
420 }
421 crate::plumbing::options::status::Ignored::Collapsed => {
422 core::repository::status::Ignored::Collapsed
423 }
424 }),
425 output_format: format,
426 statistics,
427 thread_limit: thread_limit.or(cfg!(target_os = "macos").then_some(3)), allow_write: !no_write,
429 index_worktree_renames: index_worktree_renames.map(|percentage| percentage.unwrap_or(0.5)),
430 submodules: submodules.map(|submodules| match submodules {
431 Submodules::All => core::repository::status::Submodules::All,
432 Submodules::RefChange => core::repository::status::Submodules::RefChange,
433 Submodules::Modifications => core::repository::status::Submodules::Modifications,
434 Submodules::None => core::repository::status::Submodules::None,
435 }),
436 },
437 )
438 },
439 ),
440 Subcommands::Submodule(platform) => match platform
441 .cmds
442 .unwrap_or(crate::plumbing::options::submodule::Subcommands::List { dirty_suffix: None })
443 {
444 crate::plumbing::options::submodule::Subcommands::List { dirty_suffix } => prepare_and_run(
445 "submodule-list",
446 trace,
447 verbose,
448 progress,
449 progress_keep_open,
450 None,
451 move |_progress, out, _err| {
452 core::repository::submodule::list(
453 repository(Mode::Lenient)?,
454 out,
455 format,
456 dirty_suffix.map(|suffix| suffix.unwrap_or_else(|| "dirty".to_string())),
457 )
458 },
459 ),
460 },
461 #[cfg(feature = "gitoxide-core-tools-archive")]
462 Subcommands::Archive(crate::plumbing::options::archive::Platform {
463 format,
464 prefix,
465 compression_level,
466 add_path,
467 add_virtual_file,
468 output_file,
469 treeish,
470 }) => prepare_and_run(
471 "archive",
472 trace,
473 auto_verbose,
474 progress,
475 progress_keep_open,
476 None,
477 move |progress, _out, _err| {
478 if add_virtual_file.len() % 2 != 0 {
479 anyhow::bail!(
480 "Virtual files must be specified in pairs of two: slash/separated/path content, got {}",
481 add_virtual_file.join(", ")
482 )
483 }
484 core::repository::archive::stream(
485 repository(Mode::Lenient)?,
486 &output_file,
487 treeish.as_deref(),
488 progress,
489 core::repository::archive::Options {
490 add_paths: add_path,
491 prefix,
492 files: add_virtual_file
493 .chunks_exact(2)
494 .map(|c| (c[0].clone(), c[1].clone()))
495 .collect(),
496 format: format.map(|f| match f {
497 crate::plumbing::options::archive::Format::Internal => {
498 gix::worktree::archive::Format::InternalTransientNonPersistable
499 }
500 crate::plumbing::options::archive::Format::Tar => gix::worktree::archive::Format::Tar,
501 crate::plumbing::options::archive::Format::TarGz => {
502 gix::worktree::archive::Format::TarGz { compression_level }
503 }
504 crate::plumbing::options::archive::Format::Zip => {
505 gix::worktree::archive::Format::Zip { compression_level }
506 }
507 }),
508 },
509 )
510 },
511 ),
512 Subcommands::Branch(platform) => match platform.cmd {
513 branch::Subcommands::List { all } => {
514 use core::repository::branch::list;
515
516 let kind = if all { list::Kind::All } else { list::Kind::Local };
517 let options = list::Options { kind };
518
519 prepare_and_run(
520 "branch-list",
521 trace,
522 auto_verbose,
523 progress,
524 progress_keep_open,
525 None,
526 move |_progress, out, _err| {
527 core::repository::branch::list(repository(Mode::Lenient)?, out, format, options)
528 },
529 )
530 }
531 },
532 #[cfg(feature = "gitoxide-core-tools-corpus")]
533 Subcommands::Corpus(crate::plumbing::options::corpus::Platform { db, path, cmd }) => {
534 let reverse_trace_lines = progress;
535 prepare_and_run(
536 "corpus",
537 trace,
538 auto_verbose,
539 progress,
540 progress_keep_open,
541 core::corpus::PROGRESS_RANGE,
542 move |progress, _out, _err| {
543 let mut engine = core::corpus::Engine::open_or_create(
544 db,
545 core::corpus::engine::State {
546 gitoxide_version: option_env!("GIX_VERSION")
547 .ok_or_else(|| anyhow::anyhow!("GIX_VERSION must be set in build-script"))?
548 .into(),
549 progress,
550 trace_to_progress: trace,
551 reverse_trace_lines,
552 },
553 )?;
554 match cmd {
555 crate::plumbing::options::corpus::SubCommands::Run {
556 dry_run,
557 repo_sql_suffix,
558 include_task,
559 } => engine.run(path, thread_limit, dry_run, repo_sql_suffix, include_task),
560 crate::plumbing::options::corpus::SubCommands::Refresh => engine.refresh(path),
561 }
562 },
563 )
564 }
565 Subcommands::CommitGraph(cmd) => match cmd {
566 commitgraph::Subcommands::List { long_hashes, spec } => prepare_and_run(
567 "commitgraph-list",
568 trace,
569 auto_verbose,
570 progress,
571 progress_keep_open,
572 None,
573 move |_progress, out, _err| {
574 core::repository::commitgraph::list(repository(Mode::Lenient)?, spec, out, long_hashes, format)
575 },
576 )
577 .map(|_| ()),
578 commitgraph::Subcommands::Verify { statistics } => prepare_and_run(
579 "commitgraph-verify",
580 trace,
581 auto_verbose,
582 progress,
583 progress_keep_open,
584 None,
585 move |_progress, out, err| {
586 let output_statistics = if statistics { Some(format) } else { None };
587 core::repository::commitgraph::verify(
588 repository(Mode::Lenient)?,
589 core::repository::commitgraph::verify::Context {
590 err,
591 out,
592 output_statistics,
593 },
594 )
595 },
596 )
597 .map(|_| ()),
598 },
599 #[cfg(feature = "gitoxide-core-blocking-client")]
600 Subcommands::Clone(crate::plumbing::options::clone::Platform {
601 handshake_info,
602 bare,
603 no_tags,
604 ref_name,
605 remote,
606 shallow,
607 directory,
608 }) => {
609 let opts = core::repository::clone::Options {
610 format,
611 bare,
612 handshake_info,
613 no_tags,
614 ref_name,
615 shallow: shallow.into(),
616 };
617 prepare_and_run(
618 "clone",
619 trace,
620 auto_verbose,
621 progress,
622 progress_keep_open,
623 core::repository::clone::PROGRESS_RANGE,
624 move |progress, out, err| core::repository::clone(remote, directory, config, progress, out, err, opts),
625 )
626 }
627 #[cfg(feature = "gitoxide-core-blocking-client")]
628 Subcommands::Fetch(crate::plumbing::options::fetch::Platform {
629 dry_run,
630 handshake_info,
631 negotiation_info,
632 open_negotiation_graph,
633 remote,
634 shallow,
635 ref_spec,
636 }) => {
637 let opts = core::repository::fetch::Options {
638 format,
639 dry_run,
640 remote,
641 handshake_info,
642 negotiation_info,
643 open_negotiation_graph,
644 shallow: shallow.into(),
645 ref_specs: ref_spec,
646 };
647 prepare_and_run(
648 "fetch",
649 trace,
650 auto_verbose,
651 progress,
652 progress_keep_open,
653 core::repository::fetch::PROGRESS_RANGE,
654 move |progress, out, err| {
655 core::repository::fetch(repository(Mode::LenientWithGitInstallConfig)?, progress, out, err, opts)
656 },
657 )
658 }
659 Subcommands::ConfigTree => show_progress(),
660 Subcommands::Credential(cmd) => core::repository::credential(
661 repository(Mode::StrictWithGitInstallConfig).ok(),
662 match cmd {
663 credential::Subcommands::Fill => gix::credentials::program::main::Action::Get,
664 credential::Subcommands::Approve => gix::credentials::program::main::Action::Store,
665 credential::Subcommands::Reject => gix::credentials::program::main::Action::Erase,
666 },
667 ),
668 #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))]
669 Subcommands::Remote(crate::plumbing::options::remote::Platform {
670 name,
671 cmd,
672 handshake_info,
673 }) => {
674 use crate::plumbing::options::remote;
675 match cmd {
676 remote::Subcommands::Refs | remote::Subcommands::RefMap { .. } => {
677 let kind = match cmd {
678 remote::Subcommands::Refs => core::repository::remote::refs::Kind::Remote,
679 remote::Subcommands::RefMap {
680 ref_spec,
681 show_unmapped_remote_refs,
682 } => core::repository::remote::refs::Kind::Tracking {
683 ref_specs: ref_spec,
684 show_unmapped_remote_refs,
685 },
686 };
687 let context = core::repository::remote::refs::Options {
688 name_or_url: name,
689 format,
690 handshake_info,
691 };
692 #[cfg(feature = "gitoxide-core-blocking-client")]
693 {
694 prepare_and_run(
695 "remote-refs",
696 trace,
697 auto_verbose,
698 progress,
699 progress_keep_open,
700 core::repository::remote::refs::PROGRESS_RANGE,
701 move |progress, out, err| {
702 core::repository::remote::refs(
703 repository(Mode::LenientWithGitInstallConfig)?,
704 kind,
705 progress,
706 out,
707 err,
708 context,
709 )
710 },
711 )
712 }
713 #[cfg(feature = "gitoxide-core-async-client")]
714 {
715 let (_handle, progress) = async_util::prepare(
716 auto_verbose,
717 trace,
718 "remote-refs",
719 Some(core::repository::remote::refs::PROGRESS_RANGE),
720 );
721 futures_lite::future::block_on(core::repository::remote::refs(
722 repository(Mode::LenientWithGitInstallConfig)?,
723 kind,
724 progress,
725 std::io::stdout(),
726 std::io::stderr(),
727 context,
728 ))
729 }
730 }
731 }
732 }
733 Subcommands::Config(config::Platform { filter }) => prepare_and_run(
734 "config-list",
735 trace,
736 verbose,
737 progress,
738 progress_keep_open,
739 None,
740 move |_progress, out, _err| {
741 core::repository::config::list(
742 repository(Mode::LenientWithGitInstallConfig)?,
743 filter,
744 config,
745 format,
746 out,
747 )
748 },
749 )
750 .map(|_| ()),
751 Subcommands::Free(subcommands) => match subcommands {
752 free::Subcommands::Discover => prepare_and_run(
753 "discover",
754 trace,
755 verbose,
756 progress,
757 progress_keep_open,
758 None,
759 move |_progress, out, _err| core::discover(&repository_path, out),
760 ),
761 free::Subcommands::CommitGraph(cmd) => match cmd {
762 free::commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run(
763 "commitgraph-verify",
764 trace,
765 auto_verbose,
766 progress,
767 progress_keep_open,
768 None,
769 move |_progress, out, err| {
770 let output_statistics = if statistics { Some(format) } else { None };
771 core::commitgraph::verify(
772 path,
773 core::commitgraph::verify::Context {
774 err,
775 out,
776 output_statistics,
777 },
778 )
779 },
780 )
781 .map(|_| ()),
782 },
783 free::Subcommands::Index(free::index::Platform {
784 object_hash,
785 index_path,
786 cmd,
787 }) => match cmd {
788 free::index::Subcommands::FromList {
789 force,
790 index_output_path,
791 skip_hash,
792 file,
793 } => prepare_and_run(
794 "index-from-list",
795 trace,
796 verbose,
797 progress,
798 progress_keep_open,
799 None,
800 move |_progress, _out, _err| {
801 core::repository::index::from_list(file, index_output_path, force, skip_hash)
802 },
803 ),
804 free::index::Subcommands::CheckoutExclusive {
805 directory,
806 empty_files,
807 repository,
808 keep_going,
809 } => prepare_and_run(
810 "index-checkout",
811 trace,
812 auto_verbose,
813 progress,
814 progress_keep_open,
815 None,
816 move |progress, _out, err| {
817 core::index::checkout_exclusive(
818 index_path,
819 directory,
820 repository,
821 err,
822 progress,
823 &should_interrupt,
824 core::index::checkout_exclusive::Options {
825 index: core::index::Options { object_hash, format },
826 empty_files,
827 keep_going,
828 thread_limit,
829 },
830 )
831 },
832 ),
833 free::index::Subcommands::Info { no_details } => prepare_and_run(
834 "index-info",
835 trace,
836 verbose,
837 progress,
838 progress_keep_open,
839 None,
840 move |_progress, out, err| {
841 core::index::information(
842 index_path,
843 out,
844 err,
845 core::index::information::Options {
846 index: core::index::Options { object_hash, format },
847 extension_details: !no_details,
848 },
849 )
850 },
851 ),
852 free::index::Subcommands::Verify => prepare_and_run(
853 "index-verify",
854 trace,
855 auto_verbose,
856 progress,
857 progress_keep_open,
858 None,
859 move |_progress, out, _err| {
860 core::index::verify(index_path, out, core::index::Options { object_hash, format })
861 },
862 ),
863 },
864 free::Subcommands::Mailmap {
865 cmd: free::mailmap::Platform { path, cmd },
866 } => match cmd {
867 free::mailmap::Subcommands::Verify => prepare_and_run(
868 "mailmap-verify",
869 trace,
870 auto_verbose,
871 progress,
872 progress_keep_open,
873 core::mailmap::PROGRESS_RANGE,
874 move |_progress, out, _err| core::mailmap::verify(path, format, out),
875 ),
876 },
877 free::Subcommands::Pack(subcommands) => match subcommands {
878 free::pack::Subcommands::Create {
879 repository,
880 expansion,
881 thin,
882 statistics,
883 nondeterministic_count,
884 tips,
885 pack_cache_size_mb,
886 counting_threads,
887 object_cache_size_mb,
888 output_directory,
889 } => {
890 let has_tips = !tips.is_empty();
891 prepare_and_run(
892 "pack-create",
893 trace,
894 verbose,
895 progress,
896 progress_keep_open,
897 core::pack::create::PROGRESS_RANGE,
898 move |progress, out, _err| {
899 let input = if has_tips { None } else { stdin_or_bail()?.into() };
900 let repository = repository.unwrap_or_else(|| PathBuf::from("."));
901 let context = core::pack::create::Context {
902 thread_limit,
903 thin,
904 nondeterministic_thread_count: nondeterministic_count.then_some(counting_threads),
905 pack_cache_size_in_bytes: pack_cache_size_mb.unwrap_or(0) * 1_000_000,
906 object_cache_size_in_bytes: object_cache_size_mb.unwrap_or(0) * 1_000_000,
907 statistics: if statistics { Some(format) } else { None },
908 out,
909 expansion: expansion.unwrap_or(if has_tips {
910 core::pack::create::ObjectExpansion::TreeTraversal
911 } else {
912 core::pack::create::ObjectExpansion::None
913 }),
914 };
915 core::pack::create(repository, tips, input, output_directory, progress, context)
916 },
917 )
918 }
919 #[cfg(feature = "gitoxide-core-async-client")]
920 free::pack::Subcommands::Receive {
921 protocol,
922 url,
923 directory,
924 refs,
925 refs_directory,
926 } => {
927 let (_handle, progress) =
928 async_util::prepare(verbose, trace, "pack-receive", core::pack::receive::PROGRESS_RANGE);
929 let fut = core::pack::receive(
930 protocol,
931 &url,
932 directory,
933 refs_directory,
934 refs.into_iter().map(Into::into).collect(),
935 progress,
936 core::pack::receive::Context {
937 thread_limit,
938 format,
939 out: std::io::stdout(),
940 should_interrupt,
941 object_hash,
942 },
943 );
944 return futures_lite::future::block_on(fut);
945 }
946 #[cfg(feature = "gitoxide-core-blocking-client")]
947 free::pack::Subcommands::Receive {
948 protocol,
949 url,
950 directory,
951 refs,
952 refs_directory,
953 } => prepare_and_run(
954 "pack-receive",
955 trace,
956 verbose,
957 progress,
958 progress_keep_open,
959 core::pack::receive::PROGRESS_RANGE,
960 move |progress, out, _err| {
961 core::pack::receive(
962 protocol,
963 &url,
964 directory,
965 refs_directory,
966 refs.into_iter().map(Into::into).collect(),
967 progress,
968 core::pack::receive::Context {
969 thread_limit,
970 format,
971 should_interrupt,
972 out,
973 object_hash,
974 },
975 )
976 },
977 ),
978 free::pack::Subcommands::Explode {
979 check,
980 sink_compress,
981 delete_pack,
982 pack_path,
983 object_path,
984 verify,
985 } => prepare_and_run(
986 "pack-explode",
987 trace,
988 auto_verbose,
989 progress,
990 progress_keep_open,
991 None,
992 move |progress, _out, _err| {
993 core::pack::explode::pack_or_pack_index(
994 pack_path,
995 object_path,
996 check,
997 progress,
998 core::pack::explode::Context {
999 thread_limit,
1000 delete_pack,
1001 sink_compress,
1002 verify,
1003 should_interrupt,
1004 object_hash,
1005 },
1006 )
1007 },
1008 ),
1009 free::pack::Subcommands::Verify {
1010 args:
1011 free::pack::VerifyOptions {
1012 algorithm,
1013 decode,
1014 re_encode,
1015 statistics,
1016 },
1017 path,
1018 } => prepare_and_run(
1019 "pack-verify",
1020 trace,
1021 auto_verbose,
1022 progress,
1023 progress_keep_open,
1024 verify::PROGRESS_RANGE,
1025 move |progress, out, err| {
1026 let mode = verify_mode(decode, re_encode);
1027 let output_statistics = if statistics { Some(format) } else { None };
1028 verify::pack_or_pack_index(
1029 path,
1030 progress,
1031 verify::Context {
1032 output_statistics,
1033 out,
1034 err,
1035 thread_limit,
1036 mode,
1037 algorithm,
1038 should_interrupt: &should_interrupt,
1039 object_hash,
1040 },
1041 )
1042 },
1043 )
1044 .map(|_| ()),
1045 free::pack::Subcommands::MultiIndex(free::pack::multi_index::Platform { multi_index_path, cmd }) => {
1046 match cmd {
1047 free::pack::multi_index::Subcommands::Entries => prepare_and_run(
1048 "pack-multi-index-entries",
1049 trace,
1050 verbose,
1051 progress,
1052 progress_keep_open,
1053 core::pack::multi_index::PROGRESS_RANGE,
1054 move |_progress, out, _err| core::pack::multi_index::entries(multi_index_path, format, out),
1055 ),
1056 free::pack::multi_index::Subcommands::Info => prepare_and_run(
1057 "pack-multi-index-info",
1058 trace,
1059 verbose,
1060 progress,
1061 progress_keep_open,
1062 core::pack::multi_index::PROGRESS_RANGE,
1063 move |_progress, out, err| {
1064 core::pack::multi_index::info(multi_index_path, format, out, err)
1065 },
1066 ),
1067 free::pack::multi_index::Subcommands::Verify => prepare_and_run(
1068 "pack-multi-index-verify",
1069 trace,
1070 auto_verbose,
1071 progress,
1072 progress_keep_open,
1073 core::pack::multi_index::PROGRESS_RANGE,
1074 move |progress, _out, _err| {
1075 core::pack::multi_index::verify(multi_index_path, progress, &should_interrupt)
1076 },
1077 ),
1078 free::pack::multi_index::Subcommands::Create { index_paths } => prepare_and_run(
1079 "pack-multi-index-create",
1080 trace,
1081 verbose,
1082 progress,
1083 progress_keep_open,
1084 core::pack::multi_index::PROGRESS_RANGE,
1085 move |progress, _out, _err| {
1086 core::pack::multi_index::create(
1087 index_paths,
1088 multi_index_path,
1089 progress,
1090 &should_interrupt,
1091 object_hash,
1092 )
1093 },
1094 ),
1095 }
1096 }
1097 free::pack::Subcommands::Index(subcommands) => match subcommands {
1098 free::pack::index::Subcommands::Create {
1099 iteration_mode,
1100 pack_path,
1101 directory,
1102 } => prepare_and_run(
1103 "pack-index-create",
1104 trace,
1105 verbose,
1106 progress,
1107 progress_keep_open,
1108 core::pack::index::PROGRESS_RANGE,
1109 move |progress, out, _err| {
1110 use gitoxide_core::pack::index::PathOrRead;
1111 let input = if let Some(path) = pack_path {
1112 PathOrRead::Path(path)
1113 } else {
1114 use is_terminal::IsTerminal;
1115 if std::io::stdin().is_terminal() {
1116 anyhow::bail!(
1117 "Refusing to read from standard input as no path is given, but it's a terminal."
1118 )
1119 }
1120 PathOrRead::Read(Box::new(stdin()))
1121 };
1122 core::pack::index::from_pack(
1123 input,
1124 directory,
1125 progress,
1126 core::pack::index::Context {
1127 thread_limit,
1128 iteration_mode,
1129 format,
1130 out,
1131 object_hash,
1132 should_interrupt: &gix::interrupt::IS_INTERRUPTED,
1133 },
1134 )
1135 },
1136 ),
1137 },
1138 },
1139 },
1140 Subcommands::Verify {
1141 args:
1142 free::pack::VerifyOptions {
1143 statistics,
1144 algorithm,
1145 decode,
1146 re_encode,
1147 },
1148 } => prepare_and_run(
1149 "verify",
1150 trace,
1151 auto_verbose,
1152 progress,
1153 progress_keep_open,
1154 core::repository::verify::PROGRESS_RANGE,
1155 move |progress, out, _err| {
1156 core::repository::verify::integrity(
1157 repository(Mode::Strict)?,
1158 out,
1159 progress,
1160 &should_interrupt,
1161 core::repository::verify::Context {
1162 output_statistics: statistics.then_some(format),
1163 algorithm,
1164 verify_mode: verify_mode(decode, re_encode),
1165 thread_limit,
1166 },
1167 )
1168 },
1169 ),
1170 Subcommands::Revision(cmd) => match cmd {
1171 revision::Subcommands::List {
1172 spec,
1173 svg,
1174 limit,
1175 long_hashes,
1176 } => prepare_and_run(
1177 "revision-list",
1178 trace,
1179 auto_verbose,
1180 progress,
1181 progress_keep_open,
1182 core::repository::revision::list::PROGRESS_RANGE,
1183 move |progress, out, _err| {
1184 core::repository::revision::list(
1185 repository(Mode::Lenient)?,
1186 progress,
1187 out,
1188 core::repository::revision::list::Context {
1189 limit,
1190 spec,
1191 format,
1192 long_hashes,
1193 text: svg.map_or(core::repository::revision::list::Format::Text, |path| {
1194 core::repository::revision::list::Format::Svg { path }
1195 }),
1196 },
1197 )
1198 },
1199 ),
1200 revision::Subcommands::PreviousBranches => prepare_and_run(
1201 "revision-previousbranches",
1202 trace,
1203 verbose,
1204 progress,
1205 progress_keep_open,
1206 None,
1207 move |_progress, out, _err| {
1208 core::repository::revision::previous_branches(repository(Mode::Lenient)?, out, format)
1209 },
1210 ),
1211 revision::Subcommands::Explain { spec } => prepare_and_run(
1212 "revision-explain",
1213 trace,
1214 verbose,
1215 progress,
1216 progress_keep_open,
1217 None,
1218 move |_progress, out, _err| core::repository::revision::explain(spec, out),
1219 ),
1220 revision::Subcommands::Resolve {
1221 specs,
1222 explain,
1223 cat_file,
1224 tree_mode,
1225 reference,
1226 blob_format,
1227 } => prepare_and_run(
1228 "revision-parse",
1229 trace,
1230 verbose,
1231 progress,
1232 progress_keep_open,
1233 None,
1234 move |_progress, out, _err| {
1235 core::repository::revision::resolve(
1236 repository(Mode::Strict)?,
1237 specs,
1238 out,
1239 core::repository::revision::resolve::Options {
1240 format,
1241 explain,
1242 cat_file,
1243 show_reference: reference,
1244 tree_mode: match tree_mode {
1245 revision::resolve::TreeMode::Raw => core::repository::revision::resolve::TreeMode::Raw,
1246 revision::resolve::TreeMode::Pretty => {
1247 core::repository::revision::resolve::TreeMode::Pretty
1248 }
1249 },
1250 blob_format: match blob_format {
1251 revision::resolve::BlobFormat::Git => {
1252 core::repository::revision::resolve::BlobFormat::Git
1253 }
1254 revision::resolve::BlobFormat::Worktree => {
1255 core::repository::revision::resolve::BlobFormat::Worktree
1256 }
1257 revision::resolve::BlobFormat::Diff => {
1258 core::repository::revision::resolve::BlobFormat::Diff
1259 }
1260 revision::resolve::BlobFormat::DiffOrGit => {
1261 core::repository::revision::resolve::BlobFormat::DiffOrGit
1262 }
1263 },
1264 },
1265 )
1266 },
1267 ),
1268 },
1269 Subcommands::Cat { revspec } => prepare_and_run(
1270 "cat",
1271 trace,
1272 verbose,
1273 progress,
1274 progress_keep_open,
1275 None,
1276 move |_progress, out, _err| core::repository::cat(repository(Mode::Lenient)?, &revspec, out),
1277 ),
1278 Subcommands::Commit(cmd) => match cmd {
1279 commit::Subcommands::Verify { rev_spec } => prepare_and_run(
1280 "commit-verify",
1281 trace,
1282 auto_verbose,
1283 progress,
1284 progress_keep_open,
1285 None,
1286 move |_progress, _out, _err| {
1287 core::repository::commit::verify(repository(Mode::Lenient)?, rev_spec.as_deref())
1288 },
1289 ),
1290 commit::Subcommands::Sign { rev_spec } => prepare_and_run(
1291 "commit-sign",
1292 trace,
1293 auto_verbose,
1294 progress,
1295 progress_keep_open,
1296 None,
1297 move |_progress, out, _err| {
1298 core::repository::commit::sign(repository(Mode::Lenient)?, rev_spec.as_deref(), out)
1299 },
1300 ),
1301 commit::Subcommands::Describe {
1302 annotated_tags,
1303 all_refs,
1304 first_parent,
1305 always,
1306 long,
1307 statistics,
1308 max_candidates,
1309 rev_spec,
1310 dirty_suffix,
1311 } => prepare_and_run(
1312 "commit-describe",
1313 trace,
1314 verbose,
1315 progress,
1316 progress_keep_open,
1317 None,
1318 move |_progress, out, err| {
1319 core::repository::commit::describe(
1320 repository(Mode::Strict)?,
1321 rev_spec.as_deref(),
1322 out,
1323 err,
1324 core::repository::commit::describe::Options {
1325 all_tags: !annotated_tags,
1326 all_refs,
1327 long_format: long,
1328 first_parent,
1329 statistics,
1330 max_candidates,
1331 always,
1332 dirty_suffix: dirty_suffix.map(|suffix| suffix.unwrap_or_else(|| "dirty".to_string())),
1333 },
1334 )
1335 },
1336 ),
1337 },
1338 Subcommands::Tag(platform) => match platform.cmds {
1339 Some(tag::Subcommands::List) | None => prepare_and_run(
1340 "tag-list",
1341 trace,
1342 auto_verbose,
1343 progress,
1344 progress_keep_open,
1345 None,
1346 move |_progress, out, _err| core::repository::tag::list(repository(Mode::Lenient)?, out, format),
1347 ),
1348 },
1349 Subcommands::Tree(cmd) => match cmd {
1350 tree::Subcommands::Entries {
1351 treeish,
1352 recursive,
1353 extended,
1354 } => prepare_and_run(
1355 "tree-entries",
1356 trace,
1357 verbose,
1358 progress,
1359 progress_keep_open,
1360 None,
1361 move |_progress, out, _err| {
1362 core::repository::tree::entries(
1363 repository(Mode::Strict)?,
1364 treeish.as_deref(),
1365 recursive,
1366 extended,
1367 format,
1368 out,
1369 )
1370 },
1371 ),
1372 tree::Subcommands::Info { treeish, extended } => prepare_and_run(
1373 "tree-info",
1374 trace,
1375 verbose,
1376 progress,
1377 progress_keep_open,
1378 None,
1379 move |_progress, out, err| {
1380 core::repository::tree::info(
1381 repository(Mode::Strict)?,
1382 treeish.as_deref(),
1383 extended,
1384 format,
1385 out,
1386 err,
1387 )
1388 },
1389 ),
1390 },
1391 Subcommands::Odb(cmd) => match cmd {
1392 odb::Subcommands::Stats { extra_header_lookup } => prepare_and_run(
1393 "odb-stats",
1394 trace,
1395 auto_verbose,
1396 progress,
1397 progress_keep_open,
1398 core::repository::odb::statistics::PROGRESS_RANGE,
1399 move |progress, out, err| {
1400 core::repository::odb::statistics(
1401 repository(Mode::Strict)?,
1402 progress,
1403 out,
1404 err,
1405 core::repository::odb::statistics::Options {
1406 format,
1407 thread_limit,
1408 extra_header_lookup,
1409 },
1410 )
1411 },
1412 ),
1413 odb::Subcommands::Entries => prepare_and_run(
1414 "odb-entries",
1415 trace,
1416 verbose,
1417 progress,
1418 progress_keep_open,
1419 None,
1420 move |_progress, out, _err| core::repository::odb::entries(repository(Mode::Strict)?, format, out),
1421 ),
1422 odb::Subcommands::Info => prepare_and_run(
1423 "odb-info",
1424 trace,
1425 verbose,
1426 progress,
1427 progress_keep_open,
1428 None,
1429 move |_progress, out, err| core::repository::odb::info(repository(Mode::Strict)?, format, out, err),
1430 ),
1431 },
1432 Subcommands::Fsck(fsck::Platform { spec }) => prepare_and_run(
1433 "fsck",
1434 trace,
1435 auto_verbose,
1436 progress,
1437 progress_keep_open,
1438 None,
1439 move |_progress, out, _err| core::repository::fsck(repository(Mode::Strict)?, spec, out),
1440 ),
1441 Subcommands::Mailmap(cmd) => match cmd {
1442 mailmap::Subcommands::Entries => prepare_and_run(
1443 "mailmap-entries",
1444 trace,
1445 verbose,
1446 progress,
1447 progress_keep_open,
1448 None,
1449 move |_progress, out, err| {
1450 core::repository::mailmap::entries(repository(Mode::Lenient)?, format, out, err)
1451 },
1452 ),
1453 mailmap::Subcommands::Check { contacts } => prepare_and_run(
1454 "mailmap-check",
1455 trace,
1456 verbose,
1457 progress,
1458 progress_keep_open,
1459 None,
1460 move |_progress, out, err| {
1461 core::repository::mailmap::check(repository(Mode::Lenient)?, format, contacts, out, err)
1462 },
1463 ),
1464 },
1465 Subcommands::Attributes(cmd) => match cmd {
1466 attributes::Subcommands::Query { statistics, pathspec } => prepare_and_run(
1467 "attributes-query",
1468 trace,
1469 verbose,
1470 progress,
1471 progress_keep_open,
1472 None,
1473 move |_progress, out, err| {
1474 let repo = repository(Mode::Strict)?;
1475 let pathspecs = if pathspec.is_empty() {
1476 PathsOrPatterns::Paths(Box::new(
1477 stdin_or_bail()?.byte_lines().filter_map(Result::ok).map(BString::from),
1478 ))
1479 } else {
1480 PathsOrPatterns::Patterns(pathspec)
1481 };
1482 core::repository::attributes::query(
1483 repo,
1484 pathspecs,
1485 out,
1486 err,
1487 core::repository::attributes::query::Options { format, statistics },
1488 )
1489 },
1490 ),
1491 attributes::Subcommands::ValidateBaseline { statistics, no_ignore } => prepare_and_run(
1492 "attributes-validate-baseline",
1493 trace,
1494 auto_verbose,
1495 progress,
1496 progress_keep_open,
1497 None,
1498 move |progress, out, err| {
1499 core::repository::attributes::validate_baseline(
1500 repository(Mode::StrictWithGitInstallConfig)?,
1501 stdin_or_bail()
1502 .ok()
1503 .map(|stdin| stdin.byte_lines().filter_map(Result::ok).map(gix::bstr::BString::from)),
1504 progress,
1505 out,
1506 err,
1507 core::repository::attributes::validate_baseline::Options {
1508 format,
1509 statistics,
1510 ignore: !no_ignore,
1511 },
1512 )
1513 },
1514 ),
1515 },
1516 Subcommands::Exclude(cmd) => match cmd {
1517 exclude::Subcommands::Query {
1518 statistics,
1519 patterns,
1520 pathspec,
1521 show_ignore_patterns,
1522 } => prepare_and_run(
1523 "exclude-query",
1524 trace,
1525 verbose,
1526 progress,
1527 progress_keep_open,
1528 None,
1529 move |_progress, out, err| {
1530 let repo = repository(Mode::Strict)?;
1531 let pathspecs = if pathspec.is_empty() {
1532 PathsOrPatterns::Paths(Box::new(
1533 stdin_or_bail()?.byte_lines().filter_map(Result::ok).map(BString::from),
1534 ))
1535 } else {
1536 PathsOrPatterns::Patterns(pathspec)
1537 };
1538 core::repository::exclude::query(
1539 repo,
1540 pathspecs,
1541 out,
1542 err,
1543 core::repository::exclude::query::Options {
1544 format,
1545 show_ignore_patterns,
1546 overrides: patterns,
1547 statistics,
1548 },
1549 )
1550 },
1551 ),
1552 },
1553 Subcommands::Index(cmd) => match cmd {
1554 index::Subcommands::Entries {
1555 format: entry_format,
1556 no_attributes,
1557 attributes_from_index,
1558 statistics,
1559 recurse_submodules,
1560 pathspec,
1561 } => prepare_and_run(
1562 "index-entries",
1563 trace,
1564 verbose,
1565 progress,
1566 progress_keep_open,
1567 None,
1568 move |_progress, out, err| {
1569 core::repository::index::entries(
1570 repository(Mode::Lenient)?,
1571 pathspec,
1572 out,
1573 err,
1574 core::repository::index::entries::Options {
1575 format,
1576 simple: match entry_format {
1577 index::entries::Format::Simple => true,
1578 index::entries::Format::Rich => false,
1579 },
1580 attributes: if no_attributes {
1581 None
1582 } else {
1583 Some(if attributes_from_index {
1584 core::repository::index::entries::Attributes::Index
1585 } else {
1586 core::repository::index::entries::Attributes::WorktreeAndIndex
1587 })
1588 },
1589 recurse_submodules,
1590 statistics,
1591 },
1592 )
1593 },
1594 ),
1595 index::Subcommands::FromTree {
1596 force,
1597 index_output_path,
1598 skip_hash,
1599 spec,
1600 } => prepare_and_run(
1601 "index-from-tree",
1602 trace,
1603 verbose,
1604 progress,
1605 progress_keep_open,
1606 None,
1607 move |_progress, _out, _err| {
1608 core::repository::index::from_tree(
1609 repository(Mode::Strict)?,
1610 spec,
1611 index_output_path,
1612 force,
1613 skip_hash,
1614 )
1615 },
1616 ),
1617 },
1618 Subcommands::Blame {
1619 statistics,
1620 file,
1621 ranges,
1622 since,
1623 } => prepare_and_run(
1624 "blame",
1625 trace,
1626 verbose,
1627 progress,
1628 progress_keep_open,
1629 None,
1630 move |_progress, out, err| {
1631 let repo = repository(Mode::Lenient)?;
1632 let diff_algorithm = repo.diff_algorithm()?;
1633
1634 core::repository::blame::blame_file(
1635 repo,
1636 &file,
1637 gix::blame::Options {
1638 diff_algorithm,
1639 ranges: gix::blame::BlameRanges::from_one_based_inclusive_ranges(ranges)?,
1640 since,
1641 rewrites: Some(gix::diff::Rewrites::default()),
1642 debug_track_path: false,
1643 },
1644 out,
1645 statistics.then_some(err),
1646 )
1647 },
1648 ),
1649 Subcommands::Completions { shell, out_dir } => {
1650 let mut app = Args::command();
1651
1652 let shell = shell
1653 .or_else(clap_complete::Shell::from_env)
1654 .ok_or_else(|| anyhow!("The shell could not be derived from the environment"))?;
1655
1656 let bin_name = app.get_name().to_owned();
1657 if let Some(out_dir) = out_dir {
1658 clap_complete::generate_to(shell, &mut app, bin_name, &out_dir)?;
1659 } else {
1660 clap_complete::generate(shell, &mut app, bin_name, &mut std::io::stdout());
1661 }
1662 Ok(())
1663 }
1664 }?;
1665 Ok(())
1666}
1667
1668fn stdin_or_bail() -> Result<std::io::BufReader<std::io::Stdin>> {
1669 use is_terminal::IsTerminal;
1670 if std::io::stdin().is_terminal() {
1671 anyhow::bail!("Refusing to read from standard input while a terminal is connected")
1672 }
1673 Ok(BufReader::new(stdin()))
1674}
1675
1676fn verify_mode(decode: bool, re_encode: bool) -> verify::Mode {
1677 match (decode, re_encode) {
1678 (true, false) => verify::Mode::HashCrc32Decode,
1679 (_, true) => verify::Mode::HashCrc32DecodeEncode,
1680 (false, false) => verify::Mode::HashCrc32,
1681 }
1682}
1683
1684#[cfg(test)]
1685mod tests {
1686 use super::*;
1687
1688 #[test]
1689 fn clap() {
1690 use clap::CommandFactory;
1691 Args::command().debug_assert();
1692 }
1693}