diff --git a/radicle-node/src/tests/e2e.rs b/radicle-node/src/tests/e2e.rs index bb6e62e0..3a55f181 100644 --- a/radicle-node/src/tests/e2e.rs +++ b/radicle-node/src/tests/e2e.rs @@ -327,6 +327,52 @@ fn test_fetch_trusted_remotes() { assert!(bob_remotes.contains(&alice.id)); } +#[test] +fn test_fetch_preserve_owned_refs() { + logger::init(log::Level::Debug); + + let tmp = tempfile::tempdir().unwrap(); + let mut alice = Node::init(tmp.path()); + let bob = Node::init(tmp.path()); + let acme = alice.project("acme", ""); + let mut alice = alice.spawn(service::Config::default()); + let mut bob = bob.spawn(service::Config::default()); + + alice.connect(&bob); + converge([&alice, &bob]); + + assert!(bob.handle.track_repo(acme, Scope::Trusted).unwrap()); + assert!(bob.handle.track_node(alice.id, None).unwrap()); + + let result = bob.handle.fetch(acme, alice.id).unwrap(); + assert!(result.is_success()); + + log::debug!(target: "test", "Fetch complete with {}", bob.id); + + alice.issue(acme, "Bug", "Bugs, bugs, bugs"); + + let before = alice + .storage + .repository(acme) + .unwrap() + .references_of(&alice.id) + .unwrap(); + + // Fetch shouldn't prune any of our own refs. + let result = alice.handle.fetch(acme, bob.id).unwrap(); + let updated = result.success().unwrap(); + assert_eq!(updated, vec![]); + + let after = alice + .storage + .repository(acme) + .unwrap() + .references_of(&alice.id) + .unwrap(); + + assert_eq!(before, after); +} + #[test] fn test_clone() { logger::init(log::Level::Debug); diff --git a/radicle-node/src/worker/fetch.rs b/radicle-node/src/worker/fetch.rs index 2b496a56..eeb20c4a 100644 --- a/radicle-node/src/worker/fetch.rs +++ b/radicle-node/src/worker/fetch.rs @@ -69,7 +69,7 @@ pub struct StagingPhaseFinal<'a> { production: &'a Storage, /// The delegates and tracked remotes that the fetch is being /// performed for. These are passed through from the - /// [`StagingPhaseInitial::namespaces`], if the variant is `Many`. + /// [`StagingPhaseInitial::namespaces`], if the variant is `Trusted`. trusted: HashSet, _tmp: tempfile::TempDir, } @@ -141,9 +141,6 @@ impl<'a> StagingPhaseInitial<'a> { StagedRepository::Fetching(repo) => { log::debug!(target: "worker", "Loading remotes for fetching"); match self.namespaces.clone() { - // Nb. Namespaces::One is not constructed in - // namespaces_for so it's safe to just bundle this - // with Namespaces::All Namespaces::All => { let mut trusted = repo.remote_ids()?.collect::, _>>()?; trusted.extend(repo.delegates()?.map(PublicKey::from)); @@ -232,7 +229,7 @@ impl<'a> StagingPhaseFinal<'a> { let url = url::File::new(self.repo.path().to_path_buf()).to_string(); let mut remote = production.backend.remote_anonymous(&url)?; let mut updates = Vec::new(); - log::debug!(target: "worker", "running transfer fetch"); + let callbacks = ref_updates(&mut updates); { let specs = verifications @@ -259,15 +256,23 @@ impl<'a> StagingPhaseFinal<'a> { }) .collect::>(); log::debug!(target: "worker", "Transferring staging to production {url}"); + let mut opts = git::raw::FetchOptions::default(); opts.remote_callbacks(callbacks); - opts.prune(git::raw::FetchPrune::On); + // Nb. To prevent refs owned by the local node from being deleted from the stored + // copy if they are not on the remote side, we turn pruning off. + // However, globally turning off pruning isn't a ideal either, so a better solution + // should be devised. + opts.prune(git::raw::FetchPrune::Off); + remote.fetch(&specs, Some(&mut opts), None)?; } let head = production.set_head()?; log::debug!(target: "worker", "Head for {} set to {head}", production.id); + let head = production.set_identity_head()?; log::debug!(target: "worker", "'refs/rad/id' for {} set to {head}", production.id); + Ok(updates) }