Skip to main content

git_lfs/
args.rs

1//! Clap CLI surface (struct `Cli` + subcommands).
2//!
3//! Extracted from `main.rs` so xtask (and any future tool) can
4//! reuse the command tree for man-page generation, completion
5//! scripts, etc. Keep this file focused on the clap derive — all
6//! dispatch / business logic stays in main.rs and the per-command
7//! modules.
8//!
9//! Each subcommand is a tuple variant on [`Command`] delegating to
10//! a `*Args` struct. The struct is the home for the rustdoc that
11//! drives clap's `about` / `long_about` (first paragraph → about,
12//! rest → long_about) and for `#[command(...)]` extras such as
13//! `after_help`, aliases, and arg-group headings. Keep the variants
14//! themselves bare — putting a doc comment on the variant would
15//! shadow the struct's docs.
16
17use std::path::PathBuf;
18
19use clap::{Args, Parser, Subcommand};
20
21/// Git LFS is a system for managing and versioning large files
22/// in association with a Git repository. Instead of storing the
23/// large files within the Git repository as blobs, Git LFS stores
24/// special "pointer files" in the repository, while storing the
25/// actual file contents on a Git LFS server. The contents of the
26/// large file are downloaded automatically when needed, for example
27/// when a Git branch containing the large file is checked out.
28///
29/// Git LFS works by using a "smudge" filter to look up the large
30/// file contents based on the pointer file, and a "clean" filter
31/// to create a new version of the pointer file when the large file's
32/// contents change. It also uses a pre-push hook to upload the large
33/// file contents to the Git LFS server whenever a commit containing
34/// a new large file version is about to be pushed to the
35/// corresponding Git server.
36#[derive(Parser)]
37#[command(
38    name = "git-lfs",
39    about = "Git LFS — large file storage for git",
40    // We want `git lfs --version` to print the same banner as
41    // `git lfs version`. clap's auto-derived `--version` would
42    // emit `git-lfs <version>` (one token, no `/` separator),
43    // which doesn't match the user-agent style upstream uses.
44    // Suppress clap's flag and handle --version ourselves.
45    disable_version_flag = true,
46    max_term_width = 100,
47)]
48pub struct Cli {
49    /// Print the version banner and exit.
50    #[arg(long, short = 'V', global = true)]
51    pub version: bool,
52
53    #[command(subcommand)]
54    pub command: Option<Command>,
55}
56
57// note: don't add rustdoc comments here, they will shadow the struct's docs
58// in the clap-generated help output
59#[derive(Subcommand)]
60pub enum Command {
61    Clean(CleanArgs),
62    Smudge(SmudgeArgs),
63    Install(InstallArgs),
64    Uninstall(UninstallArgs),
65    Track(TrackArgs),
66    Untrack(UntrackArgs),
67    FilterProcess(FilterProcessArgs),
68    Fetch(FetchArgs),
69    Pull(PullArgs),
70    Push(PushArgs),
71    Clone(CloneArgs),
72    PostCheckout(PostCheckoutArgs),
73    PostCommit(PostCommitArgs),
74    PostMerge(PostMergeArgs),
75    PrePush(PrePushArgs),
76    Version(VersionArgs),
77    Pointer(PointerArgs),
78    Env(EnvArgs),
79    Ext(ExtArgs),
80    Update(UpdateArgs),
81    Migrate(MigrateArgs),
82    Checkout(CheckoutArgs),
83    Prune(PruneArgs),
84    Fsck(FsckArgs),
85    Status(StatusArgs),
86    Lock(LockArgs),
87    Locks(LocksArgs),
88    Unlock(UnlockArgs),
89    LsFiles(LsFilesArgs),
90}
91
92/// Git clean filter that converts large files to pointers
93///
94/// Read the contents of a large file from standard input, and write a
95/// Git LFS pointer file for that file to standard output.
96///
97/// Clean is typically run by Git’s clean filter, configured by the
98/// repository’s Git attributes.
99///
100/// Clean is not part of the user-facing Git plumbing commands.
101/// To preview the pointer of a large file as it would be generated,
102/// see the git-lfs-pointer(1) command.
103#[derive(Args)]
104pub struct CleanArgs {
105    /// Working-tree path of the file being cleaned.
106    ///
107    /// Substituted for `%f` in any configured `lfs.extension.<name>.clean` command.
108    pub path: Option<PathBuf>,
109}
110
111/// Git smudge filter that converts pointer in blobs to the actual content
112///
113/// Read a Git LFS pointer file from standard input and write the contents of the
114/// corresponding large file to standard output. If needed, download the file’s
115/// contents from the Git LFS endpoint. The argument, if provided, is only used
116/// for a progress bar.
117///
118/// Smudge is typically run by Git’s smudge filter, configured by the repository’s
119/// Git attributes.
120///
121/// In your Git configuration or in a .lfsconfig file, you may set either or both
122/// of `lfs.fetchinclude` and `lfs.fetchexclude` to comma-separated lists of paths.
123/// If `lfs.fetchinclude` is defined, Git LFS pointer files will only be replaced
124/// with the contents of the corresponding Git LFS object file if their path
125/// matches one in that list, and if `lfs.fetchexclude` is defined, Git LFS pointer
126/// files will only be replaced with the contents of the corresponding Git LFS
127/// object file if their path does not match one in that list. Paths are matched
128/// using wildcard matching as per gitignore(5). Git LFS pointer files that are
129/// not replaced with the contents of their corresponding object files are simply
130/// copied to standard output without change.
131///
132/// Without any options, git lfs smudge outputs the raw Git LFS content to standard
133/// output.
134#[derive(Args)]
135pub struct SmudgeArgs {
136    /// Working-tree path of the file being smudged (currently unused).
137    pub path: Option<PathBuf>,
138    /// Skip automatic downloading of objects on clone or pull.
139    ///
140    /// Equivalent to `GIT_LFS_SKIP_SMUDGE=1`. Wired up by `git lfs install --skip-smudge`.
141    #[arg(long)]
142    pub skip: bool,
143}
144
145/// Install Git LFS configuration
146///
147/// Set up the `lfs` smudge and clean filters under the name `lfs` in
148/// the global Git config, and (when run from inside a repository)
149/// install a pre-push hook to run git-lfs-pre-push(1). If
150/// `core.hooksPath` is configured in any Git configuration (supported
151/// on Git v2.9.0 or later), the pre-push hook is installed to that
152/// directory instead.
153///
154/// Without any options, only sets up the `lfs` smudge and clean filters
155/// if they are not already set.
156#[derive(Args)]
157pub struct InstallArgs {
158    // TODO(post-1.0): replace the --local/--system/--worktree/--file mutex
159    // with a clap ArgGroup (multiple = false). Validation lives in
160    // resolve_install_scope (cli/src/main.rs); kept manual because
161    // tests/t-install.sh:329 (and the t-install-worktree / t-uninstall /
162    // t-uninstall-worktree variants) assert upstream's exact wording
163    // ("Only one of the --local, --system, --worktree, and --file
164    // options can be specified."). Worth taking once we're free to
165    // update those assertions.
166    /// Set the `lfs` smudge and clean filters, overwriting existing
167    /// values.
168    #[arg(short, long)]
169    pub force: bool,
170
171    /// Set the `lfs` smudge and clean filters in the local repository's
172    /// git config, instead of the global git config (`~/.gitconfig`).
173    #[arg(short, long)]
174    pub local: bool,
175
176    /// Set the `lfs` smudge and clean filters in the current working
177    /// tree's git config, instead of the global git config
178    /// (`~/.gitconfig`) or local repository's git config
179    /// (`$GIT_DIR/config`).
180    ///
181    /// If multiple working trees are in use, the Git config extension
182    /// `worktreeConfig` must be enabled to use this option. If only one
183    /// working tree is in use, `--worktree` has the same effect as
184    /// `--local`. Available only on Git v2.20.0 or later.
185    #[arg(short, long)]
186    pub worktree: bool,
187
188    /// Set the `lfs` smudge and clean filters in the system git config,
189    /// e.g. `/etc/gitconfig` instead of the global git config
190    /// (`~/.gitconfig`).
191    #[arg(long)]
192    pub system: bool,
193
194    /// Set the `lfs` smudge and clean filters in the Git configuration
195    /// file specified by `<PATH>`.
196    #[arg(long, value_name = "PATH")]
197    pub file: Option<PathBuf>,
198
199    /// Skip automatic downloading of objects on clone or pull.
200    ///
201    /// Requires a manual `git lfs pull` every time a new commit is
202    /// checked out on the repository.
203    #[arg(short, long)]
204    pub skip_smudge: bool,
205
206    /// Skip installation of hooks into the local repository.
207    ///
208    /// Use if you want to install the LFS filters but not make changes
209    /// to the hooks. Valid alongside `--local`, `--worktree`, `--system`,
210    /// or `--file`.
211    #[arg(long)]
212    pub skip_repo: bool,
213}
214
215/// Remove Git LFS configuration
216///
217/// Remove the `lfs` clean and smudge filters from the global Git config,
218/// and (when run from inside a Git repository) uninstall the Git LFS
219/// pre-push hook. Hooks that don't match what we would write are left
220/// untouched.
221#[derive(Args)]
222pub struct UninstallArgs {
223    // TODO(post-1.0): same --local/--system/--worktree/--file mutex as
224    // InstallArgs — share a clap ArgGroup. See InstallArgs's TODO for
225    // the rationale and test references.
226    /// Optional mode. With `hooks`, removes only the LFS git hooks and
227    /// leaves the filter config alone (the inverse of `--skip-repo`).
228    pub mode: Option<String>,
229
230    /// Remove the `lfs` smudge and clean filters from the local
231    /// repository's git config, instead of the global git config
232    /// (`~/.gitconfig`).
233    #[arg(short, long)]
234    pub local: bool,
235
236    /// Remove the `lfs` smudge and clean filters from the current
237    /// working tree's git config, instead of the global git config
238    /// (`~/.gitconfig`) or local repository's git config
239    /// (`$GIT_DIR/config`).
240    ///
241    /// If multiple working trees are in use, the Git config extension
242    /// `worktreeConfig` must be enabled to use this option. If only one
243    /// working tree is in use, `--worktree` has the same effect as
244    /// `--local`. Available only on Git v2.20.0 or later.
245    #[arg(short, long)]
246    pub worktree: bool,
247
248    /// Remove the `lfs` smudge and clean filters from the system git
249    /// config, instead of the global git config (`~/.gitconfig`).
250    #[arg(long)]
251    pub system: bool,
252
253    /// Remove the `lfs` smudge and clean filters from the Git
254    /// configuration file specified by `<PATH>`.
255    #[arg(long, value_name = "PATH")]
256    pub file: Option<PathBuf>,
257
258    /// Skip cleanup of the local repo.
259    ///
260    /// Use if you want to uninstall the global LFS filters but not
261    /// make changes to the current repo.
262    #[arg(long)]
263    pub skip_repo: bool,
264}
265
266/// View or add Git LFS paths to Git attributes
267///
268/// Start tracking the given pattern(s) through Git LFS. The argument is
269/// written to `.gitattributes`. If no paths are provided, list the
270/// currently-tracked paths.
271///
272/// Per gitattributes(5), patterns use the gitignore(5) pattern rules to
273/// match paths. This means that patterns containing asterisk (`*`),
274/// question mark (`?`), and the bracket characters (`[` and `]`) are
275/// treated specially; to disable this behavior and treat them literally
276/// instead, use `--filename` or escape the character with a backslash.
277#[derive(Args)]
278pub struct TrackArgs {
279    /// File patterns to track (e.g. `*.jpg`, `data/*.bin`).
280    pub patterns: Vec<String>,
281
282    /// Log files which `git lfs track` will touch. Disabled by default.
283    #[arg(short, long)]
284    pub verbose: bool,
285
286    /// Log all actions that would normally take place (adding entries
287    /// to `.gitattributes`, touching files on disk, etc.) without
288    /// performing any mutative operations.
289    ///
290    /// Implicitly mocks the behavior of `--verbose`, logging in greater
291    /// detail what it is doing. Disabled by default.
292    #[arg(short, long)]
293    pub dry_run: bool,
294
295    /// Write the currently tracked patterns as JSON to standard output.
296    ///
297    /// Intended for interoperation with external tools. Cannot be
298    /// combined with any pattern arguments. If `--no-excluded` is also
299    /// provided, that option will have no effect.
300    #[arg(short, long)]
301    pub json: bool,
302
303    /// Treat the arguments as literal filenames, not as patterns.
304    ///
305    /// Any special glob characters in the filename will be escaped
306    /// when writing the `.gitattributes` file.
307    #[arg(long)]
308    pub filename: bool,
309
310    /// Make the paths "lockable" — they should be locked to edit them,
311    /// and will be made read-only in the working copy when not locked.
312    #[arg(short, long)]
313    pub lockable: bool,
314
315    /// Remove the lockable flag from the paths so they are no longer
316    /// read-only unless locked.
317    #[arg(long)]
318    pub not_lockable: bool,
319
320    /// Don't list patterns that are excluded in the output; only list
321    /// patterns that are tracked.
322    #[arg(long)]
323    pub no_excluded: bool,
324
325    /// Make matched entries stat-dirty so that Git can re-index files
326    /// you wish to convert to LFS.
327    ///
328    /// Does not modify any `.gitattributes` file.
329    #[arg(long)]
330    pub no_modify_attrs: bool,
331}
332
333/// Remove Git LFS paths from Git attributes
334///
335/// Stop tracking the given path(s) through Git LFS. The argument can
336/// be a glob pattern or a file path. The matching pointer files in
337/// history (and the objects in the local store) are left in place.
338#[derive(Args)]
339pub struct UntrackArgs {
340    /// Paths or glob patterns to stop tracking.
341    pub patterns: Vec<String>,
342}
343
344/// Git filter process that converts between pointer and actual content
345///
346/// Implement the Git process filter API, exchanging handshake messages
347/// and then accepting and responding to requests to either clean or
348/// smudge a file.
349///
350/// `filter-process` is always run by Git's filter process, and is
351/// configured by the repository's Git attributes.
352///
353/// In your Git configuration or in a `.lfsconfig` file, you may set
354/// either or both of `lfs.fetchinclude` and `lfs.fetchexclude` to
355/// comma-separated lists of paths. If `lfs.fetchinclude` is defined,
356/// Git LFS pointer files will only be replaced with the contents of
357/// the corresponding object file if their path matches one in that
358/// list, and if `lfs.fetchexclude` is defined, pointer files will
359/// only be replaced if their path does not match one in that list.
360/// Paths are matched using wildcard matching as per gitignore(5).
361/// Pointer files that are not replaced are simply copied to standard
362/// output without change.
363///
364/// The filter process uses Git's pkt-line protocol to communicate, and
365/// is documented in detail in gitattributes(5).
366#[derive(Args)]
367pub struct FilterProcessArgs {
368    /// Skip automatic downloading of objects on clone or pull.
369    ///
370    /// Equivalent to `GIT_LFS_SKIP_SMUDGE=1`. Wired up by
371    /// `git lfs install --skip-smudge`.
372    #[arg(short, long)]
373    pub skip: bool,
374}
375
376/// Download all Git LFS files for a given ref
377///
378/// Download Git LFS objects at the given refs from the specified remote.
379/// See DEFAULT REMOTE and DEFAULT REFS for what happens if you don't
380/// specify.
381///
382/// This does not update the working copy; use git-lfs-pull(1) to
383/// download and replace pointer text with object content, or
384/// git-lfs-checkout(1) to materialize already-downloaded objects.
385#[derive(Args)]
386pub struct FetchArgs {
387    /// Optional remote name followed by refs. The first positional
388    /// argument is treated as a remote name when it resolves; any
389    /// following arguments are refs to fetch.
390    pub args: Vec<String>,
391
392    /// Specify `lfs.fetchinclude` just for this invocation; see
393    /// INCLUDE AND EXCLUDE.
394    #[arg(short = 'I', long, help_heading = FILTER)]
395    pub include: Vec<String>,
396
397    /// Specify `lfs.fetchexclude` just for this invocation; see
398    /// INCLUDE AND EXCLUDE.
399    #[arg(short = 'X', long, help_heading = FILTER)]
400    pub exclude: Vec<String>,
401
402    /// Download all objects that are referenced by any commit
403    /// reachable from the refs provided as arguments.
404    ///
405    /// If no refs are provided, then all refs are fetched. This is
406    /// primarily for backup and migration purposes. Cannot be
407    /// combined with `--include`/`--exclude`. Ignores any globally
408    /// configured include and exclude paths to ensure that all
409    /// objects are downloaded.
410    #[arg(short, long)]
411    pub all: bool,
412
413    /// Read a list of newline-delimited refs from standard input
414    /// instead of the command line.
415    #[arg(long)]
416    pub stdin: bool,
417
418    /// Prune old and unreferenced objects after fetching, equivalent
419    /// to running `git lfs prune` afterwards. See git-lfs-prune(1)
420    /// for more details.
421    #[arg(short, long)]
422    pub prune: bool,
423
424    /// Also fetch objects that are already present locally.
425    ///
426    /// Useful for recovery from a corrupt local store.
427    #[arg(long)]
428    pub refetch: bool,
429
430    /// Print what would be fetched, without actually fetching anything.
431    #[arg(short, long)]
432    pub dry_run: bool,
433
434    /// Also fetch recently-touched refs and the recent pre-images on
435    /// each.
436    ///
437    /// Walk every ref under `refs/heads/` (and, by default, every
438    /// remote-tracking ref) whose tip commit lies within
439    /// `lfs.fetchrecentrefsdays` of today, and on each of those refs
440    /// download the pre-image of every LFS file modified within
441    /// `lfs.fetchrecentcommitsdays`. Combine with the named refs'
442    /// HEAD-state fetch. The same behaviour fires automatically if
443    /// `lfs.fetchrecentalways` is set.
444    #[arg(short, long)]
445    pub recent: bool,
446
447    /// Write the details of all object transfer requests as JSON to
448    /// standard output.
449    ///
450    /// Intended for interoperation with external tools. When
451    /// `--dry-run` is also specified, writes the details of the
452    /// transfers that would occur if the objects were fetched.
453    #[arg(short, long)]
454    pub json: bool,
455}
456
457const FILTER: &str = "Filter options";
458
459/// Download all Git LFS files for current ref and checkout
460///
461/// Download Git LFS objects for the currently checked out ref, and
462/// update the working copy with the downloaded content if required.
463///
464/// This is generally equivalent to running `git lfs fetch [options]
465/// [<remote>]` followed by `git lfs checkout`. See git-lfs-checkout(1)
466/// for partial-clone, sparse-checkout, and bare-repository behavior
467/// (governed by the installed Git version and `GIT_ATTR_SOURCE`).
468///
469/// Requires `git lfs install` to have wired up the smudge filter. If
470/// the filter is missing, the fetch step still runs but the
471/// working-tree update is skipped with a hint to install.
472#[derive(Args)]
473pub struct PullArgs {
474    /// Optional remote name followed by refs.
475    ///
476    /// The first positional argument is treated as a remote name when
477    /// it resolves; any following arguments are refs to fetch. With
478    /// no arguments, the default remote is used.
479    pub args: Vec<String>,
480
481    /// Specify `lfs.fetchinclude` just for this invocation.
482    #[arg(short = 'I', long, help_heading = FILTER)]
483    pub include: Vec<String>,
484
485    /// Specify `lfs.fetchexclude` just for this invocation.
486    #[arg(short = 'X', long, help_heading = FILTER)]
487    pub exclude: Vec<String>,
488}
489
490/// Push queued large files to the Git LFS endpoint
491///
492/// Upload Git LFS files to the configured endpoint for the current Git
493/// remote. By default, filters out objects that are already referenced
494/// by the local clone of the remote (approximated via
495/// `refs/remotes/<remote>/*`); the server's batch API dedupes again,
496/// so a missing local tracking ref doesn't waste bandwidth.
497#[derive(Args)]
498pub struct PushArgs {
499    /// Remote to push to (e.g. `origin`). The remote's tracking refs
500    /// are excluded from the upload set so already-pushed objects
501    /// aren't sent again.
502    pub remote: String,
503
504    /// Refs (or, with `--object-id`, raw OIDs) to push. With `--all`,
505    /// restricts the all-refs walk to these; with `--stdin`, ignored
506    /// (a warning is emitted).
507    pub args: Vec<String>,
508
509    /// Print the files that would be pushed, without actually pushing
510    /// them.
511    #[arg(short, long)]
512    pub dry_run: bool,
513
514    /// Push all objects reachable from the refs given as arguments.
515    ///
516    /// If no refs are provided, all local refs are pushed. Note this
517    /// behavior differs from `git lfs fetch --all`, which fetches
518    /// every ref including refs outside `refs/heads` / `refs/tags`. If
519    /// you're migrating a repository, run `git lfs push` for any
520    /// additional remote refs that contain LFS objects not reachable
521    /// from your local refs.
522    #[arg(short, long)]
523    pub all: bool,
524
525    /// Push only the object OIDs listed on the command line (or read
526    /// from stdin with `--stdin`), separated by spaces.
527    #[arg(short, long)]
528    pub object_id: bool,
529
530    /// Read newline-delimited refs (or object IDs when using
531    /// `--object-id`) from standard input instead of the command
532    /// line.
533    #[arg(long)]
534    pub stdin: bool,
535}
536
537/// Efficiently clone a LFS-enabled repository
538///
539/// Clone an LFS-enabled Git repository by disabling LFS during the
540/// `git clone`, then running `git lfs pull` directly afterwards.
541/// Also installs the repo-level hooks (`.git/hooks`) that LFS requires
542/// to operate; if `--separate-git-dir` is given to `git clone`, the
543/// hooks are installed there.
544///
545/// Historically faster than a regular `git clone` because that would
546/// download LFS content via the smudge filter one file at a time.
547/// Modern `git clone` parallelizes the smudge filter, so this command
548/// no longer offers a meaningful speedup over plain `git clone`. You
549/// should prefer plain `git clone`.
550///
551/// In addition to the options accepted by `git clone`, the LFS-only
552/// flags `--include` / `-I <paths>`, `--exclude` / `-X <paths>`, and
553/// `--skip-repo` (skip installing the repo-level hooks) are accepted
554/// — see git-lfs-fetch(1) for the include/exclude semantics. They're
555/// parsed from the trailing argument list rather than declared as
556/// clap flags, so they don't appear in this command's `--help`.
557#[derive(Args)]
558pub struct CloneArgs {
559    /// `git clone` arguments plus the LFS pass-through flags
560    /// (`-I`/`--include`, `-X`/`--exclude`, `--skip-repo`). The
561    /// repository URL is required; an optional target directory
562    /// follows.
563    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
564    pub args: Vec<String>,
565}
566
567/// Git post-checkout hook implementation
568///
569/// Respond to Git post-checkout events. Git invokes this hook with
570/// `<rev-before> <ref-after> <is-branch-checkout>`. We make sure that
571/// any files which are marked as lockable by `git lfs track` are
572/// read-only in the working copy, if not currently locked by the
573/// local user.
574#[derive(Args)]
575pub struct PostCheckoutArgs {
576    /// Positional arguments passed by git. Not normally invoked by
577    /// hand.
578    pub args: Vec<String>,
579}
580
581/// Git post-commit hook implementation
582///
583/// Respond to Git post-commit events. Like `git lfs post-merge`, we
584/// make sure that any files which are marked as lockable by
585/// `git lfs track` are read-only in the working copy, if not
586/// currently locked by the local user.
587///
588/// Upstream optimizes by only checking files changed in HEAD; we
589/// currently scan the full work tree on every commit. The result is
590/// the same, but slower on large repositories.
591#[derive(Args)]
592pub struct PostCommitArgs {
593    /// Positional arguments passed by git. Not normally invoked by
594    /// hand.
595    pub args: Vec<String>,
596}
597
598/// Git post-merge hook implementation
599///
600/// Respond to Git post-merge events. Git invokes this hook with
601/// `<is-squash>`. We make sure that any files which are marked as
602/// lockable by `git lfs track` are read-only in the working copy, if
603/// not currently locked by the local user.
604#[derive(Args)]
605pub struct PostMergeArgs {
606    /// Positional arguments passed by git. Not normally invoked by
607    /// hand.
608    pub args: Vec<String>,
609}
610
611/// Git pre-push hook implementation
612///
613/// Respond to Git pre-push events. Reads the range of commits from
614/// stdin in the form `<local-ref> <local-sha1> <remote-ref>
615/// <remote-sha1>`, takes the remote name and URL as arguments, and
616/// uploads any Git LFS objects associated with those commits to the
617/// Git LFS API.
618///
619/// When pushing a new branch, the list of Git objects considered is
620/// every object reachable from the new branch. When deleting a
621/// branch, no LFS objects are pushed.
622#[derive(Args)]
623pub struct PrePushArgs {
624    /// Name of the remote being pushed to.
625    pub remote: String,
626
627    /// URL of the remote (informational; we use the `lfs.url`
628    /// config).
629    pub url: Option<String>,
630
631    /// Print the files that would be pushed, without actually
632    /// pushing them.
633    #[arg(short, long)]
634    pub dry_run: bool,
635}
636
637/// Print the git-lfs version banner and exit
638#[derive(Args)]
639pub struct VersionArgs;
640
641/// Build, compare, and check pointers
642///
643/// Build and optionally compare generated pointer files to ensure
644/// consistency between different Git LFS implementations.
645#[derive(Args)]
646pub struct PointerArgs {
647    // TODO(post-1.0): replace the --strict/--no-strict, --check/--pointer,
648    // and --check/--file/--stdin manual checks (cli/src/pointer_cmd.rs:108,
649    // 218, 223, 230, 241) with clap arg_group/conflicts_with/requires.
650    // No shell test asserts this wording, so the constraint here is
651    // softer than for the other commands — the deferral is purely about
652    // upstream parity. Worth taking whenever.
653    /// A local file to build the pointer from.
654    #[arg(short, long)]
655    pub file: Option<PathBuf>,
656
657    /// A local file containing a pointer generated from another
658    /// implementation.
659    ///
660    /// Compared to the pointer generated from `--file`.
661    #[arg(short, long)]
662    pub pointer: Option<PathBuf>,
663
664    /// Read the pointer from standard input to compare with the
665    /// pointer generated from `--file`.
666    #[arg(long)]
667    pub stdin: bool,
668
669    /// Read the pointer from standard input (with `--stdin`) or the
670    /// filepath (with `--file`).
671    ///
672    /// If neither or both of `--stdin` and `--file` are given, the
673    /// invocation is invalid. Exits 0 if the data read is a valid Git
674    /// LFS pointer, 1 otherwise. With `--strict`, exits 2 if the
675    /// pointer is not byte-canonical.
676    #[arg(long)]
677    pub check: bool,
678
679    /// With `--check`, verify that the pointer is canonical (the one
680    /// Git LFS would create).
681    ///
682    /// If it isn't, exits 2. The default — for backwards compatibility
683    /// — is `--no-strict`.
684    #[arg(long)]
685    pub strict: bool,
686
687    /// Disable strict mode (paired with `--strict`).
688    #[arg(long)]
689    pub no_strict: bool,
690
691    /// Build a plain pointer without running configured `lfs.extension.*`
692    /// clean commands. Default behavior is to chain through any
693    /// extensions (and emit a `warning:` line on stderr); pass this to
694    /// suppress both the chain and the warning.
695    #[arg(long)]
696    pub no_extensions: bool,
697}
698
699/// Display the Git LFS environment
700///
701/// Display the current Git LFS environment: version, endpoints,
702/// on-disk paths, and the three `filter.lfs.*` config values.
703#[derive(Args)]
704pub struct EnvArgs;
705
706/// List the configured LFS pointer extensions
707///
708/// Print each `lfs.extension.<name>.*` entry resolved to its final
709/// configuration in priority order. Extensions chain external
710/// clean / smudge programs around each LFS object — see
711/// git-lfs-config(5) for how to configure them.
712///
713/// With no arguments, prints every configured extension. With
714/// `list <name>...`, prints only the named extensions (one block
715/// per name, in argument order).
716#[derive(Args)]
717pub struct ExtArgs {
718    #[command(subcommand)]
719    pub cmd: Option<ExtCmd>,
720}
721
722#[derive(Subcommand)]
723pub enum ExtCmd {
724    List(ExtListArgs),
725}
726
727/// List configured LFS pointer extensions, optionally filtered by name.
728#[derive(Args)]
729pub struct ExtListArgs {
730    /// Extension names to print. With no names, prints all configured
731    /// extensions (same as bare `git lfs ext`).
732    pub names: Vec<String>,
733}
734
735/// Update Git hooks
736///
737/// Update the Git hooks used by Git LFS. Silently upgrades known hook
738/// contents. If you have your own custom hooks you may need to use
739/// one of the extended options below.
740#[derive(Args)]
741pub struct UpdateArgs {
742    /// Forcibly overwrite any existing hooks with git-lfs hooks.
743    ///
744    /// Use this option if `git lfs update` fails because of existing
745    /// hooks but you don't care about their current contents.
746    #[arg(short, long)]
747    pub force: bool,
748
749    /// Print instructions for manually updating your hooks to
750    /// include git-lfs functionality.
751    ///
752    /// Use this option if `git lfs update` fails because of existing
753    /// hooks and you want to retain their functionality.
754    #[arg(short, long)]
755    pub manual: bool,
756}
757
758/// Migrate history to or from Git LFS
759///
760/// Convert files in a Git repository to or from Git LFS pointers, or
761/// summarize Git file sizes by file type. The `import` mode converts
762/// Git files (i.e. blobs) to Git LFS, the `export` mode does the
763/// reverse, and the `info` mode provides an informational summary
764/// useful for deciding which files to import or export.
765///
766/// In all modes, by default `git lfs migrate` operates only on the
767/// currently checked-out branch, and only on files added in commits
768/// which do not exist on any remote. Multiple options are available
769/// to override these defaults — see INCLUDE AND EXCLUDE REFERENCES.
770///
771/// When converting files to or from Git LFS, this command only
772/// changes your local repository and working copy, never any remotes.
773/// `import` and `export` are generally DESTRUCTIVE — they rewrite Git
774/// history, changing commits and generating new commit SHAs. (The
775/// exception is the `--no-rewrite` `import` sub-mode.) Always commit
776/// or stash any uncommitted work first, validate the result before
777/// pushing, and force-push the new history once you're satisfied.
778///
779/// For `info` and `import`, all file types are considered by default.
780/// In `import` you'll usually want filename patterns or `--fixup`;
781/// `export` requires at least one `--include` pattern. See INCLUDE
782/// AND EXCLUDE.
783///
784/// `git lfs migrate` will examine, create, and modify `.gitattributes`
785/// files as necessary. They are always assigned the default
786/// read/write permissions mode; symbolic links with that name halt
787/// the migration.
788#[derive(Args)]
789pub struct MigrateArgs {
790    #[command(subcommand)]
791    pub cmd: MigrateCmd,
792}
793
794#[derive(Subcommand)]
795pub enum MigrateCmd {
796    Import(MigrateImportArgs),
797    Export(MigrateExportArgs),
798    Info(MigrateInfoArgs),
799}
800
801/// Convert Git objects to Git LFS pointers
802///
803/// Migrate objects present in the Git history to pointer files
804/// tracked and stored with Git LFS. Adds entries for the converted
805/// file types to `.gitattributes`, creating those files if they
806/// don't exist — as if `git lfs track` had been run at the points
807/// in history where each type first appears.
808///
809/// With `--fixup`, examine existing `.gitattributes` files and
810/// convert only Git objects that should be tracked by Git LFS
811/// according to those rules but aren't yet.
812///
813/// With `--no-rewrite`, migrate objects to pointers in a single new
814/// commit on top of HEAD without rewriting history. The base
815/// `migrate` options (`--include-ref`, `--everything`, etc.) are
816/// ignored in this sub-mode, and the positional argument list
817/// changes from branches to a list of files. Files must be tracked
818/// by patterns already in `.gitattributes`.
819#[derive(Args)]
820pub struct MigrateImportArgs {
821    // TODO(post-1.0): replace the manual --no-rewrite/--fixup/--above/
822    // --include/--exclude/--everything cross-flag validation
823    // (cli/src/migrate/import.rs:53-77, plus the shared
824    // --everything/positional check in migrate/mod.rs::resolve_refs)
825    // with clap arg_group/conflicts_with. Currently kept as-is because
826    // tests/t-migrate-fixup.sh:94,112,130 and t-migrate-import.sh:814,
827    // 825,836 assert upstream's exact wording (e.g. "--no-rewrite and
828    // --fixup cannot be combined", "Cannot use --everything with
829    // --include-ref or --exclude-ref"). Worth taking once we're free
830    // to update those assertions.
831    /// Branches to rewrite (default: the currently checked-out
832    /// branch). With `--no-rewrite`, instead a list of working-tree
833    /// files to convert. References prefixed with `^` are excluded.
834    pub args: Vec<String>,
835
836    /// Convert paths matching this glob (repeatable, comma-delimited).
837    /// Required unless `--above` is set or `--no-rewrite` is given.
838    #[arg(short = 'I', long = "include")]
839    pub include: Vec<String>,
840
841    /// Exclude paths matching this glob (repeatable, comma-delimited).
842    #[arg(short = 'X', long = "exclude")]
843    pub exclude: Vec<String>,
844
845    /// Restrict the rewrite to commits reachable from these refs.
846    /// Repeatable.
847    #[arg(long = "include-ref")]
848    pub include_ref: Vec<String>,
849
850    /// Exclude commits reachable from these refs. Repeatable.
851    #[arg(long = "exclude-ref")]
852    pub exclude_ref: Vec<String>,
853
854    /// Consider all commits reachable from any local or remote ref.
855    ///
856    /// Only local refs are updated even with `--everything`; remote
857    /// refs stay synchronized with their remote.
858    #[arg(long)]
859    pub everything: bool,
860
861    /// Only migrate files whose individual filesize is above the
862    /// given size (e.g. `1b`, `20 MB`, `3 TiB`).
863    ///
864    /// Cannot be used with `--include`, `--exclude`, or `--fixup`.
865    #[arg(long, default_value = "")]
866    pub above: String,
867
868    /// Migrate objects in a new commit on top of HEAD without
869    /// rewriting Git history.
870    ///
871    /// Switches to a different argument list (positional args become
872    /// files, not branches) and ignores the core `migrate` options
873    /// (`--include-ref`, `--everything`, etc.).
874    #[arg(long)]
875    pub no_rewrite: bool,
876
877    /// Commit message for the `--no-rewrite` commit.
878    ///
879    /// If omitted, a message is generated from the file arguments.
880    #[arg(short, long)]
881    pub message: Option<String>,
882
883    /// Infer `--include` and `--exclude` filters per-commit from the
884    /// repository's `.gitattributes` files.
885    ///
886    /// Imports filepaths that should be tracked by Git LFS but
887    /// aren't yet pointers. Incompatible with explicitly given
888    /// `--include` / `--exclude` filters.
889    #[arg(long)]
890    pub fixup: bool,
891
892    /// Write a CSV of `<OLD-SHA>,<NEW-SHA>` for every rewritten
893    /// commit to the named file.
894    #[arg(long = "object-map")]
895    pub object_map: Option<PathBuf>,
896
897    /// Print the commit OID and filename of migrated files to
898    /// standard output.
899    #[arg(long)]
900    pub verbose: bool,
901
902    /// Remote to consult when fetching missing LFS objects (default
903    /// `origin`).
904    #[arg(long)]
905    pub remote: Option<String>,
906
907    /// Don't refresh the known set of remote references before
908    /// determining the set of "un-pushed" commits to migrate.
909    ///
910    /// Has no effect when combined with `--include-ref` or
911    /// `--exclude-ref`.
912    #[arg(long)]
913    pub skip_fetch: bool,
914
915    /// Assume a yes answer to any prompts, permitting noninteractive
916    /// use.
917    ///
918    /// Currently we don't prompt for any reason, so this is accepted
919    /// as a no-op for upstream parity.
920    #[arg(long)]
921    pub yes: bool,
922}
923
924/// Convert Git LFS pointers to Git objects
925///
926/// Migrate Git LFS pointer files present in the Git history out of
927/// Git LFS, converting them back into their corresponding object
928/// files. Files matching the `--include` patterns are removed from
929/// Git LFS; files matching `--exclude` retain their LFS status.
930/// Modifies `.gitattributes` to set/unset the relevant filepath
931/// patterns.
932///
933/// At least one `--include` pattern is required. Objects not present
934/// in the local LFS store are downloaded from the `--remote`
935/// (defaults to `origin`). Pointers whose objects can't be fetched
936/// are left as-is.
937#[derive(Args)]
938pub struct MigrateExportArgs {
939    // TODO(post-1.0): make --include a required clap arg (it is required
940    // in practice — cli/src/migrate/export.rs:53). Currently kept as a
941    // runtime check because tests/t-migrate-export.sh:208 asserts
942    // upstream's exact wording ("One or more files must be specified
943    // with --include"); clap's "the following required arguments were
944    // not provided: --include <INCLUDE>" would be a strict UX win but
945    // a behavioral diff. Also see the shared --everything/positional
946    // check in migrate/mod.rs::resolve_refs.
947    /// Branches to rewrite (default: the currently checked-out
948    /// branch). References prefixed with `^` are excluded.
949    pub branches: Vec<String>,
950
951    /// Convert pointers at paths matching this glob (repeatable,
952    /// comma-delimited). Required — at least one must be given.
953    #[arg(short = 'I', long = "include")]
954    pub include: Vec<String>,
955
956    /// Don't convert pointers at paths matching this glob
957    /// (repeatable, comma-delimited).
958    #[arg(short = 'X', long = "exclude")]
959    pub exclude: Vec<String>,
960
961    /// Restrict the rewrite to commits reachable from these refs.
962    /// Repeatable.
963    #[arg(long = "include-ref")]
964    pub include_ref: Vec<String>,
965
966    /// Exclude commits reachable from these refs. Repeatable.
967    #[arg(long = "exclude-ref")]
968    pub exclude_ref: Vec<String>,
969
970    /// Consider all commits reachable from any local or remote ref.
971    ///
972    /// Only local refs are updated even with `--everything`; remote
973    /// refs stay synchronized with their remote.
974    #[arg(long)]
975    pub everything: bool,
976
977    /// Write a CSV of `<OLD-SHA>,<NEW-SHA>` for every rewritten
978    /// commit to the named file.
979    ///
980    /// Useful as input to `git filter-repo` or other downstream
981    /// tools.
982    #[arg(long = "object-map")]
983    pub object_map: Option<PathBuf>,
984
985    /// Print the commit OID and filename of migrated files to
986    /// standard output.
987    #[arg(long)]
988    pub verbose: bool,
989
990    /// Download LFS objects from this remote during the export.
991    /// Defaults to `origin`.
992    #[arg(long)]
993    pub remote: Option<String>,
994
995    /// Don't refresh the known set of remote references before the
996    /// rewrite.
997    #[arg(long)]
998    pub skip_fetch: bool,
999
1000    /// Assume a yes answer to any prompts, permitting noninteractive
1001    /// use.
1002    ///
1003    /// Currently we don't prompt for any reason, so this is accepted
1004    /// as a no-op for upstream parity.
1005    #[arg(long)]
1006    pub yes: bool,
1007}
1008
1009/// Show information about repository size
1010///
1011/// Summarize the sizes of file objects present in the Git history,
1012/// grouped by filename extension. Read-only — no objects or history
1013/// change.
1014///
1015/// Existing Git LFS pointers are followed by default (the size of
1016/// the referenced objects is totaled in a separate "LFS Objects"
1017/// line). Use `--pointers=ignore` to skip pointers entirely, or
1018/// `--pointers=no-follow` to count the pointer-text size as if the
1019/// pointers were regular files (the older Git LFS behavior).
1020#[derive(Args)]
1021pub struct MigrateInfoArgs {
1022    // TODO(post-1.0): replace the manual --everything/--include-ref/
1023    // --exclude-ref/--fixup/--pointers/--include/--exclude cross-flag
1024    // validation (cli/src/migrate/info.rs:59-83, plus the shared
1025    // --everything/positional check in migrate/mod.rs::resolve_refs)
1026    // with clap arg_group/conflicts_with. Currently kept as-is because
1027    // tests/t-migrate-info.sh:903,922,941,977,995,1013,1031 assert
1028    // upstream's exact wording (e.g. "Cannot use --fixup with
1029    // --pointers=follow"). The value-conditional --pointers checks
1030    // ("=follow" / "=no-follow") may not all collapse cleanly to
1031    // declarative clap rules.
1032    /// Branches to scan (default: the currently checked-out branch).
1033    /// References prefixed with `^` are excluded.
1034    pub branches: Vec<String>,
1035
1036    /// Only include paths matching this glob (repeatable,
1037    /// comma-delimited).
1038    #[arg(short = 'I', long = "include")]
1039    pub include: Vec<String>,
1040
1041    /// Exclude paths matching this glob (repeatable, comma-delimited).
1042    #[arg(short = 'X', long = "exclude")]
1043    pub exclude: Vec<String>,
1044
1045    /// Restrict the scan to commits reachable from these refs.
1046    /// Repeatable.
1047    #[arg(long = "include-ref")]
1048    pub include_ref: Vec<String>,
1049
1050    /// Exclude commits reachable from these refs. Repeatable.
1051    #[arg(long = "exclude-ref")]
1052    pub exclude_ref: Vec<String>,
1053
1054    /// Consider all commits reachable from any local or remote ref.
1055    #[arg(long)]
1056    pub everything: bool,
1057
1058    /// Only count files whose individual filesize is above the given
1059    /// size (e.g. `1b`, `20 MB`, `3 TiB`).
1060    ///
1061    /// File-extension groups whose largest file is below `--above`
1062    /// don't appear in the output.
1063    #[arg(long, default_value = "")]
1064    pub above: String,
1065
1066    /// Display the top N entries, ordered by total file count.
1067    ///
1068    /// Default 5. When existing Git LFS objects are found, an extra
1069    /// "LFS Objects" line is output in addition to the top N
1070    /// entries (unless `--pointers` changes this).
1071    #[arg(long, default_value_t = 5)]
1072    pub top: usize,
1073
1074    /// How to handle existing LFS pointer blobs.
1075    ///
1076    /// `follow` (default): summarize referenced objects in a separate
1077    /// "LFS Objects" line. `ignore`: skip pointers entirely.
1078    /// `no-follow`: count pointer-text size as if pointers were
1079    /// regular files (the older Git LFS behavior). When `--fixup` is
1080    /// given, defaults to `ignore`.
1081    #[arg(long)]
1082    pub pointers: Option<String>,
1083
1084    /// Format byte quantities in this unit.
1085    ///
1086    /// Valid units: `b, kib, mib, gib, tib, pib` (IEC) or
1087    /// `b, kb, mb, gb, tb, pb` (SI). Auto-scaled when omitted.
1088    #[arg(long)]
1089    pub unit: Option<String>,
1090
1091    /// Infer `--include` and `--exclude` filters per-commit from the
1092    /// repository's `.gitattributes` files.
1093    ///
1094    /// Counts filepaths that should be tracked by Git LFS but aren't
1095    /// yet pointers. Incompatible with explicit `--include` /
1096    /// `--exclude` filters and with `--pointers` settings other than
1097    /// `ignore`. Implies `--pointers=ignore` if not set.
1098    #[arg(long)]
1099    pub fixup: bool,
1100
1101    /// Don't refresh the known set of remote references before the
1102    /// scan.
1103    #[arg(long)]
1104    pub skip_fetch: bool,
1105
1106    /// Remote to consult (currently a no-op; reserved for the
1107    /// auto-fetch path).
1108    #[arg(long)]
1109    pub remote: Option<String>,
1110}
1111
1112/// Populate working copy with real content from Git LFS files.
1113///
1114/// Try to ensure that the working copy contains file content for Git LFS
1115/// objects for the current ref, if the object data is available. Does not
1116/// download any content; see git-lfs-fetch(1) for that.
1117///
1118/// Checkout scans the current ref for all LFS objects that would be
1119/// required, then where a file is either missing in the working copy, or
1120/// contains placeholder pointer content with the same SHA, the real file
1121/// content is written, provided we have it in the local store. Modified
1122/// files are never overwritten.
1123///
1124/// One or more may be provided as arguments to restrict the set of files
1125/// that are updated. Glob patterns are matched as per the format described
1126/// in gitignore(5).
1127///
1128/// When used with `--to` and the working tree is in a conflicted state due
1129/// to a merge, this option checks out one of the three stages a conflicting
1130/// Git LFS object into a separate file (which can be outside of the work
1131/// tree). This can make using diff tools to inspect and resolve merges
1132/// easier. A single Git LFS object's file path must be provided in
1133/// `PATHS`. If `FILE` already exists, whether as a regular
1134/// file, symbolic link, or directory, it will be removed and replaced, unless
1135/// it is a non-empty directory or otherwise cannot be deleted.
1136///
1137/// If the installed Git version is at least 2.42.0,
1138/// this command will by default check out Git LFS objects for files
1139/// only if they are present in the Git index and if they match a Git LFS
1140/// filter attribute from a `.gitattributes` file that is present in either
1141/// the index or the current working tree (or, as is always the case, if
1142/// they match a Git LFS filter attribute in a local gitattributes file
1143/// such as `$GIT_DIR/info/attributes`). These constraints do not apply
1144/// with prior versions of Git.
1145///
1146/// In a repository with a partial clone or sparse checkout, it is therefore
1147/// advisable to check out all `.gitattributes` files from HEAD before
1148/// using this command, if Git v2.42.0 or later is installed. Alternatively,
1149/// the `GIT_ATTR_SOURCE` environment variable may be set to HEAD, which
1150/// will cause Git to only read attributes from `.gitattributes` files in
1151/// HEAD and ignore those in the index or working tree.
1152///
1153/// In a bare repository, this command prints an informational message and
1154/// exits without modifying anything. In a future version, it may exit with
1155/// an error.
1156#[derive(Args)]
1157pub struct CheckoutArgs {
1158    // TODO(post-1.0): replace this manual stage/--to validation with
1159    // clap arg_group/requires/conflicts_with. Currently kept as-is
1160    // because tests/t-checkout.sh:897-909 assert upstream's exact error
1161    // wording; clap's wording would be a strict UX improvement but a
1162    // behavioral diff. Worth taking once we're free to update those
1163    // assertions.
1164    /// Check out the merge base of the specified file
1165    #[arg(long)]
1166    pub base: bool,
1167
1168    /// Check out our side (that of the current branch) of the
1169    /// conflict for the specified file
1170    #[arg(long)]
1171    pub ours: bool,
1172
1173    /// Check out their side (that of the other branch) of the
1174    /// conflict for the specified file
1175    #[arg(long)]
1176    pub theirs: bool,
1177
1178    /// If the working tree is in a conflicted state, check out the
1179    /// portion of the conflict specified by `--base`, `--ours`, or
1180    /// `--theirs` to the given path. Exactly one of these options
1181    /// is required.
1182    #[arg(long, value_name = "FILE")]
1183    pub to: Option<String>,
1184
1185    /// Paths to check out.
1186    ///
1187    /// When empty, everything in HEAD's tree is checked out. In
1188    /// conflict mode (`--to <path>` together with one of `--base`,
1189    /// `--ours`, or `--theirs`), exactly one path is required.
1190    pub paths: Vec<String>,
1191}
1192
1193/// Delete old LFS files from local storage
1194///
1195/// Delete locally stored LFS objects that aren't reachable from HEAD
1196/// or any unpushed commit, freeing up disk space.
1197#[derive(Args)]
1198pub struct PruneArgs {
1199    /// Don't actually delete anything; just report what would have
1200    /// been done.
1201    #[arg(short, long)]
1202    pub dry_run: bool,
1203
1204    /// Report the full detail of what is/would be deleted.
1205    #[arg(short, long)]
1206    pub verbose: bool,
1207
1208    /// Ignore the recent-refs / recent-commits retention windows
1209    /// when computing what is prunable. Equivalent to setting
1210    /// `lfs.fetchrecentrefsdays` and `lfs.fetchrecentcommitsdays` to
1211    /// 0 for this invocation.
1212    #[arg(long)]
1213    pub recent: bool,
1214
1215    /// Treat every pushed object as prunable regardless of the
1216    /// recent-refs / recent-commits / unpushed retention rules.
1217    /// Pointers reachable from HEAD's tree are still kept.
1218    #[arg(short, long)]
1219    pub force: bool,
1220
1221    /// Verify with the remote that prunable objects exist there
1222    /// before deleting them locally. With this on, an object that
1223    /// can't be served by the remote either halts the prune (default)
1224    /// or is dropped from the delete set (`--when-unverified=continue`).
1225    /// Reachable-but-unverified objects are reported as `missing on
1226    /// remote:`; unreachable objects (orphans not in any commit) are
1227    /// silently passed through unless `--verify-unreachable` is also
1228    /// set. Overrides `lfs.pruneverifyremotealways`.
1229    #[arg(short = 'c', long, conflicts_with = "no_verify_remote")]
1230    pub verify_remote: bool,
1231
1232    /// Override `lfs.pruneverifyremotealways=true` and skip the
1233    /// remote verify pass for this invocation.
1234    #[arg(long)]
1235    pub no_verify_remote: bool,
1236
1237    /// When `--verify-remote` is in effect, verify orphan objects
1238    /// (not reachable from any commit) too. Without this, orphans
1239    /// pass through verification silently and are still pruned.
1240    /// Overrides `lfs.pruneverifyunreachablealways`.
1241    #[arg(long, conflicts_with = "no_verify_unreachable")]
1242    pub verify_unreachable: bool,
1243
1244    /// Override `lfs.pruneverifyunreachablealways=true` and skip
1245    /// orphan verification for this invocation.
1246    #[arg(long)]
1247    pub no_verify_unreachable: bool,
1248
1249    /// What to do when `--verify-remote` finds objects missing on
1250    /// the remote. `halt` (the default) refuses the prune and lists
1251    /// the missing OIDs; `continue` drops them from the delete set
1252    /// and prunes the verified ones.
1253    #[arg(long, value_name = "MODE", default_value = "halt", value_parser = ["halt", "continue"])]
1254    pub when_unverified: String,
1255}
1256
1257/// Check Git LFS files for consistency
1258///
1259/// Check all Git LFS files in the current HEAD for consistency.
1260/// Corrupted files are moved to `.git/lfs/bad`.
1261///
1262/// A single committish may be given to inspect that commit instead of
1263/// HEAD. The `<a>..<b>` range form from upstream is not yet supported
1264/// — only a single ref is accepted. With no argument, HEAD is
1265/// examined.
1266///
1267/// The default is to perform all checks. `lfs.fetchexclude` is also
1268/// not yet honored on this command; objects whose paths match the
1269/// exclude list will still be checked.
1270#[derive(Args)]
1271pub struct FsckArgs {
1272    /// Ref to scan. Defaults to HEAD.
1273    pub refspec: Option<String>,
1274
1275    /// Check that each object in HEAD matches its expected hash and
1276    /// that each object exists on disk.
1277    #[arg(long)]
1278    pub objects: bool,
1279
1280    /// Check that each pointer is canonical and that each file which
1281    /// should be stored as a Git LFS file is so stored.
1282    #[arg(long)]
1283    pub pointers: bool,
1284
1285    /// Perform checks, but do not move any corrupted files to
1286    /// `.git/lfs/bad`.
1287    #[arg(short, long)]
1288    pub dry_run: bool,
1289}
1290
1291/// Show the status of Git LFS files in the working tree
1292///
1293/// Display paths of Git LFS objects that have not been pushed to the
1294/// Git LFS server (large files that would be uploaded by `git push`),
1295/// that have differences between the index file and the current HEAD
1296/// commit (large files that would be committed by `git commit`), or
1297/// that have differences between the working tree and the index file
1298/// (files that could be staged with `git add`).
1299///
1300/// Must be run in a non-bare repository.
1301#[derive(Args)]
1302pub struct StatusArgs {
1303    /// Give the output in an easy-to-parse format for scripts.
1304    #[arg(short, long)]
1305    pub porcelain: bool,
1306
1307    /// Write Git LFS file status information as JSON to standard
1308    /// output if the command exits successfully.
1309    ///
1310    /// Intended for interoperation with external tools. If
1311    /// `--porcelain` is also provided, that option takes precedence.
1312    #[arg(short, long)]
1313    pub json: bool,
1314}
1315
1316/// Set a file as "locked" on the Git LFS server
1317///
1318/// Sets the given file path as "locked" against the Git LFS server,
1319/// with the intention of blocking attempts by other users to update
1320/// the given path. Locking a file requires the file to exist in the
1321/// working copy.
1322///
1323/// Once locked, LFS will verify that Git pushes do not modify files
1324/// locked by other users. See the description of the
1325/// `lfs.<url>.locksverify` config key in git-lfs-config(5) for
1326/// details.
1327#[derive(Args)]
1328pub struct LockArgs {
1329    /// Paths to lock. Repo-relative or absolute; must resolve inside
1330    /// the working tree. Upstream's CLI accepts a single path; ours
1331    /// accepts multiple (additive extension).
1332    pub paths: Vec<String>,
1333
1334    /// Specify the Git LFS server to use. Ignored if the `lfs.url`
1335    /// config key is set.
1336    #[arg(short, long)]
1337    pub remote: Option<String>,
1338
1339    /// Write lock info as JSON to standard output if the command
1340    /// exits successfully.
1341    ///
1342    /// Intended for interoperation with external tools. If the command
1343    /// returns with a non-zero exit code, plain text messages are sent
1344    /// to standard error.
1345    #[arg(short, long)]
1346    pub json: bool,
1347
1348    /// Refspec to associate the lock with (extension over upstream).
1349    ///
1350    /// Defaults to the current branch's tracked upstream
1351    /// (`branch.<current>.merge`) or the current branch's full ref
1352    /// (`refs/heads/<branch>`).
1353    #[arg(long = "ref")]
1354    pub refspec: Option<String>,
1355}
1356
1357/// Lists currently locked files from the Git LFS server
1358///
1359/// Lists current locks from the Git LFS server. Without filters, all
1360/// locks visible to the configured remote are returned.
1361#[derive(Args)]
1362pub struct LocksArgs {
1363    /// Specify the Git LFS server to use. Ignored if the `lfs.url`
1364    /// config key is set.
1365    #[arg(short, long)]
1366    pub remote: Option<String>,
1367
1368    /// Specify a lock by its ID. Returns a single result.
1369    #[arg(short, long)]
1370    pub id: Option<String>,
1371
1372    /// Specify a lock by its path. Returns a single result.
1373    #[arg(short, long)]
1374    pub path: Option<String>,
1375
1376    /// List only our own locks which are cached locally. Skips a
1377    /// remote call.
1378    ///
1379    /// Useful when offline or to confirm what `git lfs lock` recorded
1380    /// locally. Combine with `--path` / `--id` / `--limit` to filter;
1381    /// `--verify` is rejected.
1382    #[arg(long)]
1383    pub local: bool,
1384
1385    /// Verify the lock owner on the server and mark our own locks
1386    /// with `O`.
1387    ///
1388    /// Own locks are held by us and the corresponding files can be
1389    /// updated for the next push. All other locks are held by someone
1390    /// else. Contrary to `--local`, this also detects locks held by us
1391    /// despite no local lock information being available (e.g. because
1392    /// the file had been locked from a different clone) and detects
1393    /// "broken" locks (e.g. someone else forcibly unlocked our files).
1394    #[arg(long)]
1395    pub verify: bool,
1396
1397    /// Maximum number of results to return.
1398    #[arg(short, long)]
1399    pub limit: Option<u32>,
1400
1401    /// Write lock info as JSON to standard output if the command
1402    /// exits successfully.
1403    ///
1404    /// Intended for interoperation with external tools. If the command
1405    /// returns with a non-zero exit code, plain text messages are sent
1406    /// to standard error.
1407    #[arg(short, long)]
1408    pub json: bool,
1409
1410    /// Refspec to filter locks by (extension over upstream).
1411    ///
1412    /// Defaults to the current branch's tracked upstream — same
1413    /// auto-resolution as `git lfs lock`.
1414    #[arg(long = "ref")]
1415    pub refspec: Option<String>,
1416}
1417
1418/// Remove "locked" setting for a file on the Git LFS server
1419///
1420/// Removes the given file path as "locked" on the Git LFS server.
1421/// Files must exist and have a clean git status before they can be
1422/// unlocked. The `--force` flag will skip these checks.
1423#[derive(Args)]
1424pub struct UnlockArgs {
1425    // TODO(post-1.0): replace the manual --id-xor-paths check
1426    // (cli/src/lock.rs:301-306) with a clap ArgGroup
1427    // (required = true, multiple = false) covering --id and the
1428    // positional paths arg. Currently kept as-is because
1429    // tests/t-unlock.sh:228,431,482 assert upstream's exact wording
1430    // ("Exactly one of --id or a set of paths must be provided").
1431    // Worth taking once we're free to update those assertions.
1432    /// Paths to unlock. Upstream's CLI accepts a single path; ours
1433    /// accepts multiple (additive extension). Mutually exclusive
1434    /// with `--id`.
1435    pub paths: Vec<String>,
1436
1437    /// Specify the Git LFS server to use. Ignored if the `lfs.url`
1438    /// config key is set.
1439    #[arg(short, long)]
1440    pub remote: Option<String>,
1441
1442    /// Tell the server to remove the lock, even if it's owned by
1443    /// another user.
1444    #[arg(short, long)]
1445    pub force: bool,
1446
1447    /// Specify a lock by its ID instead of path. Mutually exclusive
1448    /// with the positional paths.
1449    #[arg(short, long)]
1450    pub id: Option<String>,
1451
1452    /// Write lock info as JSON to standard output if the command
1453    /// exits successfully.
1454    ///
1455    /// Intended for interoperation with external tools. If the command
1456    /// returns with a non-zero exit code, plain text messages are sent
1457    /// to standard error.
1458    #[arg(short, long)]
1459    pub json: bool,
1460
1461    /// Refspec to send with the unlock request (extension over
1462    /// upstream).
1463    ///
1464    /// Defaults to the current branch's tracked upstream — same
1465    /// auto-resolution as `git lfs lock`.
1466    #[arg(long = "ref")]
1467    pub refspec: Option<String>,
1468}
1469
1470/// Show information about Git LFS files in the index and working tree
1471///
1472/// Display paths of Git LFS files that are found in the tree at the
1473/// given reference. If no reference is given, scan the currently
1474/// checked-out branch.
1475///
1476/// An asterisk (`*`) after the OID indicates a full object, a minus
1477/// (`-`) indicates an LFS pointer.
1478///
1479/// Note: upstream's `--include` / `--exclude` path filters and the
1480/// `--deleted` flag (which shows the full history of the given
1481/// reference, including objects that have been deleted) aren't yet
1482/// supported. The two-references form (`git lfs ls-files <a> <b>`,
1483/// to show files modified between two refs) is also not yet
1484/// supported.
1485#[derive(Args)]
1486pub struct LsFilesArgs {
1487    /// Ref to list. Defaults to HEAD.
1488    pub refspec: Option<String>,
1489
1490    /// Show the entire 64-character OID, instead of just the first
1491    /// 10.
1492    #[arg(short, long)]
1493    pub long: bool,
1494
1495    /// Show the size of the LFS object in parentheses at the end of
1496    /// each line.
1497    #[arg(short, long)]
1498    pub size: bool,
1499
1500    /// Show only the LFS-tracked file names.
1501    #[arg(short, long)]
1502    pub name_only: bool,
1503
1504    /// Inspect the full history of the repository, not the current
1505    /// HEAD (or other provided reference).
1506    ///
1507    /// Includes previous versions of LFS objects that are no longer
1508    /// found in the current tree.
1509    #[arg(short, long)]
1510    pub all: bool,
1511
1512    /// Show as much information as possible about an LFS file.
1513    ///
1514    /// Intended for manual inspection; the exact format may change
1515    /// at any time.
1516    #[arg(short, long)]
1517    pub debug: bool,
1518
1519    /// Write Git LFS file information as JSON to standard output if
1520    /// the command exits successfully.
1521    ///
1522    /// Intended for interoperation with external tools. If `--debug`
1523    /// is also provided, that option takes precedence. If any of
1524    /// `--long`, `--size`, or `--name-only` are provided, those
1525    /// options will have no effect.
1526    #[arg(short, long)]
1527    pub json: bool,
1528}