diff --git a/src/backend.rs b/src/backend.rs index 3d523b97b8..1564199dfd 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -41,51 +41,42 @@ impl Branch { } } -/// A nomad managed ref for the current user. -#[derive(Debug, PartialEq, Eq)] -pub struct HostBranch { - /// The host this branch comes from. - pub host: String, - /// The branch name. - pub branch: Branch, - /// Any additional data the [`Backend`] would like to carry around. - pub ref_: Ref, -} - -/// A nomad managed ref from a remote, which may belong to an entirely different user. +/// A ref representing a branch managed by nomad. #[derive(Debug, PartialEq, Eq, Hash)] -pub struct RemoteHostBranch { +pub struct NomadRef { /// The user this branch belongs to. pub user: String, /// The host this branch comes from. pub host: String, /// The branch name. pub branch: Branch, + /// Any additional data the [`Backend`] would like to carry around. + pub ref_: Ref, } /// A point in time view of refs we care about. -pub struct Snapshot { +pub struct Snapshot { /// The active branches in this clone that the user manipulates directly with `git branch` etc. pub local_branches: HashSet, /// The refs that nomad manages to follow the local branches. - pub host_branches: Vec>, + pub host_branches: Vec>, } /// Describes where a ref should be removed from. #[derive(Debug, PartialEq, Eq)] pub enum PruneFrom { - LocalOnly(HostBranch), - LocalAndRemote(HostBranch), + LocalOnly(NomadRef), + LocalAndRemote(NomadRef), } -impl Snapshot { +impl Snapshot { /// Find nomad host branches that can be pruned because: /// 1. The local branch they were based on no longer exists. /// 2. The remote branch they were based on no longer exists. pub fn prune_deleted_branches( self, config: &Config, - remote_host_branches: &HashSet, + remote_host_branches: &HashSet>, ) -> Vec> { let Self { host_branches, @@ -99,11 +90,7 @@ impl Snapshot { if !local_branches.contains(&hb.branch) { prune.push(PruneFrom::LocalAndRemote(hb)); } - } else if !remote_host_branches.contains(&RemoteHostBranch { - user: config.user.clone(), - host: hb.host.clone(), - branch: hb.branch.clone(), - }) { + } else if !remote_host_branches.contains(&hb) { prune.push(PruneFrom::LocalOnly(hb)); } } @@ -135,9 +122,9 @@ impl Snapshot { .collect() } - /// Return all [`HostBranch`]s grouped by host in sorted order. - pub fn sorted_hosts_and_branches(self) -> Vec<(String, Vec>)> { - let mut by_host = HashMap::>>::new(); + /// Return all [`NomadRef`]s grouped by host in sorted order. + pub fn sorted_hosts_and_branches(self) -> Vec<(String, Vec>)> { + let mut by_host = HashMap::>>::new(); let Self { host_branches, .. } = self; for hb in host_branches { @@ -164,7 +151,7 @@ impl Snapshot { pub fn branches_for_host(&self, config: &Config) -> Vec { self.host_branches .iter() - .filter(|hb| hb.host == config.host) + .filter(|hb| hb.user == config.user && hb.host == config.host) .map(|hb| hb.branch.clone()) .collect() } @@ -174,7 +161,7 @@ impl Snapshot { /// with the low level implementation ("invoke a git binary"). pub trait Backend { /// Any additional information the backend would like to carry about a nomad managed ref. - type Ref: Display; + type Ref: Display + Eq + Hash; /// Extract the persistent nomad [`Config`] for this git clone. fn read_config(&self) -> Result>; @@ -183,10 +170,10 @@ pub trait Backend { fn write_config(&self, config: &Config) -> Result<()>; /// Build a point in time snapshot for all refs that nomad cares about. - fn snapshot(&self) -> Result>; + fn snapshot(&self, config: &Config) -> Result>; /// Fetch all nomad managed refs from a given remote. - fn fetch(&self, config: &Config, remote: &Remote) -> Result>; + fn fetch(&self, config: &Config, remote: &Remote) -> Result>>; /// Push local branches to nomad managed refs in the remote. fn push(&self, config: &Config, remote: &Remote) -> Result<()>; @@ -206,11 +193,11 @@ mod tests { iter::{self, FromIterator}, }; - use crate::backend::{Config, HostBranch, PruneFrom}; + use crate::backend::{Config, PruneFrom}; - use super::{Branch, RemoteHostBranch, Snapshot}; + use super::{Branch, NomadRef, Snapshot}; - #[derive(Debug, PartialEq, Eq)] + #[derive(Debug, PartialEq, Eq, Hash)] struct Ref; impl fmt::Display for Ref { @@ -223,17 +210,20 @@ mod tests { Snapshot { local_branches: local_branches.into_iter().map(Branch::str).collect(), host_branches: vec![ - HostBranch { + NomadRef { + user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch0"), ref_: Ref, }, - HostBranch { + NomadRef { + user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch1"), ref_: Ref, }, - HostBranch { + NomadRef { + user: "user0".to_string(), host: "host1".to_string(), branch: Branch::str("branch1"), ref_: Ref, @@ -251,16 +241,13 @@ mod tests { fn remote_host_branches( collection: impl IntoIterator, - ) -> HashSet { - HashSet::from_iter( - collection - .into_iter() - .map(|(user, host, branch)| RemoteHostBranch { - user: user.to_string(), - host: host.to_string(), - branch: Branch::str(branch), - }), - ) + ) -> HashSet> { + HashSet::from_iter(collection.into_iter().map(|(user, host, branch)| NomadRef { + user: user.to_string(), + host: host.to_string(), + branch: Branch::str(branch), + ref_: Ref {}, + })) } /// Sets up the scenario where: @@ -332,7 +319,8 @@ mod tests { assert_eq!( prune, - vec![PruneFrom::LocalAndRemote(HostBranch { + vec![PruneFrom::LocalAndRemote(NomadRef { + user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch1"), ref_: Ref, @@ -364,7 +352,8 @@ mod tests { assert_eq!( prune, - vec![PruneFrom::LocalOnly(HostBranch { + vec![PruneFrom::LocalOnly(NomadRef { + user: "user0".to_string(), host: "host1".to_string(), branch: Branch::str("branch1"), ref_: Ref, @@ -379,17 +368,20 @@ mod tests { assert_eq!( prune, vec![ - PruneFrom::LocalAndRemote(HostBranch { + PruneFrom::LocalAndRemote(NomadRef { + user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch0"), ref_: Ref, },), - PruneFrom::LocalAndRemote(HostBranch { + PruneFrom::LocalAndRemote(NomadRef { + user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch1"), ref_: Ref, },), - PruneFrom::LocalAndRemote(HostBranch { + PruneFrom::LocalAndRemote(NomadRef { + user: "user0".to_string(), host: "host1".to_string(), branch: Branch::str("branch1"), ref_: Ref, @@ -406,12 +398,14 @@ mod tests { assert_eq!( prune, vec![ - PruneFrom::LocalAndRemote(HostBranch { + PruneFrom::LocalAndRemote(NomadRef { + user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch0"), ref_: Ref, },), - PruneFrom::LocalAndRemote(HostBranch { + PruneFrom::LocalAndRemote(NomadRef { + user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch1"), ref_: Ref, diff --git a/src/command.rs b/src/command.rs index 55e06a5adc..becc3db617 100644 --- a/src/command.rs +++ b/src/command.rs @@ -3,7 +3,7 @@ use anyhow::{bail, Result}; use crate::{ - backend::{Backend, Config, HostBranch, PruneFrom, Remote, Snapshot}, + backend::{Backend, Config, NomadRef, PruneFrom, Remote, Snapshot}, progress::Progress, }; @@ -35,7 +35,7 @@ pub fn sync( ) -> Result<()> { backend.push(config, remote)?; let remote_host_branches = backend.fetch(config, remote)?; - let snapshot = backend.snapshot()?; + let snapshot = backend.snapshot(config)?; backend.prune( config, remote, @@ -46,7 +46,7 @@ pub fn sync( if progress.is_output_allowed() { println!(); - ls(backend)? + ls(backend, config)? } Ok(()) @@ -56,13 +56,13 @@ pub fn sync( /// /// Does not respect [`Progress::is_output_allowed`] because output is the whole point of this /// command. -pub fn ls(backend: B) -> Result<()> { - let snapshot = backend.snapshot()?; +pub fn ls(backend: B, config: &Config) -> Result<()> { + let snapshot = backend.snapshot(config)?; for (host, branches) in snapshot.sorted_hosts_and_branches() { println!("{}", host); - for HostBranch { ref_, .. } in branches { + for NomadRef { ref_, .. } in branches { println!(" {}", ref_); } } @@ -76,7 +76,7 @@ where F: Fn(Snapshot) -> Vec>, { backend.fetch(config, remote)?; - let snapshot = backend.snapshot()?; + let snapshot = backend.snapshot(config)?; let prune = to_prune(snapshot); backend.prune(config, remote, prune.iter())?; Ok(()) diff --git a/src/git_binary.rs b/src/git_binary.rs index 2158f4d784..446a95f409 100644 --- a/src/git_binary.rs +++ b/src/git_binary.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Result}; use std::{collections::HashSet, ffi::OsStr, path::Path, process::Command}; use crate::{ - backend::{Backend, Branch, Config, HostBranch, PruneFrom, Remote, RemoteHostBranch, Snapshot}, + backend::{Backend, Branch, Config, NomadRef, PruneFrom, Remote, Snapshot}, git_ref::GitRef, progress::{output_stdout, Progress, Run}, }; @@ -38,7 +38,7 @@ fn git_command>(name: S) -> Command { /// both built-in and third party. mod namespace { use crate::{ - backend::{Branch, Config, HostBranch, RemoteHostBranch}, + backend::{Branch, Config, NomadRef}, git_ref::GitRef, }; @@ -103,7 +103,10 @@ mod namespace { format!("refs/{}/{}/{}/{}", PREFIX, config.user, config.host, branch) } - pub fn host_branch_from_local_ref(git_ref: GitRef) -> Result, GitRef> { + pub fn host_branch_from_local_ref( + config: &Config, + git_ref: GitRef, + ) -> Result, GitRef> { let parts = git_ref.name.split('/').collect::>(); match parts.as_slice() { ["refs", prefix, host, branch_name] => { @@ -111,7 +114,8 @@ mod namespace { return Err(git_ref); } - Ok(HostBranch { + Ok(NomadRef { + user: config.user.clone(), host: host.to_string(), branch: Branch::str(branch_name), ref_: git_ref, @@ -121,7 +125,7 @@ mod namespace { } } - pub fn host_branch_from_remote_ref(git_ref: GitRef) -> Result { + pub fn host_branch_from_remote_ref(git_ref: GitRef) -> Result, GitRef> { let parts = git_ref.name.split('/').collect::>(); match parts.as_slice() { ["refs", prefix, user, host, branch_name] => { @@ -129,10 +133,11 @@ mod namespace { return Err(git_ref); } - Ok(RemoteHostBranch { + Ok(NomadRef { user: user.to_string(), host: host.to_string(), branch: Branch::str(branch_name), + ref_: git_ref, }) } _ => Err(git_ref), @@ -142,7 +147,7 @@ mod namespace { #[cfg(test)] mod tests { use crate::{ - backend::{Branch, Config, HostBranch, RemoteHostBranch}, + backend::{Branch, Config, NomadRef}, git_ref::GitRef, }; @@ -154,21 +159,20 @@ mod namespace { /// are duals). #[test] fn test_create_and_parse_local_ref() { + let config = &Config { + user: "user0".to_string(), + host: "host0".to_string(), + }; let git_ref = GitRef { commit_id: "some_commit_id".to_string(), - name: local_ref( - &Config { - user: "user0".to_string(), - host: "host0".to_string(), - }, - "branch0", - ), + name: local_ref(config, "branch0"), }; assert_eq!( - host_branch_from_local_ref(git_ref.clone()), - Ok(HostBranch { - host: "host0".to_string(), + host_branch_from_local_ref(config, git_ref.clone()), + Ok(NomadRef { + user: config.user.clone(), + host: config.host.clone(), branch: Branch::str("branch0"), ref_: git_ref, }) @@ -191,11 +195,12 @@ mod namespace { }; assert_eq!( - host_branch_from_remote_ref(git_ref), - Ok(RemoteHostBranch { + host_branch_from_remote_ref(git_ref.clone()), + Ok(NomadRef { user: "user0".to_string(), host: "host0".to_string(), branch: Branch::str("branch0"), + ref_: git_ref, }) ); } @@ -453,18 +458,18 @@ impl<'progress, 'name> Backend for GitBinary<'progress, 'name> { Ok(()) } - fn snapshot(&self) -> Result> { + fn snapshot(&self, config: &Config) -> Result> { let refs = self.list_refs("Fetching all refs")?; let mut local_branches = HashSet::::new(); - let mut host_branches = Vec::>::new(); + let mut host_branches = Vec::>::new(); for r in refs { if let Some(name) = r.name.strip_prefix("refs/heads/") { local_branches.insert(Branch::str(name)); } - if let Ok(host_branch) = namespace::host_branch_from_local_ref(r) { + if let Ok(host_branch) = namespace::host_branch_from_local_ref(config, r) { host_branches.push(host_branch); } } @@ -475,7 +480,7 @@ impl<'progress, 'name> Backend for GitBinary<'progress, 'name> { }) } - fn fetch(&self, config: &Config, remote: &Remote) -> Result> { + fn fetch(&self, config: &Config, remote: &Remote) -> Result>> { self.fetch_refspecs( format!("Fetching branches from {}", remote.0), remote, @@ -705,7 +710,7 @@ mod test_backend { use tempfile::{tempdir, TempDir}; use crate::{ - backend::{Backend, Branch, Config, HostBranch, PruneFrom, Remote, RemoteHostBranch}, + backend::{Backend, Branch, Config, NomadRef, PruneFrom, Remote}, git_binary::namespace, progress::{Progress, Run, Verbosity}, }; @@ -813,7 +818,7 @@ mod test_backend { self.git.push(&self.config, &self.remote()).unwrap(); } - fn fetch(&self) -> HashSet { + fn fetch(&self) -> HashSet> { self.git.fetch(&self.config, &self.remote()).unwrap() } @@ -824,7 +829,8 @@ mod test_backend { let ref_name = namespace::local_ref(&self.config, name); let ref_ = self.git.get_ref("", ref_name).unwrap(); - PruneFrom::LocalAndRemote(HostBranch { + PruneFrom::LocalAndRemote(NomadRef { + user: self.config.host.clone(), host: self.config.host.clone(), branch: Branch::str(name), ref_, @@ -921,7 +927,7 @@ mod test_backend { assert_eq!( host1 .git - .snapshot() + .snapshot(&host0.config) .unwrap() .branches_for_host(&host0.config), vec![] @@ -929,10 +935,19 @@ mod test_backend { let remote_host_branches = host1.fetch(); assert_eq!( remote_host_branches, - HashSet::from_iter([RemoteHostBranch { + HashSet::from_iter([NomadRef { user: USER.to_string(), host: "host0".to_string(), branch: Branch::str(INITIAL_BRANCH), + + // FIXME(#2): we don't know about the `GitRef` here, so simply extract it from the + // repo. This needs some refactoring that separates nomad ref parsing from + // `Config`. + ref_: remote_host_branches + .iter() + .next() + .map(|hb| hb.ref_.clone()) + .unwrap(), }]), ); @@ -948,7 +963,7 @@ mod test_backend { assert_eq!( host1 .git - .snapshot() + .snapshot(&host1.config) .unwrap() .branches_for_host(&host0.config), vec![Branch::str(INITIAL_BRANCH)] diff --git a/src/main.rs b/src/main.rs index 59abf8fe84..f7c75b4114 100644 --- a/src/main.rs +++ b/src/main.rs @@ -152,7 +152,10 @@ fn main() -> Result<()> { } if matches.subcommand_matches("ls").is_some() { - return command::ls(git); + return match git.read_config()? { + None => bail!("No configuration found, nothing to prune"), + Some(config) => command::ls(git, &config), + }; } if let Some(matches) = matches.subcommand_matches("prune") {