Phase 1 - Code Quality: - Rename script/script.rs to script/types.rs (module inception fix) - Apply Clippy lint fixes (is_none_or, is_some_and, char patterns, etc.) - Implement FromStr for CatalogFile and Category - Add filtered_targets() and filtered_repos() helpers to Config Phase 2 - Validation & Error Handling: - Add validate_repo_identifier() for GitHub shorthand validation - Fix first-run setup to fail gracefully if no targets configured - Improve import to collect failures and only save on success - Add AssetInstallResult for detailed install failure tracking - Fix lockfile timestamp documentation (Unix epoch, not RFC 3339) - Add comprehensive RevType heuristics documentation - Add checkout warning when local modifications will be discarded Phase 3 - Test Coverage: - Add tempfile, assert_cmd, predicates dev dependencies - Add security tests (symlink boundaries, copy mode) - Add git operations tests (init, head_commit, RevType parsing) - Add lockfile tests (roundtrip, lock/get operations) - Add CLI integration tests (help, validation, duplicates) - Add config validation tests for new helper methods All 48 tests pass, clippy clean, release build verified.
113 lines
3.6 KiB
Rust
113 lines
3.6 KiB
Rust
use colored::Colorize;
|
|
|
|
use crate::config::Config;
|
|
use crate::error::Result;
|
|
use crate::paths::Paths;
|
|
use crate::repo::{Repository, git_ops::{RevType, UpdateResult}};
|
|
use crate::ui::create_spinner;
|
|
|
|
/// Execute the `update` command - fetch and update all repositories
|
|
pub fn execute(repos_filter: Option<Vec<String>>) -> Result<()> {
|
|
let paths = Paths::new()?;
|
|
let config = Config::load(&paths.config_file)?;
|
|
|
|
if config.repos.is_empty() {
|
|
println!("{}", "No repositories configured.".yellow());
|
|
return Ok(());
|
|
}
|
|
|
|
let mut updated = 0;
|
|
let mut up_to_date = 0;
|
|
let mut errors = 0;
|
|
|
|
for entry in config.filtered_repos(repos_filter.as_deref()) {
|
|
let repo = Repository::from_entry(entry.clone(), &paths);
|
|
|
|
if !repo.is_cloned {
|
|
println!(
|
|
"{} {} (not installed, run 'install' first)",
|
|
"Skipping".yellow(),
|
|
entry.repo.cyan()
|
|
);
|
|
continue;
|
|
}
|
|
|
|
let spinner = create_spinner(&format!("Checking {}...", entry.repo));
|
|
|
|
// Get current commit before fetch
|
|
let before_commit = repo.current_commit()?.unwrap_or_default();
|
|
|
|
// Fetch updates
|
|
if let Err(e) = repo.fetch() {
|
|
spinner.finish_with_message(format!("{} {} {}", "✗".red(), entry.repo.cyan(), "fetch failed".red()));
|
|
eprintln!(" {}: {}", "Error".red(), e);
|
|
errors += 1;
|
|
continue;
|
|
}
|
|
|
|
// Check for updates
|
|
match repo.update() {
|
|
Ok(UpdateResult::Updated(new_commit)) => {
|
|
spinner.finish_with_message(format!(
|
|
"{} {} {} ({} → {})",
|
|
"✓".green(),
|
|
entry.repo.cyan(),
|
|
"updated".green(),
|
|
&before_commit[..7.min(before_commit.len())].dimmed(),
|
|
&new_commit[..7.min(new_commit.len())].green()
|
|
));
|
|
updated += 1;
|
|
}
|
|
Ok(UpdateResult::UpToDate) => {
|
|
spinner.finish_with_message(format!(
|
|
"{} {} {}",
|
|
"✓".dimmed(),
|
|
entry.repo.cyan(),
|
|
"up to date".dimmed()
|
|
));
|
|
up_to_date += 1;
|
|
}
|
|
Ok(UpdateResult::Pinned) => {
|
|
let pin_info = match repo.rev_type() {
|
|
RevType::Commit(c) => format!("commit {}", &c[..7.min(c.len())]),
|
|
RevType::Tag(t) => format!("tag {}", t),
|
|
_ => "pinned".to_string(),
|
|
};
|
|
spinner.finish_with_message(format!(
|
|
"{} {} {} ({})",
|
|
"📌".dimmed(),
|
|
entry.repo.cyan(),
|
|
"pinned".dimmed(),
|
|
pin_info.dimmed()
|
|
));
|
|
up_to_date += 1;
|
|
}
|
|
Err(e) => {
|
|
spinner.finish_with_message(format!(
|
|
"{} {} {}",
|
|
"✗".red(),
|
|
entry.repo.cyan(),
|
|
"update failed".red()
|
|
));
|
|
eprintln!(" {}: {}", "Error".red(), e);
|
|
errors += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
println!();
|
|
println!(
|
|
"{} {} updated, {} up to date, {} errors",
|
|
"Done!".green().bold(),
|
|
updated.to_string().cyan(),
|
|
up_to_date.to_string().dimmed(),
|
|
if errors > 0 {
|
|
errors.to_string().red()
|
|
} else {
|
|
errors.to_string().dimmed()
|
|
}
|
|
);
|
|
|
|
Ok(())
|
|
}
|