Skip to main content

git_lfs/
man.rs

1//! Per-subcommand documentation extras (man pages + mdbook).
2//!
3//! The clap derive in [`crate::args`] is the source of truth for the
4//! NAME, SYNOPSIS, and OPTIONS surface; xtask renders that
5//! automatically into both groff (man pages) and markdown (mdbook).
6//! This module owns everything richer: DESCRIPTION prose, EXAMPLES,
7//! NOTES, FILES, SEE ALSO.
8//!
9//! **Bodies are authored in markdown.** xtask passes them through
10//! verbatim for the markdown output and converts them to groff for
11//! the man pages, so a single source feeds both formats. The
12//! supported markdown vocabulary is intentionally small: paragraphs,
13//! bold and italic, code spans and fenced code blocks, bulleted and
14//! numbered lists. Stick to that and the groff conversion stays
15//! predictable.
16//!
17//! Each subcommand exposes its extras here as a [`ManContent`] entry
18//! in [`extras_for`]. Bodies live under `cli/man/<sub>/*.md` and are
19//! pulled in via [`include_str!`], keeping prose out of `man.rs`.
20//!
21//! Onboarding a new section is two-step:
22//! 1. Drop one or more `.md` files into `cli/man/<subcommand>/`.
23//! 2. Add a match arm in [`extras_for`] referencing them.
24//!
25//! Subcommands without an entry get the auto-generated page with no
26//! extras: still useful, just shorter.
27
28/// Hand-authored extras for a single command's documentation.
29///
30/// Returned by [`extras_for`] keyed on the subcommand name (or `""`
31/// for the top-level `git-lfs` page). Both fields are markdown; xtask
32/// renders them to either groff or markdown depending on output
33/// format.
34#[derive(Debug)]
35pub struct ManContent {
36    /// Replaces the auto-generated DESCRIPTION (which is just the short
37    /// `about` from the clap derive). Markdown.
38    pub description: Option<&'static str>,
39
40    /// Sections appended after OPTIONS, in order. Each entry is
41    /// `(title, markdown body)`. Conventional titles: `EXAMPLES`,
42    /// `FILES`, `ENVIRONMENT`, `NOTES`, `BUGS`, `SEE ALSO`. The title
43    /// becomes a `.SH` in groff and a top-level `##` in markdown.
44    pub extra_sections: &'static [(&'static str, &'static str)],
45}
46
47impl ManContent {
48    /// A [`ManContent`] with no description and no extra sections.
49    ///
50    /// Returned by [`extras_for`] for subcommands that don't have
51    /// hand-authored extras; the caller splices in the empty result
52    /// unconditionally.
53    pub const fn empty() -> Self {
54        Self {
55            description: None,
56            extra_sections: &[],
57        }
58    }
59}
60
61const EMPTY: ManContent = ManContent::empty();
62
63const ROOT: ManContent = ManContent {
64    description: None,
65    extra_sections: &[("EXAMPLES", include_str!("../man/root/examples.md"))],
66};
67
68/// Markdown body for the `REPORTING BUGS` section.
69///
70/// xtask appends this to every generated man page and mdbook page, so
71/// the project URL and the "this is the Rust implementation" framing
72/// have a single source of truth. Change here and every page picks it
73/// up on the next regen.
74pub const REPORTING_BUGS: &str = include_str!("../man/reporting_bugs.md");
75
76const SMUDGE: ManContent = ManContent {
77    description: None,
78    extra_sections: &[
79        ("ENVIRONMENT", include_str!("../man/smudge/environment.md")),
80        ("KNOWN BUGS", include_str!("../man/smudge/known_bugs.md")),
81        ("SEE ALSO", include_str!("../man/smudge/see_also.md")),
82    ],
83};
84
85const CHECKOUT: ManContent = ManContent {
86    description: None,
87    extra_sections: &[("EXAMPLES", include_str!("../man/checkout/examples.md"))],
88};
89
90const FETCH: ManContent = ManContent {
91    description: None,
92    extra_sections: &[
93        (
94            "INCLUDE AND EXCLUDE",
95            include_str!("../man/fetch/include_and_exclude.md"),
96        ),
97        (
98            "DEFAULT REMOTE",
99            include_str!("../man/fetch/default_remote.md"),
100        ),
101        ("DEFAULT REFS", include_str!("../man/fetch/default_refs.md")),
102        (
103            "RECENT CHANGES",
104            include_str!("../man/fetch/recent_changes.md"),
105        ),
106        ("EXAMPLES", include_str!("../man/fetch/examples.md")),
107        ("SEE ALSO", include_str!("../man/fetch/see_also.md")),
108    ],
109};
110
111const PULL: ManContent = ManContent {
112    description: None,
113    extra_sections: &[
114        (
115            "INCLUDE AND EXCLUDE",
116            include_str!("../man/pull/include_and_exclude.md"),
117        ),
118        (
119            "DEFAULT REMOTE",
120            include_str!("../man/pull/default_remote.md"),
121        ),
122        ("EXAMPLES", include_str!("../man/pull/examples.md")),
123        ("SEE ALSO", include_str!("../man/pull/see_also.md")),
124    ],
125};
126
127const PUSH: ManContent = ManContent {
128    description: None,
129    extra_sections: &[("SEE ALSO", include_str!("../man/push/see_also.md"))],
130};
131
132const INSTALL: ManContent = ManContent {
133    description: None,
134    extra_sections: &[("SEE ALSO", include_str!("../man/install/see_also.md"))],
135};
136
137const UNINSTALL: ManContent = ManContent {
138    description: None,
139    extra_sections: &[("SEE ALSO", include_str!("../man/uninstall/see_also.md"))],
140};
141
142const TRACK: ManContent = ManContent {
143    description: None,
144    extra_sections: &[
145        ("EXAMPLES", include_str!("../man/track/examples.md")),
146        ("SEE ALSO", include_str!("../man/track/see_also.md")),
147    ],
148};
149
150const UNTRACK: ManContent = ManContent {
151    description: None,
152    extra_sections: &[
153        ("EXAMPLES", include_str!("../man/untrack/examples.md")),
154        ("SEE ALSO", include_str!("../man/untrack/see_also.md")),
155    ],
156};
157
158const LOCK: ManContent = ManContent {
159    description: None,
160    extra_sections: &[("SEE ALSO", include_str!("../man/lock/see_also.md"))],
161};
162
163const LOCKS: ManContent = ManContent {
164    description: None,
165    extra_sections: &[("SEE ALSO", include_str!("../man/locks/see_also.md"))],
166};
167
168const UNLOCK: ManContent = ManContent {
169    description: None,
170    extra_sections: &[("SEE ALSO", include_str!("../man/unlock/see_also.md"))],
171};
172
173const STATUS: ManContent = ManContent {
174    description: None,
175    extra_sections: &[("SEE ALSO", include_str!("../man/status/see_also.md"))],
176};
177
178const LS_FILES: ManContent = ManContent {
179    description: None,
180    extra_sections: &[("SEE ALSO", include_str!("../man/ls-files/see_also.md"))],
181};
182
183const PRUNE: ManContent = ManContent {
184    description: Some(include_str!("../man/prune/description.md")),
185    extra_sections: &[
186        ("RECENT FILES", include_str!("../man/prune/recent_files.md")),
187        (
188            "UNPUSHED LFS FILES",
189            include_str!("../man/prune/unpushed.md"),
190        ),
191        (
192            "VERIFY REMOTE",
193            include_str!("../man/prune/verify_remote.md"),
194        ),
195        (
196            "DEFAULT REMOTE",
197            include_str!("../man/prune/default_remote.md"),
198        ),
199        ("SEE ALSO", include_str!("../man/prune/see_also.md")),
200    ],
201};
202
203const FSCK: ManContent = ManContent {
204    description: None,
205    extra_sections: &[("SEE ALSO", include_str!("../man/fsck/see_also.md"))],
206};
207
208const CLEAN: ManContent = ManContent {
209    description: None,
210    extra_sections: &[("SEE ALSO", include_str!("../man/clean/see_also.md"))],
211};
212
213const FILTER_PROCESS: ManContent = ManContent {
214    description: None,
215    extra_sections: &[(
216        "SEE ALSO",
217        include_str!("../man/filter-process/see_also.md"),
218    )],
219};
220
221const CLONE: ManContent = ManContent {
222    description: None,
223    extra_sections: &[("SEE ALSO", include_str!("../man/clone/see_also.md"))],
224};
225
226const PRE_PUSH: ManContent = ManContent {
227    description: None,
228    extra_sections: &[("SEE ALSO", include_str!("../man/pre-push/see_also.md"))],
229};
230
231const POST_CHECKOUT: ManContent = ManContent {
232    description: None,
233    extra_sections: &[("SEE ALSO", include_str!("../man/post-checkout/see_also.md"))],
234};
235
236const POST_COMMIT: ManContent = ManContent {
237    description: None,
238    extra_sections: &[("SEE ALSO", include_str!("../man/post-commit/see_also.md"))],
239};
240
241const POST_MERGE: ManContent = ManContent {
242    description: None,
243    extra_sections: &[("SEE ALSO", include_str!("../man/post-merge/see_also.md"))],
244};
245
246const MIGRATE: ManContent = ManContent {
247    description: None,
248    extra_sections: &[
249        (
250            "INCLUDE AND EXCLUDE",
251            include_str!("../man/migrate/include_and_exclude.md"),
252        ),
253        (
254            "INCLUDE AND EXCLUDE REFERENCES",
255            include_str!("../man/migrate/include_and_exclude_references.md"),
256        ),
257        ("EXAMPLES", include_str!("../man/migrate/examples.md")),
258        ("SEE ALSO", include_str!("../man/migrate/see_also.md")),
259    ],
260};
261
262const MIGRATE_INFO: ManContent = ManContent {
263    description: None,
264    extra_sections: &[
265        ("EXAMPLES", include_str!("../man/migrate-info/examples.md")),
266        ("SEE ALSO", include_str!("../man/migrate-info/see_also.md")),
267    ],
268};
269
270const MIGRATE_IMPORT: ManContent = ManContent {
271    description: None,
272    extra_sections: &[
273        (
274            "EXAMPLES",
275            include_str!("../man/migrate-import/examples.md"),
276        ),
277        (
278            "SEE ALSO",
279            include_str!("../man/migrate-import/see_also.md"),
280        ),
281    ],
282};
283
284const MIGRATE_EXPORT: ManContent = ManContent {
285    description: None,
286    extra_sections: &[
287        (
288            "EXAMPLES",
289            include_str!("../man/migrate-export/examples.md"),
290        ),
291        (
292            "SEE ALSO",
293            include_str!("../man/migrate-export/see_also.md"),
294        ),
295    ],
296};
297
298const EXT: ManContent = ManContent {
299    description: None,
300    extra_sections: &[("EXAMPLES", include_str!("../man/ext/examples.md"))],
301};
302
303// git-lfs-config(5) is a synthetic page: no clap-derived OPTIONS or
304// DESCRIPTION, everything lives in the extras. The OPTIONS section is
305// built up across several commits; the framing sections
306// (CONFIGURATION FILES, LFSCONFIG, EXAMPLES, SEE ALSO) bracket
307// whatever lands in between.
308const CONFIG: ManContent = ManContent {
309    description: None,
310    extra_sections: &[
311        (
312            "CONFIGURATION FILES",
313            include_str!("../man/config/configuration_files.md"),
314        ),
315        (
316            "GENERAL SETTINGS",
317            include_str!("../man/config/general_settings.md"),
318        ),
319        (
320            "UPLOAD AND DOWNLOAD TRANSFER SETTINGS",
321            include_str!("../man/config/transfer_settings.md"),
322        ),
323        (
324            "PUSH SETTINGS",
325            include_str!("../man/config/push_settings.md"),
326        ),
327        (
328            "FETCH SETTINGS",
329            include_str!("../man/config/fetch_settings.md"),
330        ),
331        (
332            "PRUNE SETTINGS",
333            include_str!("../man/config/prune_settings.md"),
334        ),
335        ("EXTENSIONS", include_str!("../man/config/extensions.md")),
336        (
337            "OTHER SETTINGS",
338            include_str!("../man/config/other_settings.md"),
339        ),
340        ("LFSCONFIG", include_str!("../man/config/lfsconfig.md")),
341        ("EXAMPLES", include_str!("../man/config/examples.md")),
342        ("SEE ALSO", include_str!("../man/config/see_also.md")),
343    ],
344};
345
346/// Look up the doc extras for `subcommand`.
347///
348/// `subcommand` is the clap subcommand name (e.g. `"fetch"`,
349/// `"checkout"`); pass `""` for the top-level `git-lfs` page. Returns
350/// a reference to [`ManContent::empty`] when there's no entry, so the
351/// caller can always splice unconditionally.
352pub fn extras_for(subcommand: &str) -> &'static ManContent {
353    match subcommand {
354        "smudge" => &SMUDGE,
355        "ext" => &EXT,
356        "config" => &CONFIG,
357        "checkout" => &CHECKOUT,
358        "fetch" => &FETCH,
359        "pull" => &PULL,
360        "push" => &PUSH,
361        "install" => &INSTALL,
362        "uninstall" => &UNINSTALL,
363        "track" => &TRACK,
364        "untrack" => &UNTRACK,
365        "lock" => &LOCK,
366        "locks" => &LOCKS,
367        "unlock" => &UNLOCK,
368        "status" => &STATUS,
369        "ls-files" => &LS_FILES,
370        "prune" => &PRUNE,
371        "fsck" => &FSCK,
372        "clean" => &CLEAN,
373        "filter-process" => &FILTER_PROCESS,
374        "clone" => &CLONE,
375        "pre-push" => &PRE_PUSH,
376        "post-checkout" => &POST_CHECKOUT,
377        "post-commit" => &POST_COMMIT,
378        "post-merge" => &POST_MERGE,
379        "migrate" => &MIGRATE,
380        "migrate-info" => &MIGRATE_INFO,
381        "migrate-import" => &MIGRATE_IMPORT,
382        "migrate-export" => &MIGRATE_EXPORT,
383        "" => &ROOT,
384        _ => &EMPTY,
385    }
386}