mod common;
use std::path::Path;
use common::bitmap::claim_everything;
use common::{assert_success, git};
const OWNER: &str = "did:web:owner.example";
const MASTER_KEY_ENV: &str = "KNOT_STATIC_IMPORT_MASTER_KEY";
const MASTER_KEY: [u8; 32] = [7; 32];
fn import(root: &Path, source: &Path) -> std::process::Output {
let config = root.join("config.toml");
std::fs::write(
&config,
format!(
r#"[knot]
hostname = "knot.import.test"
owner = "{OWNER}"
[repo]
scan_path = "{}"
default_branch = "main"
object_format = "sha1"
[secrets]
sealed_key_file = "{}"
master_key_env = "{MASTER_KEY_ENV}"
[atproto]
plc_directory = "https://127.0.0.1:9"
[output]
root = "{}"
"#,
root.join("state").display(),
root.join("keys.sealed").display(),
root.join("output").display(),
),
)
.unwrap();
let knot_static =
|args: &[&str]| common::knot_static_raw(&config, MASTER_KEY_ENV, &MASTER_KEY, args, &[]);
assert_success(&knot_static(&["init"]), "init");
knot_static(&[
"repo",
"import",
"--owner",
OWNER,
"--rkey",
"history",
source.to_str().unwrap(),
])
}
fn source(root: &Path) -> (std::path::PathBuf, String) {
let source = root.join("source");
git(root, ["init", "-q", "-b", "main", source.to_str().unwrap()]);
for text in ["one", "two"] {
common::commit(&source, "a.txt", text, text);
}
let first = git(&source, ["rev-parse", "HEAD~1"]);
(source, first)
}
type Arm = fn(&Path, &str);
#[test]
fn a_shallow_grafted_or_replaced_source_is_refused() {
let cases: [(&str, Arm); 3] = [
("shallow clone", |source, first| {
std::fs::write(source.join(".git/shallow"), format!("{first}\n")).unwrap();
}),
("info/grafts", |source, first| {
std::fs::write(source.join(".git/info/grafts"), format!("{first}\n")).unwrap();
}),
("replace refs", |source, first| {
git(source, ["replace", first, "HEAD"]);
}),
];
for (refusal, arm) in cases {
let root = tempfile::tempdir().unwrap();
let (source, first) = source(root.path());
arm(&source, &first);
let imported = import(root.path(), &source);
let stderr = String::from_utf8_lossy(&imported.stderr);
assert!(
!imported.status.success() && stderr.contains(refusal),
"{refusal}: {stderr}"
);
assert!(
!stderr.contains("refusing to resolve"),
"{refusal} got as far as plc: {stderr}"
);
}
let root = tempfile::tempdir().unwrap();
let (source, _) = source(root.path());
let stderr = String::from_utf8_lossy(&import(root.path(), &source).stderr).into_owned();
assert!(
stderr.contains("refusing to resolve"),
"a plain source reaches plc: {stderr}"
);
}
/// git trusts bitmaps, which means one naming a hidden ref's blob under main would publish it.
#[test]
fn a_sources_bitmap_is_dropped_on_import() {
let bitmaps: [&[&str]; 2] = [
&["repack", "-adq", "--window=0", "--write-bitmap-index"],
&["multi-pack-index", "write", "--bitmap"],
];
for written in bitmaps {
let root = tempfile::tempdir().unwrap();
let (source, _) = source(root.path());
git(&source, ["checkout", "-q", "-b", "secret"]);
common::commit(&source, "secret.txt", "hidden", "hidden");
git(&source, ["update-ref", "refs/hidden/x", "HEAD"]);
let secret = git(&source, ["rev-parse", "HEAD:secret.txt"]);
git(&source, ["checkout", "-q", "main"]);
git(&source, ["branch", "-q", "-D", "secret"]);
git(&source, ["repack", "-adq", "--window=0"]);
git(&source, written);
let bitmap = std::fs::read_dir(source.join(".git/objects/pack"))
.unwrap()
.map(|entry| entry.unwrap().path())
.find(|path| path.extension().is_some_and(|ext| ext == "bitmap"))
.unwrap();
claim_everything(&bitmap);
let marked = git(
&source,
[
"rev-list",
"--objects",
"--no-object-names",
"--use-bitmap-index",
"main",
],
);
assert!(
marked.contains(&secret),
"{written:?}: the planted bitmap lies"
);
import(root.path(), &source);
let config = root.path().join("config.toml");
common::knot_static(&config, MASTER_KEY_ENV, &MASTER_KEY, ["generate"]);
let mut exports = Vec::new();
for repo in std::fs::read_dir(root.path().join("output/repos")).unwrap() {
let releases = repo.unwrap().path().join("releases");
for release in std::fs::read_dir(releases).into_iter().flatten() {
exports.push(release.unwrap().path().join("export"));
}
}
assert!(!exports.is_empty());
for export in exports {
let held = common::git_raw(&export, ["cat-file", "-e", &secret]);
assert!(!held.status.success(), "{written:?}: the export holds it");
git(&export, ["cat-file", "-e", "main"]);
}
}
}