gitoxide/plumbing/
main.rs

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                // Enable precious file parsing unless the user made a choice.
124                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        // SAFETY: The closure doesn't use mutexes or memory allocation, so it should be safe to call from a signal handler.
153        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)), // TODO: make this a configurable when in `gix`, this seems to be optimal on MacOS, linux scales though! MacOS also scales if reading a lot of files for refresh index
428                        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}