Skip to main content

git_lfs/
args.rs

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