feat: deploy drift via auto-discovered VPS mirror, no manual metadata
Sprint E, redesigned from the original per-repo-metadata plan: the deploy host already mirrors workspace_root 1:1 (documented convention), so instead of maintaining a repo->host/compose-path mapping by hand, vulp refresh runs one batched remote command (via a configurable ssh wrapper) listing every mirrored repo's HEAD sha and diffs it against local HEAD. - src/deploy.rs: DeployState (InSync/Behind(n)/Diverged), scan_deploy_heads (one remote round-trip, retried once for this VPS's known SSH flakiness), drift_for (path relative to workspace_root -> host lookup -> local git rev-list --count) - config: deploy_ssh_cmd + deploy_root, both empty by default (feature is fully inert, no SSH attempted, until configured) - cache: RepoMeta.deploy_status, folded into the existing refresh loop (TUI [R] and CLI) alongside CI/dependabot rather than a second pass - table: DEPLOY column (checkmark/behind-count/diverged/dash); CLI list --stale-deploy and deploy_status in --json - verified against the real VPS: 37/49 repos matched to a mirror (30 in sync, 7 behind 1-4 commits - a real signal), everything else correctly shows unset (no mirror there, e.g. vulp itself, a pure OSS dev tool) - set deploy_ssh_cmd/deploy_root in the live config so this is active now, not just theoretically configurable
This commit is contained in:
1 parent
a3658d5e97
commit
4b0a6e324f
15 files changed
+256
-16
No files matched your search
@@ -9,7 +9,7 @@ use anyhow::Result;
|
||||
pub fn list(
|
||||
cfg: &AppConfig, json: bool,
|
||||
dirty: bool, behind: bool, ahead: bool,
|
||||
lang: Option<String>, project: Option<String>, alerts: bool,
|
||||
lang: Option<String>, project: Option<String>, alerts: bool, stale_deploy: bool,
|
||||
) -> Result<()> {
|
||||
let mut repos = scan_repos(cfg);
|
||||
|
||||
@@ -17,6 +17,14 @@ pub fn list(
|
||||
if behind { repos.retain(|r| r.behind > 0); }
|
||||
if ahead { repos.retain(|r| r.ahead > 0); }
|
||||
if alerts { repos.retain(|r| r.dependabot_open.unwrap_or(0) > 0); }
|
||||
if stale_deploy {
|
||||
repos.retain(|r| {
|
||||
!matches!(
|
||||
r.deploy_status.as_deref().and_then(crate::deploy::DeployState::parse),
|
||||
None | Some(crate::deploy::DeployState::InSync)
|
||||
)
|
||||
});
|
||||
}
|
||||
if let Some(l) = lang {
|
||||
let l = l.to_lowercase();
|
||||
repos.retain(|r| r.lang.as_deref().map(|x| x.to_lowercase().contains(&l)).unwrap_or(false));
|
||||
@@ -45,7 +53,15 @@ pub fn list(
|
||||
if r.is_dirty { flags.push('⚠'); }
|
||||
if r.behind > 0 { flags.push('↓'); }
|
||||
if r.ahead > 0 { flags.push('↑'); }
|
||||
if alerts {
|
||||
if stale_deploy {
|
||||
let state = r.deploy_status.as_deref().and_then(crate::deploy::DeployState::parse);
|
||||
let label = match state {
|
||||
Some(crate::deploy::DeployState::Behind(n)) => format!("{n} behind"),
|
||||
Some(crate::deploy::DeployState::Diverged) => "diverged".into(),
|
||||
_ => "-".into(),
|
||||
};
|
||||
println!("{:<name_w$} {:<12} {:<10} {}", r.name, r.branch, label, r.lang.as_deref().unwrap_or("-"));
|
||||
} else if alerts {
|
||||
println!(
|
||||
"{:<name_w$} {:<12} {:>3} alerts {}",
|
||||
r.name, r.branch, r.dependabot_open.unwrap_or(0),
|
||||
|
||||
Reference in new issue
Block a user