mod check;
mod commit;
mod fetch;
pub mod format;
#[cfg(feature = "knit")]
mod knit_repo;
mod pack_2a;
mod pack_2a_writer;
mod pack_collection;
#[cfg(feature = "knitpack")]
mod pack_index;
#[cfg(feature = "knitpack")]
mod pack_knit;
mod tree;
#[cfg(feature = "weave")]
mod weave_repo;
pub use check::{check, CheckResult};
pub use commit::CommitBuilder;
pub use fetch::fetch;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct ReconcileResult {
pub garbage_inventories: usize,
pub repacked: bool,
}
pub use format::{all_formats, find_format, RepositoryFormat};
#[cfg(feature = "knit")]
pub use knit_repo::KnitRepository;
pub use pack_2a::{Pack2aRepository, RepositoryError, SharedTransport};
#[cfg(feature = "knitpack")]
pub use pack_knit::KnitPackRepository;
pub use tree::RevisionTree;
#[cfg(feature = "weave")]
pub use weave_repo::WeaveRepository;
use crate::inventory::Inventory;
pub trait Repository: Send + Sync {
fn format(&self) -> &'static RepositoryFormat;
fn as_any(&self) -> &dyn std::any::Any;
fn try_fetch_from(
&mut self,
_source: &dyn Repository,
_revision_ids: &[Vec<u8>],
) -> Result<bool, RepositoryError> {
Ok(false)
}
fn all_revision_ids(&self) -> Result<Vec<Vec<u8>>, RepositoryError>;
fn get_parent_map(
&self,
revision_ids: &[Vec<u8>],
) -> Result<std::collections::HashMap<Vec<u8>, Vec<Vec<u8>>>, RepositoryError>;
fn has_revision(&self, revision_id: &[u8]) -> Result<bool, RepositoryError> {
Ok(self
.get_parent_map(std::slice::from_ref(&revision_id.to_vec()))?
.contains_key(revision_id))
}
fn get_revision(
&self,
revision_id: &[u8],
) -> Result<crate::revision::Revision, RepositoryError>;
fn get_inventory(&self, revision_id: &[u8]) -> Result<Box<dyn Inventory>, RepositoryError>;
fn revision_tree(&self, revision_id: &[u8]) -> Result<RevisionTree, RepositoryError> {
if revision_id == crate::branch::NULL_REVISION {
let empty = crate::inventory::MutableInventory::new();
return Ok(RevisionTree::new(revision_id.to_vec(), Box::new(empty)));
}
let inventory = self.get_inventory(revision_id)?;
Ok(RevisionTree::new(revision_id.to_vec(), inventory))
}
fn get_file_text(&self, file_id: &[u8], revision: &[u8]) -> Result<Vec<u8>, RepositoryError>;
fn get_file_text_at_path(
&self,
path: &str,
revision: &[u8],
) -> Result<Vec<u8>, RepositoryError> {
let tree = self.revision_tree(revision)?;
let file_id = tree
.path2id(path)
.ok_or_else(|| RepositoryError::NoSuchRevision(path.as_bytes().to_vec()))?;
self.get_file_text(file_id.as_bytes(), revision)
}
fn start_write_group(&mut self) -> Result<(), RepositoryError>;
fn add_revision(
&mut self,
revision: &crate::revision::Revision,
parents: &[Vec<u8>],
) -> Result<(), RepositoryError>;
fn add_inventory_from_entries(
&mut self,
revision_id: &[u8],
parents: &[Vec<u8>],
root_id: &[u8],
entries: &[crate::inventory::Entry],
) -> Result<Vec<u8>, RepositoryError>;
fn add_inventory_by_delta(
&mut self,
basis_revision_id: &[u8],
delta: &crate::inventory_delta::InventoryDelta,
new_revision_id: &[u8],
parents: &[Vec<u8>],
) -> Result<Vec<u8>, RepositoryError>;
fn add_text(
&mut self,
file_id: &[u8],
revision: &[u8],
parents: &[(Vec<u8>, Vec<u8>)],
bytes: &[u8],
) -> Result<(), RepositoryError>;
fn add_signature_text(
&mut self,
revision_id: &[u8],
signature: &[u8],
) -> Result<(), RepositoryError>;
fn get_signature_text(&self, revision_id: &[u8]) -> Result<Option<Vec<u8>>, RepositoryError>;
fn commit_write_group(&mut self) -> Result<(), RepositoryError>;
fn pack(&mut self) -> Result<(), RepositoryError> {
Ok(())
}
fn autopack(&mut self) -> Result<bool, RepositoryError> {
Ok(false)
}
fn check(&self) -> Result<CheckResult, RepositoryError> {
check::check(self)
}
fn reconcile(&mut self) -> Result<ReconcileResult, RepositoryError> {
Ok(ReconcileResult::default())
}
fn add_fallback_repository(
&mut self,
_fallback: Box<dyn Repository>,
) -> Result<(), RepositoryError> {
Err(RepositoryError::UnsupportedFormat(
"repository does not support fallbacks",
))
}
#[cfg(feature = "gpg")]
fn verify_revision_signature(
&self,
revision_id: &[u8],
certs: &[sequoia_openpgp::Cert],
) -> Result<crate::gpg::VerificationResult, RepositoryError> {
use crate::gpg::VerificationResult;
let Some(signature) = self.get_signature_text(revision_id)? else {
return Ok(VerificationResult::NotSigned);
};
let expected = testament_short_text_for_revision(self, revision_id)?;
let verification = crate::gpg::verify_clearsigned(&signature, certs);
if verification.plaintext.as_deref() != Some(expected.as_slice()) {
return Ok(VerificationResult::NotValid);
}
Ok(verification.result)
}
#[cfg(feature = "gpg")]
fn verify_revision_signature_bytes(
&self,
revision_id: &[u8],
keyring: &[Vec<u8>],
) -> Result<crate::gpg::VerificationResult, RepositoryError> {
let certs = crate::gpg::parse_keyring(keyring)
.map_err(|e| RepositoryError::Corrupt(format!("keyring: {e}")))?;
self.verify_revision_signature(revision_id, &certs)
}
}
#[cfg(feature = "gpg")]
fn testament_short_text_for_revision(
repo: &(impl Repository + ?Sized),
revision_id: &[u8],
) -> Result<Vec<u8>, RepositoryError> {
use crate::testament::{EntryKind as TKind, Testament, TestamentEntry, TestamentFormat};
let revision = repo.get_revision(revision_id)?;
let tree = repo.revision_tree(revision_id)?;
let mut entries = Vec::new();
for (path, entry) in tree.iter_entries() {
if path.is_empty() || path == "." {
continue;
}
let (kind, content) = match entry.kind() {
crate::osutils::Kind::File => (
TKind::File,
entry.text_sha1().map(|s| s.to_vec()).unwrap_or_default(),
),
crate::osutils::Kind::Directory => (TKind::Directory, Vec::new()),
crate::osutils::Kind::Symlink => (
TKind::Symlink,
entry
.symlink_target()
.map(|t| t.as_bytes().to_vec())
.unwrap_or_default(),
),
crate::osutils::Kind::TreeReference => (TKind::TreeReference, Vec::new()),
};
entries.push(TestamentEntry {
path,
kind,
file_id: entry.file_id().as_bytes().to_vec(),
content,
revision: entry
.revision()
.map(|r| r.as_bytes().to_vec())
.unwrap_or_default(),
executable: entry.executable(),
});
}
let testament = Testament {
revision_id: revision_id.to_vec(),
committer: revision.committer.clone().unwrap_or_default(),
timestamp: revision.timestamp as i64,
timezone: revision.timezone.unwrap_or(0),
message: revision.message.clone(),
parent_ids: revision
.parent_ids
.iter()
.map(|p| p.as_bytes().to_vec())
.collect(),
revprops: revision
.properties
.iter()
.map(|(k, v)| (k.clone(), String::from_utf8_lossy(v).into_owned()))
.collect(),
entries,
};
testament
.as_short_text(TestamentFormat::V1)
.map_err(|e| RepositoryError::Corrupt(format!("testament: {e}")))
}
pub struct StackedRepository {
primary: Box<dyn Repository>,
fallbacks: Vec<Box<dyn Repository>>,
}
fn is_not_present(e: &RepositoryError) -> bool {
matches!(
e,
RepositoryError::NoSuchRevision(_) | RepositoryError::NoSuchFileText { .. }
)
}
impl StackedRepository {
pub fn new(primary: Box<dyn Repository>) -> Self {
StackedRepository {
primary,
fallbacks: Vec::new(),
}
}
fn first_present<T>(
&self,
mut f: impl FnMut(&dyn Repository) -> Result<T, RepositoryError>,
) -> Result<T, RepositoryError> {
match f(self.primary.as_ref()) {
Err(e) if is_not_present(&e) => {
for fallback in &self.fallbacks {
match f(fallback.as_ref()) {
Err(e) if is_not_present(&e) => continue,
other => return other,
}
}
Err(e)
}
other => other,
}
}
}
impl Repository for StackedRepository {
fn format(&self) -> &'static RepositoryFormat {
self.primary.format()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn all_revision_ids(&self) -> Result<Vec<Vec<u8>>, RepositoryError> {
self.primary.all_revision_ids()
}
fn get_parent_map(
&self,
revision_ids: &[Vec<u8>],
) -> Result<std::collections::HashMap<Vec<u8>, Vec<Vec<u8>>>, RepositoryError> {
let mut map = self.primary.get_parent_map(revision_ids)?;
let mut missing: Vec<Vec<u8>> = revision_ids
.iter()
.filter(|id| !map.contains_key(*id))
.cloned()
.collect();
for fallback in &self.fallbacks {
if missing.is_empty() {
break;
}
let found = fallback.get_parent_map(&missing)?;
missing.retain(|id| !found.contains_key(id));
map.extend(found);
}
Ok(map)
}
fn has_revision(&self, revision_id: &[u8]) -> Result<bool, RepositoryError> {
if self.primary.has_revision(revision_id)? {
return Ok(true);
}
for fallback in &self.fallbacks {
if fallback.has_revision(revision_id)? {
return Ok(true);
}
}
Ok(false)
}
fn get_revision(
&self,
revision_id: &[u8],
) -> Result<crate::revision::Revision, RepositoryError> {
self.first_present(|r| r.get_revision(revision_id))
}
fn get_inventory(&self, revision_id: &[u8]) -> Result<Box<dyn Inventory>, RepositoryError> {
self.first_present(|r| r.get_inventory(revision_id))
}
fn get_file_text(&self, file_id: &[u8], revision: &[u8]) -> Result<Vec<u8>, RepositoryError> {
self.first_present(|r| r.get_file_text(file_id, revision))
}
fn start_write_group(&mut self) -> Result<(), RepositoryError> {
self.primary.start_write_group()
}
fn add_revision(
&mut self,
revision: &crate::revision::Revision,
parents: &[Vec<u8>],
) -> Result<(), RepositoryError> {
self.primary.add_revision(revision, parents)
}
fn add_inventory_from_entries(
&mut self,
revision_id: &[u8],
parents: &[Vec<u8>],
root_id: &[u8],
entries: &[crate::inventory::Entry],
) -> Result<Vec<u8>, RepositoryError> {
self.primary
.add_inventory_from_entries(revision_id, parents, root_id, entries)
}
fn add_inventory_by_delta(
&mut self,
basis_revision_id: &[u8],
delta: &crate::inventory_delta::InventoryDelta,
new_revision_id: &[u8],
parents: &[Vec<u8>],
) -> Result<Vec<u8>, RepositoryError> {
self.primary
.add_inventory_by_delta(basis_revision_id, delta, new_revision_id, parents)
}
fn add_text(
&mut self,
file_id: &[u8],
revision: &[u8],
parents: &[(Vec<u8>, Vec<u8>)],
bytes: &[u8],
) -> Result<(), RepositoryError> {
self.primary.add_text(file_id, revision, parents, bytes)
}
fn add_signature_text(
&mut self,
revision_id: &[u8],
signature: &[u8],
) -> Result<(), RepositoryError> {
self.primary.add_signature_text(revision_id, signature)
}
fn get_signature_text(&self, revision_id: &[u8]) -> Result<Option<Vec<u8>>, RepositoryError> {
match self.primary.get_signature_text(revision_id)? {
Some(sig) => Ok(Some(sig)),
None => {
for fallback in &self.fallbacks {
if let Some(sig) = fallback.get_signature_text(revision_id)? {
return Ok(Some(sig));
}
}
Ok(None)
}
}
}
fn commit_write_group(&mut self) -> Result<(), RepositoryError> {
self.primary.commit_write_group()
}
fn add_fallback_repository(
&mut self,
fallback: Box<dyn Repository>,
) -> Result<(), RepositoryError> {
self.fallbacks.push(fallback);
Ok(())
}
}
pub(crate) fn unkey_knit_parent_map(
raw: std::collections::HashMap<crate::knit::KnitKey, Vec<crate::knit::KnitKey>>,
) -> std::collections::HashMap<Vec<u8>, Vec<Vec<u8>>> {
let mut out = std::collections::HashMap::with_capacity(raw.len());
for (key, parents) in raw {
if let Some(revid) = key.into_iter().next() {
let parent_ids = parents
.into_iter()
.filter_map(|p| p.into_iter().next())
.collect();
out.insert(revid, parent_ids);
}
}
out
}
impl dyn Repository + '_ {
pub fn get_commit_builder(
&mut self,
parents: Vec<Vec<u8>>,
new_revision_id: Vec<u8>,
committer: String,
timestamp: u64,
timezone: i32,
) -> CommitBuilder<'_> {
CommitBuilder::new(
self,
parents,
new_revision_id,
committer,
timestamp,
timezone,
)
}
}
pub fn open(transport: SharedTransport) -> Result<Box<dyn Repository>, RepositoryError> {
let marker = transport.get_bytes("format")?;
let format =
find_format(&marker).ok_or_else(|| RepositoryError::UnknownFormat(marker.clone()))?;
(format.open)(transport)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::LocalTransport;
use std::sync::Arc;
struct Scenario {
label: &'static str,
create: fn(SharedTransport) -> Box<dyn Repository>,
reopen: fn(SharedTransport) -> Box<dyn Repository>,
signs: bool,
}
#[cfg(feature = "knitpack")]
fn knitpack6() -> &'static RepositoryFormat {
find_format(b"Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n").unwrap()
}
#[cfg(feature = "knit")]
fn knit1() -> &'static RepositoryFormat {
find_format(b"Bazaar-NG Knit Repository Format 1").unwrap()
}
#[cfg(feature = "weave")]
fn weave6() -> &'static RepositoryFormat {
find_format(b"Bazaar-NG branch, format 6\n").unwrap()
}
fn scenarios() -> Vec<Scenario> {
vec![
Scenario {
label: "2a",
create: |t| Box::new(Pack2aRepository::create(t).unwrap()),
reopen: |t| Box::new(Pack2aRepository::open(t).unwrap()),
signs: true,
},
#[cfg(feature = "knitpack")]
Scenario {
label: "knit-pack",
create: |t| Box::new(KnitPackRepository::create(t, knitpack6()).unwrap()),
reopen: |t| Box::new(KnitPackRepository::open(t).unwrap()),
signs: true,
},
#[cfg(feature = "knit")]
Scenario {
label: "knit",
create: |t| Box::new(KnitRepository::create(t, knit1()).unwrap()),
reopen: |t| Box::new(KnitRepository::open(t).unwrap()),
signs: true,
},
#[cfg(feature = "weave")]
Scenario {
label: "weave",
create: |t| Box::new(WeaveRepository::create(t, weave6()).unwrap()),
reopen: |t| Box::new(WeaveRepository::open(t, weave6()).unwrap()),
signs: true,
},
]
}
fn revision(id: &[u8], parents: Vec<&[u8]>, message: &str) -> crate::revision::Revision {
crate::revision::Revision::new(
crate::RevisionId::from(id),
parents.into_iter().map(crate::RevisionId::from).collect(),
Some("T <t@e>".to_string()),
message.to_string(),
std::collections::HashMap::new(),
None,
1577880000.0,
Some(0),
)
}
fn entries(rev: &[u8]) -> Vec<crate::inventory::Entry> {
use crate::FileId;
let root = crate::inventory::ROOT_ID;
vec![
crate::inventory::Entry::root(FileId::from(root), Some(crate::RevisionId::from(rev))),
crate::inventory::Entry::file(
FileId::from(&b"file-1"[..]),
"a.txt".into(),
FileId::from(root),
Some(crate::RevisionId::from(rev)),
Some(crate::weave::sha_strings(&[b"hello\n"])),
Some(6),
Some(false),
None,
),
]
}
#[test]
fn revision_text_inventory_signature_round_trip() {
for s in scenarios() {
let dir = tempfile::tempdir().unwrap();
let t: SharedTransport = Arc::new(LocalTransport::new(dir.path()));
let mut repo = (s.create)(t.clone());
repo.start_write_group().unwrap();
repo.add_revision(&revision(b"rev-1", vec![], "first"), &[])
.unwrap();
repo.add_inventory_from_entries(
b"rev-1",
&[],
crate::inventory::ROOT_ID,
&entries(b"rev-1"),
)
.unwrap();
repo.add_text(b"file-1", b"rev-1", &[], b"hello\n").unwrap();
repo.add_text(
b"file-1",
b"rev-2",
&[(b"file-1".to_vec(), b"rev-1".to_vec())],
b"hello\ngoodbye\n",
)
.unwrap();
repo.add_revision(
&revision(b"rev-2", vec![b"rev-1"], "second"),
&[b"rev-1".to_vec()],
)
.unwrap();
if s.signs {
repo.add_signature_text(b"rev-1", b"-----SIG-----\nsigned\n")
.unwrap();
}
repo.commit_write_group().unwrap();
let repo = (s.reopen)(t);
let mut ids = repo.all_revision_ids().unwrap();
ids.sort();
assert_eq!(
ids,
vec![b"rev-1".to_vec(), b"rev-2".to_vec()],
"{}",
s.label
);
let pm = repo
.get_parent_map(&[b"rev-1".to_vec(), b"rev-2".to_vec(), b"nope".to_vec()])
.unwrap();
assert_eq!(pm.get(&b"rev-1".to_vec()), Some(&vec![]), "{}", s.label);
assert_eq!(
pm.get(&b"rev-2".to_vec()),
Some(&vec![b"rev-1".to_vec()]),
"{}",
s.label
);
assert!(!pm.contains_key(&b"nope".to_vec()), "{}", s.label);
assert!(repo.has_revision(b"rev-2").unwrap(), "{}", s.label);
assert!(!repo.has_revision(b"nope").unwrap(), "{}", s.label);
assert_eq!(
repo.get_revision(b"rev-1").unwrap().message,
"first",
"{}",
s.label
);
let got2 = repo.get_revision(b"rev-2").unwrap();
assert_eq!(got2.message, "second", "{}", s.label);
assert_eq!(
got2.parent_ids
.iter()
.map(|p| p.as_bytes().to_vec())
.collect::<Vec<_>>(),
vec![b"rev-1".to_vec()],
"{}",
s.label
);
assert_eq!(
repo.get_file_text(b"file-1", b"rev-1").unwrap(),
b"hello\n",
"{}",
s.label
);
assert_eq!(
repo.get_file_text(b"file-1", b"rev-2").unwrap(),
b"hello\ngoodbye\n",
"{}",
s.label
);
let inv = repo.get_inventory(b"rev-1").unwrap();
let paths: Vec<String> = inv.entries().unwrap().into_iter().map(|(p, _)| p).collect();
assert_eq!(paths, vec!["a.txt".to_string()], "{}", s.label);
let tree = repo.revision_tree(b"rev-1").unwrap();
assert_eq!(
tree.path2id("a.txt").map(|f| f.as_bytes().to_vec()),
Some(b"file-1".to_vec()),
"{}",
s.label
);
assert!(tree.path2id("nope").is_none(), "{}", s.label);
let tree_paths: Vec<String> = tree.iter_entries().into_iter().map(|(p, _)| p).collect();
assert_eq!(tree_paths, vec!["a.txt".to_string()], "{}", s.label);
assert_eq!(
repo.get_file_text_at_path("a.txt", b"rev-1").unwrap(),
b"hello\n",
"{}",
s.label
);
let expected = if s.signs {
Some(b"-----SIG-----\nsigned\n".to_vec())
} else {
None
};
assert_eq!(
repo.get_signature_text(b"rev-1").unwrap(),
expected,
"{}",
s.label
);
assert_eq!(
repo.get_signature_text(b"rev-2").unwrap(),
None,
"{}",
s.label
);
}
}
fn make_2a_with_rev(dir: &std::path::Path, rev: &[u8]) -> Box<dyn Repository> {
let t: SharedTransport = Arc::new(LocalTransport::new(dir));
let mut repo =
Box::new(Pack2aRepository::create(t.clone()).unwrap()) as Box<dyn Repository>;
repo.start_write_group().unwrap();
repo.add_revision(&revision(rev, vec![], "msg"), &[])
.unwrap();
repo.add_inventory_from_entries(rev, &[], crate::inventory::ROOT_ID, &entries(rev))
.unwrap();
repo.add_text(b"file-1", rev, &[], b"hello\n").unwrap();
repo.commit_write_group().unwrap();
Box::new(Pack2aRepository::open(t).unwrap())
}
#[test]
fn stacked_repository_reads_through_fallback() {
let base_dir = tempfile::tempdir().unwrap();
let base = make_2a_with_rev(base_dir.path(), b"rev-base");
let top_dir = tempfile::tempdir().unwrap();
let top_t: SharedTransport = Arc::new(LocalTransport::new(top_dir.path()));
let primary = Box::new(Pack2aRepository::create(top_t).unwrap()) as Box<dyn Repository>;
let mut stacked = StackedRepository::new(primary);
assert!(!stacked.has_revision(b"rev-base").unwrap());
stacked.add_fallback_repository(base).unwrap();
assert!(stacked.has_revision(b"rev-base").unwrap());
assert_eq!(stacked.get_revision(b"rev-base").unwrap().message, "msg");
assert_eq!(
stacked.get_file_text(b"file-1", b"rev-base").unwrap(),
b"hello\n"
);
let pm = stacked.get_parent_map(&[b"rev-base".to_vec()]).unwrap();
assert_eq!(pm.get(&b"rev-base".to_vec()), Some(&vec![]));
assert!(stacked.all_revision_ids().unwrap().is_empty());
assert!(matches!(
stacked.get_revision(b"rev-missing"),
Err(RepositoryError::NoSuchRevision(_))
));
}
#[test]
fn plain_repository_rejects_fallback() {
let dir = tempfile::tempdir().unwrap();
let t: SharedTransport = Arc::new(LocalTransport::new(dir.path()));
let mut repo = Pack2aRepository::create(t).unwrap();
let other_dir = tempfile::tempdir().unwrap();
let other = make_2a_with_rev(other_dir.path(), b"rev-x");
assert!(matches!(
repo.add_fallback_repository(other),
Err(RepositoryError::UnsupportedFormat(_))
));
}
#[cfg(feature = "gpg")]
fn gen_signing_cert() -> (sequoia_openpgp::Cert, Vec<u8>) {
use sequoia_openpgp::cert::CertBuilder;
use sequoia_openpgp::serialize::Serialize;
let (cert, _) = CertBuilder::new().add_signing_subkey().generate().unwrap();
let mut tsk = Vec::new();
cert.as_tsk().serialize(&mut tsk).unwrap();
(cert, tsk)
}
#[cfg(feature = "gpg")]
#[test]
fn verify_revision_signature_valid_and_unsigned() {
use crate::gpg::VerificationResult;
let dir = tempfile::tempdir().unwrap();
let t: SharedTransport = Arc::new(LocalTransport::new(dir.path()));
let repo = make_2a_with_rev_at(&t, b"rev-1");
assert_eq!(
repo.verify_revision_signature(b"rev-1", &[]).unwrap(),
VerificationResult::NotSigned
);
let (cert, tsk) = gen_signing_cert();
let testament = testament_short_text_for_revision(repo.as_ref(), b"rev-1").unwrap();
let signature = crate::gpg::clearsign(&testament, &tsk).unwrap();
let mut repo = repo;
repo.start_write_group().unwrap();
repo.add_signature_text(b"rev-1", &signature).unwrap();
repo.commit_write_group().unwrap();
let repo = Box::new(Pack2aRepository::open(t.clone()).unwrap()) as Box<dyn Repository>;
assert_eq!(
repo.verify_revision_signature(b"rev-1", std::slice::from_ref(&cert))
.unwrap(),
VerificationResult::Valid
);
}
#[cfg(feature = "gpg")]
#[test]
fn verify_revision_signature_wrong_testament_is_not_valid() {
use crate::gpg::VerificationResult;
let dir = tempfile::tempdir().unwrap();
let t: SharedTransport = Arc::new(LocalTransport::new(dir.path()));
let mut repo = make_2a_with_rev_at(&t, b"rev-1");
let (cert, tsk) = gen_signing_cert();
let signature = crate::gpg::clearsign(b"not the testament\n", &tsk).unwrap();
repo.start_write_group().unwrap();
repo.add_signature_text(b"rev-1", &signature).unwrap();
repo.commit_write_group().unwrap();
let repo = Box::new(Pack2aRepository::open(t.clone()).unwrap()) as Box<dyn Repository>;
assert_eq!(
repo.verify_revision_signature(b"rev-1", std::slice::from_ref(&cert))
.unwrap(),
VerificationResult::NotValid
);
}
#[cfg(feature = "gpg")]
fn make_2a_with_rev_at(t: &SharedTransport, rev: &[u8]) -> Box<dyn Repository> {
let mut repo =
Box::new(Pack2aRepository::create(t.clone()).unwrap()) as Box<dyn Repository>;
repo.start_write_group().unwrap();
repo.add_revision(&revision(rev, vec![], "msg"), &[])
.unwrap();
repo.add_inventory_from_entries(rev, &[], crate::inventory::ROOT_ID, &entries(rev))
.unwrap();
repo.add_text(b"file-1", rev, &[], b"hello\n").unwrap();
repo.commit_write_group().unwrap();
Box::new(Pack2aRepository::open(t.clone()).unwrap())
}
}