node: Delete unsigned refs on fetch
This commit is contained in:
parent
e13bf0b75b
commit
2598886018
|
|
@ -205,13 +205,16 @@ fn test_replication_no_delegates() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let alice = Node::init(tmp.path());
|
let alice = Node::init(tmp.path());
|
||||||
let mut bob = Node::init(tmp.path());
|
let mut bob = Node::init(tmp.path());
|
||||||
let (repo, _) = fixtures::repository(tmp.path().join("acme"));
|
|
||||||
|
|
||||||
let acme = bob.project_from("acme", "", &repo);
|
let acme = bob.project("acme", "");
|
||||||
|
// Delete one of the signed refs.
|
||||||
// Populate repo, but don't sign the refs.
|
bob.storage
|
||||||
let branches = fixtures::populate(&repo, 1);
|
.repository_mut(acme)
|
||||||
git::push(&repo, "rad", branches.iter().map(|b| (b, b))).unwrap();
|
.unwrap()
|
||||||
|
.reference(&bob.id, &git::qualified!("refs/heads/master"))
|
||||||
|
.unwrap()
|
||||||
|
.delete()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut alice = alice.spawn(service::Config::default());
|
let mut alice = alice.spawn(service::Config::default());
|
||||||
let bob = bob.spawn(service::Config::default());
|
let bob = bob.spawn(service::Config::default());
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,8 @@ enum VerifiedRemote {
|
||||||
// Nb. unused but we want to ensure that we verify the identity
|
// Nb. unused but we want to ensure that we verify the identity
|
||||||
_doc: Doc<Verified>,
|
_doc: Doc<Verified>,
|
||||||
remote: Remote<Verified>,
|
remote: Remote<Verified>,
|
||||||
|
/// Unsigned refs.
|
||||||
|
unsigned: Vec<git::RefString>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,6 +253,7 @@ impl<'a> StagingPhaseFinal<'a> {
|
||||||
let url = url::File::new(self.repo.path().to_path_buf()).to_string();
|
let url = url::File::new(self.repo.path().to_path_buf()).to_string();
|
||||||
let mut remote = production.backend.remote_anonymous(&url)?;
|
let mut remote = production.backend.remote_anonymous(&url)?;
|
||||||
let mut updates = Vec::new();
|
let mut updates = Vec::new();
|
||||||
|
let mut delete = HashSet::new();
|
||||||
|
|
||||||
let callbacks = ref_updates(&mut updates);
|
let callbacks = ref_updates(&mut updates);
|
||||||
let remotes = {
|
let remotes = {
|
||||||
|
|
@ -266,10 +269,15 @@ impl<'a> StagingPhaseFinal<'a> {
|
||||||
);
|
);
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
VerifiedRemote::Success { remote, .. } => {
|
VerifiedRemote::Success {
|
||||||
|
remote, unsigned, ..
|
||||||
|
} => {
|
||||||
let ns = remote.id.to_namespace();
|
let ns = remote.id.to_namespace();
|
||||||
let mut refspecs = vec![];
|
let mut refspecs = vec![];
|
||||||
|
|
||||||
|
// Unsigned refs should be deleted.
|
||||||
|
delete.insert((remote.id, unsigned));
|
||||||
|
|
||||||
// First add the standard git refs.
|
// First add the standard git refs.
|
||||||
let heads = ns.join(git::refname!("refs/heads"));
|
let heads = ns.join(git::refname!("refs/heads"));
|
||||||
let cobs = ns.join(git::refname!("refs/cobs"));
|
let cobs = ns.join(git::refname!("refs/cobs"));
|
||||||
|
|
@ -338,8 +346,22 @@ impl<'a> StagingPhaseFinal<'a> {
|
||||||
// should be devised.
|
// should be devised.
|
||||||
opts.prune(git::raw::FetchPrune::Off);
|
opts.prune(git::raw::FetchPrune::Off);
|
||||||
|
|
||||||
|
// Fetch into production copy.
|
||||||
remote.fetch(&specs, Some(&mut opts), None)?;
|
remote.fetch(&specs, Some(&mut opts), None)?;
|
||||||
|
|
||||||
|
// Delete unsigned refs.
|
||||||
|
for (namespace, unsigned) in delete {
|
||||||
|
for refstr in unsigned {
|
||||||
|
let q = git::Qualified::from_refstr(&refstr)
|
||||||
|
.expect("StagingPhaseFinal::transfer: unsigned references are qualified");
|
||||||
|
|
||||||
|
if let Ok(mut r) = production.reference(&namespace, &q) {
|
||||||
|
log::debug!(target: "worker", "Deleting unsigned ref {namespace}/{q}..");
|
||||||
|
|
||||||
|
r.delete()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fetching
|
fetching
|
||||||
};
|
};
|
||||||
let head = production.set_head()?;
|
let head = production.set_head()?;
|
||||||
|
|
@ -363,7 +385,11 @@ impl<'a> StagingPhaseFinal<'a> {
|
||||||
let remote_id = remote.id;
|
let remote_id = remote.id;
|
||||||
let verification = match self.repo.identity_doc_of(&remote_id) {
|
let verification = match self.repo.identity_doc_of(&remote_id) {
|
||||||
Ok(doc) => match self.repo.validate_remote(&remote) {
|
Ok(doc) => match self.repo.validate_remote(&remote) {
|
||||||
Ok(()) => VerifiedRemote::Success { _doc: doc, remote },
|
Ok(unsigned) => VerifiedRemote::Success {
|
||||||
|
_doc: doc,
|
||||||
|
remote,
|
||||||
|
unsigned,
|
||||||
|
},
|
||||||
Err(e) => VerifiedRemote::Failed {
|
Err(e) => VerifiedRemote::Failed {
|
||||||
reason: e.to_string(),
|
reason: e.to_string(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -304,10 +304,11 @@ pub trait ReadRepository {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate all the remote's references in the repository, checking that they are signed as
|
/// Validates a remote's signed refs and identity.
|
||||||
/// part of the remote's signed refs. Also verify that no signed reference is missing from the
|
///
|
||||||
/// repository.
|
/// Returns any ref found under that remote that isn't signed.
|
||||||
fn validate_remote(&self, remote: &Remote<Verified>) -> Result<(), VerifyError>;
|
/// If a signed ref is missing from the repository, an error is returned.
|
||||||
|
fn validate_remote(&self, remote: &Remote<Verified>) -> Result<Vec<RefString>, VerifyError>;
|
||||||
|
|
||||||
/// Get the head of this repository.
|
/// Get the head of this repository.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -202,8 +202,6 @@ pub enum VerifyError {
|
||||||
InvalidIdentity(#[from] IdentityError),
|
InvalidIdentity(#[from] IdentityError),
|
||||||
#[error("refs error: {0}")]
|
#[error("refs error: {0}")]
|
||||||
Refs(#[from] refs::Error),
|
Refs(#[from] refs::Error),
|
||||||
#[error("unknown reference `{1}` in remote `{0}`")]
|
|
||||||
UnknownRef(RemoteId, git::RefString),
|
|
||||||
#[error("missing reference `{1}` in remote `{0}`")]
|
#[error("missing reference `{1}` in remote `{0}`")]
|
||||||
MissingRef(RemoteId, git::RefString),
|
MissingRef(RemoteId, git::RefString),
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
|
|
@ -371,9 +369,10 @@ impl ReadRepository for Repository {
|
||||||
.get(&self.backend)
|
.get(&self.backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_remote(&self, remote: &Remote<Verified>) -> Result<(), VerifyError> {
|
fn validate_remote(&self, remote: &Remote<Verified>) -> Result<Vec<RefString>, VerifyError> {
|
||||||
// Contains a copy of the signed refs of this remote.
|
// Contains a copy of the signed refs of this remote.
|
||||||
let mut refs = BTreeMap::from((*remote.refs).clone());
|
let mut signed = BTreeMap::from((*remote.refs).clone());
|
||||||
|
let mut unsigned = Vec::new();
|
||||||
|
|
||||||
// Check all repository references, making sure they are present in the signed refs map.
|
// Check all repository references, making sure they are present in the signed refs map.
|
||||||
for (refname, oid) in self.references_of(&remote.id)? {
|
for (refname, oid) in self.references_of(&remote.id)? {
|
||||||
|
|
@ -381,24 +380,24 @@ impl ReadRepository for Repository {
|
||||||
if refname == refs::SIGREFS_BRANCH.to_ref_string() {
|
if refname == refs::SIGREFS_BRANCH.to_ref_string() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let signed_oid = refs
|
if let Some(signed_oid) = signed.remove(&refname) {
|
||||||
.remove(&refname)
|
if oid != signed_oid {
|
||||||
.ok_or_else(|| VerifyError::UnknownRef(remote.id, refname.clone()))?;
|
return Err(VerifyError::InvalidRefTarget(remote.id, refname, *oid));
|
||||||
|
}
|
||||||
if oid != signed_oid {
|
} else {
|
||||||
return Err(VerifyError::InvalidRefTarget(remote.id, refname, *oid));
|
unsigned.push(refname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The refs that are left in the map, are ones that were signed, but are not
|
// The refs that are left in the map, are ones that were signed, but are not
|
||||||
// in the repository. If any are left, bail.
|
// in the repository. If any are left, bail.
|
||||||
if let Some((name, _)) = refs.into_iter().next() {
|
if let Some((name, _)) = signed.into_iter().next() {
|
||||||
return Err(VerifyError::MissingRef(remote.id, name));
|
return Err(VerifyError::MissingRef(remote.id, name));
|
||||||
}
|
}
|
||||||
// Finally, verify the identity history of remote.
|
// Finally, verify the identity history of remote.
|
||||||
self.identity_of(&remote.id)?.verified(self.id)?;
|
self.identity_of(&remote.id)?.verified(self.id)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(unsigned)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reference(
|
fn reference(
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,11 @@ impl ReadRepository for MockRepository {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_remote(&self, _remote: &Remote<Verified>) -> Result<(), VerifyError> {
|
fn validate_remote(
|
||||||
Ok(())
|
&self,
|
||||||
|
_remote: &Remote<Verified>,
|
||||||
|
) -> Result<Vec<fmt::RefString>, VerifyError> {
|
||||||
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&self) -> &std::path::Path {
|
fn path(&self) -> &std::path::Path {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue