Update dependencies, improve CLI asset generation, and add man page

- Bump `git2` from 0.20.4 to 0.21.0 and `rusqlite` from 0.39.0 to 0.40.1 in Cargo.toml.
- Refactor CLI asset generation in `cli_assets.rs` to write a man page directly instead of generating it with `clap_mangen`.
- Implement a function to remove old man pages before writing the new one.
- Add tests to ensure the generated man page documents all visible command paths.
- Update the install script to copy the newly generated man page correctly.
- Introduce a new man page for the `depot` command with comprehensive documentation.
This commit is contained in:
2026-06-10 06:33:39 -05:00
parent 65cec6903a
commit 5b23ea95e0
7 changed files with 783 additions and 359 deletions
+60 -7
View File
@@ -1,4 +1,4 @@
//! Generation helpers for CLI man pages and shell completions.
//! Generation helpers for CLI assets.
use crate::cli::Cli;
use anyhow::{Context, Result};
@@ -8,8 +8,9 @@ use std::fs;
use std::path::Path;
const BIN_NAME: &str = "depot";
const MAN_PAGE: &str = include_str!("../man/depot.1");
/// Generate all supported shell completion scripts and a man page into `out_dir`.
/// Generate all supported shell completion scripts and the manual page into `out_dir`.
pub fn generate_cli_assets(out_dir: &Path) -> Result<()> {
fs::create_dir_all(out_dir)
.with_context(|| format!("Failed to create output directory {}", out_dir.display()))?;
@@ -17,7 +18,7 @@ pub fn generate_cli_assets(out_dir: &Path) -> Result<()> {
generate_completion(out_dir, Shell::Bash, "depot.bash")?;
generate_completion(out_dir, Shell::Zsh, "_depot")?;
generate_completion(out_dir, Shell::Fish, "depot.fish")?;
generate_man_pages(out_dir)?;
write_man_page(out_dir)?;
Ok(())
}
@@ -40,19 +41,59 @@ fn generate_completion(out_dir: &Path, shell: Shell, filename: &str) -> Result<(
Ok(())
}
fn generate_man_pages(out_dir: &Path) -> Result<()> {
clap_mangen::generate_to(command_for_generation(), out_dir)
.context("Failed to generate clap man pages")?;
fn write_man_page(out_dir: &Path) -> Result<()> {
remove_old_depot_man_pages(out_dir)?;
let output_path = out_dir.join("depot.1");
fs::write(&output_path, MAN_PAGE)
.with_context(|| format!("Failed to write man page to {}", output_path.display()))?;
Ok(())
}
fn remove_old_depot_man_pages(out_dir: &Path) -> Result<()> {
for entry in fs::read_dir(out_dir)
.with_context(|| format!("Failed to read output directory {}", out_dir.display()))?
{
let path = entry
.with_context(|| format!("Failed to inspect output directory {}", out_dir.display()))?
.path();
let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else {
continue;
};
if file_name == "depot.1" || file_name.starts_with("depot-") && file_name.ends_with(".1") {
fs::remove_file(&path)
.with_context(|| format!("Failed to remove stale man page {}", path.display()))?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Command;
fn visible_command_paths(command: &Command) -> Vec<String> {
command
.get_subcommands()
.filter(|subcommand| !subcommand.is_hide_set())
.flat_map(|subcommand| {
let name = subcommand.get_name();
let nested = visible_command_paths(subcommand);
if nested.is_empty() {
vec![name.to_string()]
} else {
let mut paths = vec![name.to_string()];
paths.extend(nested.into_iter().map(|nested| format!("{name} {nested}")));
paths
}
})
.collect()
}
#[test]
fn generate_cli_assets_writes_expected_files() {
let temp = tempfile::tempdir().unwrap();
std::fs::write(temp.path().join("depot-install.1"), "stale").unwrap();
generate_cli_assets(temp.path()).unwrap();
let bash = temp.path().join("depot.bash");
@@ -69,7 +110,19 @@ mod tests {
assert!(zsh.exists());
assert!(fish.exists());
assert!(man.exists());
assert!(man_pages > 1);
assert_eq!(man_pages, 1);
assert!(!std::fs::read_to_string(&man).unwrap().is_empty());
assert!(!temp.path().join("depot-install.1").exists());
}
#[test]
fn manual_page_documents_visible_command_paths() {
let command = command_for_generation();
for path in visible_command_paths(&command) {
assert!(
MAN_PAGE.contains(&format!("depot {path}")),
"manual page does not document `depot {path}`"
);
}
}
}
+2 -2
View File
@@ -4,7 +4,7 @@ use crate::metadata_time;
use anyhow::{Context, Result};
use rusqlite::{Connection, params};
use sha2::{Digest, Sha256, Sha512};
use std::collections::{HashMap, BTreeSet};
use std::collections::{BTreeSet, HashMap};
use std::fs;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
@@ -2538,7 +2538,7 @@ pub fn mirrors_status(
let head = repo.head().ok();
let branch = head
.as_ref()
.and_then(|h| h.shorthand().map(|s| s.to_string()))
.and_then(|h| h.shorthand().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "(no branch)".to_string());
// Latest commit OID
+5 -5
View File
@@ -170,7 +170,7 @@ fn apply_cherry_picks(repo: &Repository, cherry_pick_revs: &[String]) -> Result<
let tree = repo
.find_tree(tree_id)
.with_context(|| format!("Failed to find tree after cherry-pick {}", rev))?;
let message = commit.summary().unwrap_or("cherry-pick");
let message = commit.summary().ok().flatten().unwrap_or("cherry-pick");
let new_head = repo
.commit(
None,
@@ -336,7 +336,7 @@ fn repair_mirror_refs(repo: &Repository) -> Result<()> {
fn sync_remote_tracking_heads(repo: &Repository) -> Result<()> {
for reference_result in repo.references_glob("refs/remotes/origin/*")? {
let reference = reference_result?;
let Some(name) = reference.name() else {
let Ok(name) = reference.name() else {
continue;
};
if name == "refs/remotes/origin/HEAD" {
@@ -368,7 +368,7 @@ fn ensure_valid_local_head(repo: &Repository) -> Result<()> {
let mut candidates: Vec<String> = Vec::new();
for reference_result in repo.references_glob("refs/heads/*")? {
let reference = reference_result?;
let Some(name) = reference.name() else {
let Ok(name) = reference.name() else {
continue;
};
candidates.push(name.to_string());
@@ -517,7 +517,7 @@ fn resolve_remote_head_like<'a>(repo: &'a Repository) -> Result<Option<git2::Obj
let mut candidates: Vec<(String, Oid)> = Vec::new();
for reference_result in repo.references_glob("refs/remotes/origin/*")? {
let reference = reference_result?;
let Some(name) = reference.name() else {
let Ok(name) = reference.name() else {
continue;
};
if name == "refs/remotes/origin/HEAD" {
@@ -1049,7 +1049,7 @@ mod tests {
assert_eq!(
repo.head().unwrap().resolve().unwrap().name(),
Some("refs/heads/main")
Ok("refs/heads/main")
);
}