Files
agent-radar 7be535059e
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 19:05:14 +08:00

2197 lines
80 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use std::{
fs,
io::Write,
path::{Path, PathBuf},
process::{Child, Command, Output, Stdio},
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
const HELPER: &str = env!("CARGO_BIN_EXE_maka-gitoxide-helper");
const MAX_REPOSITORY_METADATA_BYTES: usize = 1024 * 1024;
const MAX_REPOSITORY_PACK_DIRECTORY_ENTRIES: usize = 1024;
static FIXTURE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
#[test]
fn inspects_a_sha1_repository_without_invoking_system_git() {
let fixture = RepositoryFixture::sha1_with_commit();
let expected_commit = fixture.git_output(["rev-parse", "HEAD"]);
let expected_tree = fixture.git_output(["rev-parse", "HEAD^{tree}"]);
let output = invoke_helper(&fixture.root);
assert!(
output.status.success(),
"helper failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(
response,
serde_json::json!({
"protocolVersion": 1,
"kind": "repository_inspected",
"objectFormat": "sha1",
"headCommitOid": expected_commit,
"headTreeOid": expected_tree,
})
);
}
#[test]
fn inspects_and_imports_a_bounded_packed_sha1_repository() {
let fixture = RepositoryFixture::sha1_with_commit();
fixture.git(["gc", "--quiet"]);
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let expected_tree = fixture.git_output(["rev-parse", "HEAD^{tree}"]);
let inspection = invoke_helper(&fixture.root);
assert!(
inspection.status.success(),
"helper failed: {}",
String::from_utf8_lossy(&inspection.stderr)
);
let inspected: serde_json::Value = serde_json::from_slice(&inspection.stdout).unwrap();
assert_eq!(inspected["headTreeOid"], expected_tree);
let destination = fixture.root.join("packed-source-destination.git");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert!(
import.status.success(),
"helper failed: {}",
String::from_utf8_lossy(&import.stderr)
);
let imported: serde_json::Value = serde_json::from_slice(&import.stdout).unwrap();
assert_eq!(imported["baselineTreeOid"], expected_tree);
}
#[test]
fn rejects_oversized_repository_metadata_before_opening_or_importing() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("oversized-metadata.git");
let config_path = fixture.root.join(".git").join("config");
let mut config = fs::OpenOptions::new()
.append(true)
.open(config_path)
.unwrap();
config.write_all(b"\n#").unwrap();
config
.write_all(&vec![b'x'; MAX_REPOSITORY_METADATA_BYTES])
.unwrap();
drop(config);
let inspection = invoke_helper(&fixture.root);
assert_helper_error(&inspection, "repository_metadata_limit_exceeded");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "repository_metadata_limit_exceeded");
assert!(!destination.exists());
}
#[test]
fn rejects_nested_alternate_object_databases_before_opening_or_importing() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let first_alternate = fixture.root.join("alternate-one");
let second_alternate = fixture.root.join("alternate-two");
for alternate in [&first_alternate, &second_alternate] {
fs::create_dir_all(alternate.join("info")).unwrap();
fs::create_dir_all(alternate.join("pack")).unwrap();
}
fs::write(
first_alternate.join("info/alternates"),
format!("{}\n", second_alternate.display()),
)
.unwrap();
fs::write(
fixture.root.join(".git/objects/info/alternates"),
format!("{}\n", first_alternate.display()),
)
.unwrap();
let inspection = invoke_helper(&fixture.root);
assert_helper_error(&inspection, "repository_alternates_unsupported");
let destination = fixture.root.join("alternates-destination.git");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "repository_alternates_unsupported");
assert!(!destination.exists());
}
#[test]
fn rejects_excessive_primary_pack_directory_entries_before_opening_or_importing() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let pack_directory = fixture.root.join(".git/objects/pack");
for index in 0..=MAX_REPOSITORY_PACK_DIRECTORY_ENTRIES {
fs::write(
pack_directory.join(format!("untrusted-{index:04}.entry")),
b"",
)
.unwrap();
}
let inspection = invoke_helper(&fixture.root);
assert_helper_error(&inspection, "repository_metadata_limit_exceeded");
let destination = fixture.root.join("pack-entries-destination.git");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "repository_metadata_limit_exceeded");
assert!(!destination.exists());
}
#[test]
fn rejects_oversized_bare_repository_metadata_when_an_invalid_dot_git_child_exists() {
let fixture = RepositoryFixture::sha1_with_commit();
let bare_repository = fixture.root.join("source.git");
let clone = Command::new("git")
.args(["clone", "--quiet", "--bare"])
.arg(&fixture.root)
.arg(&bare_repository)
.output()
.unwrap();
assert!(
clone.status.success(),
"bare fixture clone failed: {}",
String::from_utf8_lossy(&clone.stderr)
);
fs::create_dir(bare_repository.join(".git")).unwrap();
let mut config = fs::OpenOptions::new()
.append(true)
.open(bare_repository.join("config"))
.unwrap();
config.write_all(b"\n#").unwrap();
config
.write_all(&vec![b'x'; MAX_REPOSITORY_METADATA_BYTES])
.unwrap();
drop(config);
let inspection = invoke_helper(&bare_repository);
assert_helper_error(&inspection, "repository_metadata_limit_exceeded");
}
#[test]
fn does_not_count_worktree_user_data_as_bare_repository_metadata() {
let fixture = RepositoryFixture::sha1_with_commit();
let user_refs = fixture.root.join("refs");
fs::create_dir(&user_refs).unwrap();
fs::write(
user_refs.join("application-data.txt"),
vec![b'x'; MAX_REPOSITORY_METADATA_BYTES + 1],
)
.unwrap();
let inspection = invoke_helper(&fixture.root);
assert!(
inspection.status.success(),
"helper failed: stdout={} stderr={}",
String::from_utf8_lossy(&inspection.stdout),
String::from_utf8_lossy(&inspection.stderr)
);
}
#[test]
fn rejects_sha256_before_returning_repository_identity() {
let fixture = RepositoryFixture::sha256_unborn();
let output = invoke_helper(&fixture.root);
assert_eq!(output.status.code(), Some(2));
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(
response,
serde_json::json!({
"protocolVersion": 1,
"kind": "repository_rejected",
"reason": "unsupported_object_format",
"objectFormat": "sha256",
"supportedObjectFormats": ["sha1"],
})
);
}
#[test]
fn rejects_an_unknown_object_format_during_repository_open() {
let fixture = RepositoryFixture::unknown_object_format();
let output = invoke_helper(&fixture.root);
assert_eq!(output.status.code(), Some(2));
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(
response,
serde_json::json!({
"protocolVersion": 1,
"kind": "repository_rejected",
"reason": "unsupported_object_format",
"objectFormat": "unknown",
"supportedObjectFormats": ["sha1"],
})
);
}
#[test]
fn observes_raw_head_identity_instead_of_replacement_ref_semantics() {
let (fixture, expected_commit, expected_tree) = RepositoryFixture::sha1_with_replacement_ref();
let output = invoke_helper(&fixture.root);
assert!(output.status.success());
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(response["headCommitOid"], expected_commit);
assert_eq!(response["headTreeOid"], expected_tree);
}
#[test]
fn rejects_a_head_commit_whose_storage_key_does_not_match_its_bytes() {
let (fixture, claimed_head) = RepositoryFixture::sha1_with_mismatched_head_storage();
let inspection = invoke_helper(&fixture.root);
assert_helper_error(&inspection, "head_commit_identity_mismatch");
let destination = fixture.root.join("mismatched-head.git");
let import = invoke_import(&fixture.root, &claimed_head, &destination);
assert_helper_error(&import, "source_head_commit_identity_mismatch");
assert!(!destination.exists());
}
#[test]
fn rejects_a_source_tree_whose_storage_key_does_not_match_its_bytes() {
let (fixture, claimed_head) = RepositoryFixture::sha1_with_mismatched_tree_storage();
let destination = fixture.root.join("mismatched-tree.git");
let import = invoke_import(&fixture.root, &claimed_head, &destination);
assert_helper_error(&import, "source_tree_identity_mismatch");
assert!(!destination.exists());
}
#[test]
fn rejects_a_source_blob_whose_storage_key_does_not_match_its_bytes() {
let (fixture, claimed_head) = RepositoryFixture::sha1_with_mismatched_blob_storage();
let destination = fixture.root.join("mismatched-blob.git");
let import = invoke_import(&fixture.root, &claimed_head, &destination);
assert_helper_error(&import, "source_blob_identity_mismatch");
assert!(!destination.exists());
}
#[test]
fn rejects_noncanonical_tree_entry_modes_before_claiming_the_destination() {
for mode in ["100600", "100664", "100700", "100777", "040000", "0100644"] {
let (fixture, source_head) = RepositoryFixture::sha1_with_raw_tree(&[RawTreeEntry {
mode,
name: "entry",
object_kind: if mode.ends_with("40000") {
RawObjectKind::Tree
} else {
RawObjectKind::Blob
},
}]);
let destination = fixture.root.join(format!("noncanonical-{mode}.git"));
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "source_tree_noncanonical_mode");
assert!(!destination.exists());
}
}
#[test]
fn rejects_unsorted_tree_entries_before_claiming_the_destination() {
let (fixture, source_head) = RepositoryFixture::sha1_with_raw_tree(&[
RawTreeEntry {
mode: "100644",
name: "z-last",
object_kind: RawObjectKind::Blob,
},
RawTreeEntry {
mode: "100644",
name: "a-first",
object_kind: RawObjectKind::Blob,
},
]);
let destination = fixture.root.join("unsorted-tree.git");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "source_tree_not_sorted");
assert!(!destination.exists());
}
#[test]
fn rejects_full_unicode_casefold_collisions_before_claiming_the_destination() {
let (fixture, source_head) = RepositoryFixture::sha1_with_raw_tree(&[
RawTreeEntry {
mode: "100644",
name: "STRASSE.txt",
object_kind: RawObjectKind::Blob,
},
RawTreeEntry {
mode: "100644",
name: "Straße.txt",
object_kind: RawObjectKind::Blob,
},
]);
let destination = fixture.root.join("unicode-casefold-collision.git");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "source_path_collision");
assert!(!destination.exists());
}
#[test]
fn rejects_protected_names_using_the_policy_v3_unicode_fold() {
let (fixture, source_head) = RepositoryFixture::sha1_with_raw_tree(&[RawTreeEntry {
mode: "100644",
name: ".gitattributeſ",
object_kind: RawObjectKind::Blob,
}]);
let destination = fixture.root.join("unicode-protected-name.git");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "unsupported_source_path");
assert!(!destination.exists());
}
#[test]
fn rejects_git_hfs_protected_name_aliases_before_claiming_the_destination() {
for (index, name) in [
".g\u{200c}it",
".\u{200e}git",
".git\u{feff}",
".gitattr\u{200d}ibutes",
]
.into_iter()
.enumerate()
{
let (fixture, source_head) = RepositoryFixture::sha1_with_raw_tree(&[RawTreeEntry {
mode: "100644",
name,
object_kind: RawObjectKind::Blob,
}]);
let destination = fixture.root.join(format!("hfs-protected-name-{index}.git"));
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "unsupported_source_path");
assert!(!destination.exists());
}
}
#[test]
fn rejects_a_nonportable_raw_git_path_before_claiming_the_destination() {
let (fixture, source_head) = RepositoryFixture::sha1_with_raw_tree(&[RawTreeEntry {
mode: "100644",
name: "com¹.txt",
object_kind: RawObjectKind::Blob,
}]);
let destination = fixture.root.join("nonportable-path.git");
let import = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&import, "unsupported_source_path");
assert!(!destination.exists());
}
#[test]
fn imports_an_exact_source_head_into_a_fresh_managed_repository() {
let fixture = RepositoryFixture::sha1_with_commit();
fs::create_dir_all(fixture.root.join("docs")).unwrap();
fs::write(fixture.root.join("docs/guide.txt"), b"nested guide\n").unwrap();
fixture.git(["add", "docs/guide.txt"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"source import fixture",
]);
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let source_tree = fixture.git_output(["rev-parse", "HEAD^{tree}"]);
let destination = fixture.root.join("managed.git");
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert!(
output.status.success(),
"helper failed: stdout={} stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(response["kind"], "source_imported");
assert_eq!(response["sourceHeadCommitOid"], source_head);
assert_eq!(response["sourceTreeOid"], source_tree);
assert_eq!(response["baselineTreeOid"], source_tree);
assert_eq!(response["managedTreePolicyVersion"], 3);
assert_eq!(response["filesImported"], 2);
assert_eq!(response["bytesImported"], 29);
let baseline_commit = response["baselineCommitOid"].as_str().unwrap();
assert_ne!(baseline_commit, source_head);
assert_eq!(
git_bare_output(&destination, ["rev-parse", "refs/maka/baseline"]),
baseline_commit
);
assert_eq!(
git_bare_output(
&destination,
["rev-parse", &format!("{baseline_commit}^{{tree}}")]
),
source_tree
);
assert!(!git_bare_succeeds(
&destination,
["cat-file", "-e", source_head.as_str()]
));
assert!(!destination.join("objects/info/alternates").exists());
let second_destination = fixture.root.join("managed-second.git");
let second_output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": second_destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert!(
second_output.status.success(),
"second helper import failed: stdout={} stderr={}",
String::from_utf8_lossy(&second_output.stdout),
String::from_utf8_lossy(&second_output.stderr)
);
let second_response: serde_json::Value = serde_json::from_slice(&second_output.stdout).unwrap();
assert_eq!(second_response["baselineCommitOid"], baseline_commit);
let baseline_commit_text = git_bare_output(&destination, ["cat-file", "-p", baseline_commit]);
assert_eq!(
baseline_commit_text
.lines()
.filter(|line| line.starts_with("parent "))
.count(),
0
);
assert!(
baseline_commit_text
.contains("author Maka Workspace Service <workspace@maka.invalid> 946684800 +0000")
);
assert!(
baseline_commit_text
.contains("committer Maka Workspace Service <workspace@maka.invalid> 946684800 +0000")
);
assert!(baseline_commit_text.ends_with("maka managed workspace baseline v2"));
let retry = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&retry, "import_destination_not_fresh");
}
#[test]
fn publishes_and_exactly_retries_an_operation_candidate_without_advancing_accepted() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("successor.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
assert!(imported.status.success());
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let baseline_tree = imported["baselineTreeOid"].as_str().unwrap();
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": "refs/maka/candidates/1111111111111111111111111111111111111111111111111111111111111111",
"path": "docs/result.txt",
"contentBase64": "c3VjY2Vzc29yIGNvbnRlbnQK",
"managedTreePolicyVersion": 3,
});
let first = invoke_request(request.clone());
assert!(
first.status.success(),
"{}",
String::from_utf8_lossy(&first.stdout)
);
let first: serde_json::Value = serde_json::from_slice(&first.stdout).unwrap();
assert_eq!(first["kind"], "candidate_published");
assert_eq!(first["managedTreePolicyVersion"], 3);
assert_eq!(
git_bare_output(&destination, ["rev-parse", "refs/maka/baseline"]),
baseline
);
assert_eq!(
git_bare_output(
&destination,
["rev-parse", first["candidateRef"].as_str().unwrap()]
),
first["candidateCommitOid"].as_str().unwrap()
);
assert_eq!(
git_bare_bytes(
&destination,
[
"show",
&format!(
"{}:docs/result.txt",
first["candidateCommitOid"].as_str().unwrap()
),
],
),
b"successor content\n"
);
let retry = invoke_request(request);
assert!(retry.status.success());
let retry: serde_json::Value = serde_json::from_slice(&retry.stdout).unwrap();
assert_eq!(retry, first);
}
#[test]
fn rejects_an_existing_candidate_receipt_with_an_unrelated_tree_change() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("candidate-extra-change.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let candidate_ref =
"refs/maka/candidates/1414141414141414141414141414141414141414141414141414141414141414";
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": candidate_ref,
"path": "docs/result.txt",
"contentBase64": "ZXhhY3QgcmVzdWx0Cg==",
"managedTreePolicyVersion": 3,
});
let published = invoke_request(request.clone());
assert!(published.status.success());
let published: serde_json::Value = serde_json::from_slice(&published.stdout).unwrap();
let unrelated_blob = git_bare_input_output(
&destination,
["hash-object", "-t", "blob", "-w", "--stdin"],
b"unrelated\n",
);
let mut forged_tree_input = git_bare_bytes(
&destination,
["ls-tree", published["candidateTreeOid"].as_str().unwrap()],
);
forged_tree_input
.extend_from_slice(format!("100644 blob {unrelated_blob}\tunrelated.txt\n").as_bytes());
let forged_tree = git_bare_input_output(&destination, ["mktree"], &forged_tree_input);
replace_candidate_ref_tree(&destination, candidate_ref, &published, &forged_tree);
let retry = invoke_request(request);
assert_helper_error(&retry, "candidate_ref_target_invalid");
}
#[test]
fn rejects_an_existing_candidate_receipt_with_a_target_mode_flip() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("candidate-mode-flip.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let candidate_ref =
"refs/maka/candidates/1515151515151515151515151515151515151515151515151515151515151515";
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": candidate_ref,
"path": "hello.txt",
"contentBase64": "bW9kZS1wcmVzZXJ2aW5nCg==",
"managedTreePolicyVersion": 3,
});
let published = invoke_request(request.clone());
assert!(published.status.success());
let published: serde_json::Value = serde_json::from_slice(&published.stdout).unwrap();
let original_entry = format!(
"100644 blob {}\thello.txt",
published["resultBlobOid"].as_str().unwrap()
);
let candidate_tree = String::from_utf8(git_bare_bytes(
&destination,
["ls-tree", published["candidateTreeOid"].as_str().unwrap()],
))
.unwrap();
assert!(candidate_tree.contains(&original_entry));
let forged_tree_input = candidate_tree.replacen(
&original_entry,
&original_entry.replacen("100644", "100755", 1),
1,
);
let forged_tree = git_bare_input_output(&destination, ["mktree"], forged_tree_input.as_bytes());
replace_candidate_ref_tree(&destination, candidate_ref, &published, &forged_tree);
let retry = invoke_request(request);
assert_helper_error(&retry, "candidate_ref_target_invalid");
}
#[test]
fn rejects_an_exact_candidate_retry_when_the_result_blob_is_missing() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("candidate-missing-result-blob.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": "refs/maka/candidates/1212121212121212121212121212121212121212121212121212121212121212",
"path": "docs/result.txt",
"contentBase64": "bWlzc2luZyBibG9iCg==",
"managedTreePolicyVersion": 3,
});
let published = invoke_request(request.clone());
assert!(published.status.success());
let published: serde_json::Value = serde_json::from_slice(&published.stdout).unwrap();
fs::remove_file(loose_object_path(
&destination,
published["resultBlobOid"].as_str().unwrap(),
))
.unwrap();
let retry = invoke_request(request);
assert_helper_error(&retry, "candidate_ref_target_invalid");
}
#[test]
fn rejects_an_exact_candidate_retry_when_the_result_blob_is_corrupt() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("candidate-corrupt-result-blob.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": "refs/maka/candidates/1313131313131313131313131313131313131313131313131313131313131313",
"path": "docs/result.txt",
"contentBase64": "Y29ycnVwdCBibG9iCg==",
"managedTreePolicyVersion": 3,
});
let published = invoke_request(request.clone());
assert!(published.status.success());
let published: serde_json::Value = serde_json::from_slice(&published.stdout).unwrap();
let result_object_path =
loose_object_path(&destination, published["resultBlobOid"].as_str().unwrap());
fs::rename(
&result_object_path,
fixture.root.join("original-corrupt-result-object"),
)
.unwrap();
fs::write(result_object_path, b"not a zlib object").unwrap();
let retry = invoke_request(request);
assert_helper_error(&retry, "candidate_ref_target_invalid");
}
#[test]
fn rejects_a_symbolic_candidate_ref_during_exact_retry() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("symbolic-candidate-ref.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let candidate_ref =
"refs/maka/candidates/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": candidate_ref,
"path": "docs/result.txt",
"contentBase64": "c3ltYm9saWMgcmV0cnkK",
"managedTreePolicyVersion": 3,
});
let published = invoke_request(request.clone());
assert!(published.status.success());
let published: serde_json::Value = serde_json::from_slice(&published.stdout).unwrap();
let candidate_commit = published["candidateCommitOid"].as_str().unwrap();
let alias_ref = "refs/maka/candidate-alias";
git_bare_output(&destination, ["update-ref", alias_ref, candidate_commit]);
git_bare_output(&destination, ["symbolic-ref", candidate_ref, alias_ref]);
let rejected = invoke_request(request);
assert_helper_error(&rejected, "candidate_ref_not_direct");
}
#[test]
fn rejects_an_annotated_tag_candidate_ref_during_exact_retry() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("tag-candidate-ref.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let candidate_ref =
"refs/maka/candidates/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": candidate_ref,
"path": "docs/result.txt",
"contentBase64": "dGFnIHJldHJ5Cg==",
"managedTreePolicyVersion": 3,
});
let published = invoke_request(request.clone());
assert!(published.status.success());
let published: serde_json::Value = serde_json::from_slice(&published.stdout).unwrap();
let candidate_commit = published["candidateCommitOid"].as_str().unwrap();
git_bare_output(
&destination,
[
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"tag",
"-a",
"candidate-receipt",
candidate_commit,
"-m",
"candidate receipt",
],
);
let tag_oid = git_bare_output(&destination, ["rev-parse", "refs/tags/candidate-receipt"]);
git_bare_output(&destination, ["update-ref", candidate_ref, &tag_oid]);
let rejected = invoke_request(request);
assert_helper_error(&rejected, "candidate_ref_target_invalid");
}
#[test]
fn rejects_a_symbolic_accepted_ref_before_candidate_creation() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("symbolic-accepted-ref.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let alias_ref = "refs/maka/accepted-alias";
git_bare_output(&destination, ["update-ref", alias_ref, baseline]);
git_bare_output(
&destination,
["symbolic-ref", "refs/maka/baseline", alias_ref],
);
let rejected = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": "refs/maka/candidates/dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
"path": "docs/result.txt",
"contentBase64": "c3ltYm9saWMgYWNjZXB0ZWQK",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&rejected, "accepted_ref_not_direct");
}
#[test]
fn rejects_an_annotated_tag_accepted_ref_before_candidate_creation() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("tag-accepted-ref.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
git_bare_output(
&destination,
[
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"tag",
"-a",
"accepted-receipt",
baseline,
"-m",
"accepted receipt",
],
);
let tag_oid = git_bare_output(&destination, ["rev-parse", "refs/tags/accepted-receipt"]);
git_bare_output(&destination, ["update-ref", "refs/maka/baseline", &tag_oid]);
let rejected = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": "refs/maka/candidates/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"path": "docs/result.txt",
"contentBase64": "dGFnIGFjY2VwdGVkCg==",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&rejected, "accepted_ref_target_invalid");
}
#[test]
fn publishes_an_operation_bound_no_change_receipt() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("no-change.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let baseline_tree = imported["baselineTreeOid"].as_str().unwrap();
let candidate_ref =
"refs/maka/candidates/4444444444444444444444444444444444444444444444444444444444444444";
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": candidate_ref,
"path": "hello.txt",
"contentBase64": "aGVsbG8gZnJvbSBzaGExCg==",
"managedTreePolicyVersion": 3,
});
let response = invoke_request(request.clone());
assert!(response.status.success());
let response: serde_json::Value = serde_json::from_slice(&response.stdout).unwrap();
assert_eq!(response["kind"], "candidate_no_change");
assert_eq!(response["baseCommitOid"], baseline);
assert_eq!(response["baseTreeOid"], baseline_tree);
assert_eq!(response["candidateRef"], candidate_ref);
assert_eq!(response["candidateTreeOid"], baseline_tree);
assert_eq!(
git_bare_output(&destination, ["rev-parse", candidate_ref]),
response["candidateCommitOid"].as_str().unwrap()
);
assert_eq!(
git_bare_output(&destination, ["rev-parse", "refs/maka/baseline"]),
baseline
);
let retry = invoke_request(request);
assert!(retry.status.success());
let retry: serde_json::Value = serde_json::from_slice(&retry.stdout).unwrap();
assert_eq!(retry, response);
}
#[test]
fn rejects_a_second_terminal_interpretation_for_the_same_operation_ref() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("operation-linearity.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let baseline_tree = imported["baselineTreeOid"].as_str().unwrap();
let candidate_ref =
"refs/maka/candidates/7777777777777777777777777777777777777777777777777777777777777777";
let request = |path: &str, content_base64: &str| {
serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": candidate_ref,
"path": path,
"contentBase64": content_base64,
"managedTreePolicyVersion": 3,
})
};
let no_change = invoke_request(request("hello.txt", "aGVsbG8gZnJvbSBzaGExCg=="));
assert!(no_change.status.success());
let no_change: serde_json::Value = serde_json::from_slice(&no_change.stdout).unwrap();
assert_eq!(no_change["kind"], "candidate_no_change");
let objects_before_conflict = git_bare_output(&destination, ["count-objects", "-v"]);
let conflicting = invoke_request(request("docs/result.txt", "Y2hhbmdlZAo="));
assert_helper_error(&conflicting, "candidate_request_conflict");
assert_eq!(
git_bare_output(&destination, ["count-objects", "-v"]),
objects_before_conflict
);
assert_eq!(
git_bare_output(&destination, ["rev-parse", candidate_ref]),
no_change["candidateCommitOid"].as_str().unwrap()
);
let reverse_candidate_ref =
"refs/maka/candidates/9999999999999999999999999999999999999999999999999999999999999999";
let reverse_request = |path: &str, content_base64: &str| {
serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": reverse_candidate_ref,
"path": path,
"contentBase64": content_base64,
"managedTreePolicyVersion": 3,
})
};
let published = invoke_request(reverse_request("docs/result.txt", "Y2hhbmdlZAo="));
assert!(published.status.success());
let published: serde_json::Value = serde_json::from_slice(&published.stdout).unwrap();
assert_eq!(published["kind"], "candidate_published");
let reverse_conflict = invoke_request(reverse_request("hello.txt", "aGVsbG8gZnJvbSBzaGExCg=="));
assert_helper_error(&reverse_conflict, "candidate_request_conflict");
assert_eq!(
git_bare_output(&destination, ["rev-parse", reverse_candidate_ref]),
published["candidateCommitOid"].as_str().unwrap()
);
}
#[test]
fn concurrent_exact_candidate_retry_converges_to_one_commit() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("concurrent-exact-retry.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let request = serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": "refs/maka/candidates/8888888888888888888888888888888888888888888888888888888888888888",
"path": "docs/result.txt",
"contentBase64": "Y29uY3VycmVudAo=",
"managedTreePolicyVersion": 3,
});
let first_request = request.clone();
let first = std::thread::spawn(move || invoke_request(first_request));
let second = std::thread::spawn(move || invoke_request(request));
let first = first.join().unwrap();
let second = second.join().unwrap();
assert!(
first.status.success(),
"{}",
String::from_utf8_lossy(&first.stdout)
);
assert!(
second.status.success(),
"{}",
String::from_utf8_lossy(&second.stdout)
);
let first: serde_json::Value = serde_json::from_slice(&first.stdout).unwrap();
let second: serde_json::Value = serde_json::from_slice(&second.stdout).unwrap();
assert_eq!(first, second);
}
#[test]
fn concurrent_conflicting_candidate_requests_have_one_stable_winner() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("concurrent-conflict.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let request = |content_base64: &str| {
serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": imported["baselineCommitOid"],
"expectedBaseTreeOid": imported["baselineTreeOid"],
"candidateRef": "refs/maka/candidates/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"path": "docs/result.txt",
"contentBase64": content_base64,
"managedTreePolicyVersion": 3,
})
};
let first = request("Zmlyc3QK");
let second = request("c2Vjb25kCg==");
let first = std::thread::spawn(move || invoke_request(first));
let second = std::thread::spawn(move || invoke_request(second));
let outcomes = [first.join().unwrap(), second.join().unwrap()];
assert_eq!(
outcomes
.iter()
.filter(|outcome| outcome.status.success())
.count(),
1
);
let rejected = outcomes
.iter()
.find(|outcome| !outcome.status.success())
.unwrap();
assert_helper_error(rejected, "candidate_request_conflict");
}
#[test]
fn rejects_a_stale_candidate_before_publishing_its_ref() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("stale-successor.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let baseline_tree = imported["baselineTreeOid"].as_str().unwrap();
let advanced = git_bare_output(
&destination,
[
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit-tree",
baseline_tree,
"-p",
baseline,
"-m",
"advanced",
],
);
let _ = git_bare_output(
&destination,
["update-ref", "refs/maka/baseline", &advanced],
);
let rejected = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": "refs/maka/candidates/2222222222222222222222222222222222222222222222222222222222222222",
"path": "must-not-exist.txt",
"contentBase64": "bXVzdCBub3QgcHVibGlzaAo=",
"managedTreePolicyVersion": 3,
}));
assert_eq!(rejected.status.code(), Some(3));
let rejected: serde_json::Value = serde_json::from_slice(&rejected.stdout).unwrap();
assert_eq!(rejected["kind"], "candidate_rejected");
assert_eq!(rejected["reason"], "base_commit_mismatch");
assert_eq!(rejected["actualBaseCommitOid"], advanced);
assert_eq!(
git_bare_output(&destination, ["rev-parse", "refs/maka/baseline"]),
advanced
);
assert!(!git_bare_succeeds(
&destination,
[
"rev-parse",
"refs/maka/candidates/2222222222222222222222222222222222222222222222222222222222222222"
]
));
}
#[test]
fn validates_the_complete_candidate_tree_before_candidate_ref_publication() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("invalid-successor.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let baseline_tree = imported["baselineTreeOid"].as_str().unwrap();
let rejected = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": "refs/maka/candidates/3333333333333333333333333333333333333333333333333333333333333333",
"path": ".gitattributes",
"contentBase64": "KiBmaWx0ZXI9ZXh0ZXJuYWwK",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&rejected, "unsupported_source_attributes");
assert_eq!(
git_bare_output(&destination, ["rev-parse", "refs/maka/baseline"]),
baseline
);
}
#[test]
fn rejects_hfs_protected_candidate_paths_before_object_or_ref_publication() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("hfs-candidate.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let baseline_tree = imported["baselineTreeOid"].as_str().unwrap();
let candidate_ref =
"refs/maka/candidates/6666666666666666666666666666666666666666666666666666666666666666";
let rejected = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": candidate_ref,
"path": ".g\u{200c}it",
"contentBase64": "ZXZpbAo=",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&rejected, "invalid_successor_path");
assert!(!git_bare_succeeds(
&destination,
["rev-parse", candidate_ref]
));
assert_eq!(
git_bare_output(&destination, ["rev-parse", "refs/maka/baseline"]),
baseline
);
}
#[test]
fn rejects_a_candidate_when_the_exact_base_tree_storage_is_corrupt() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("corrupt-base-tree.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let baseline = imported["baselineCommitOid"].as_str().unwrap();
let baseline_tree = imported["baselineTreeOid"].as_str().unwrap();
fs::write(fixture.root.join("hello.txt"), b"different tree bytes\n").unwrap();
fixture.git(["add", "hello.txt"]);
let replacement_tree = fixture.git_output(["write-tree"]);
let target = loose_object_path(&destination, baseline_tree);
fs::remove_file(&target).unwrap();
fs::copy(fixture.loose_object_path(&replacement_tree), target).unwrap();
let candidate_ref =
"refs/maka/candidates/5555555555555555555555555555555555555555555555555555555555555555";
let rejected = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "create_candidate",
"repositoryPath": destination,
"acceptedRef": "refs/maka/baseline",
"expectedBaseCommitOid": baseline,
"expectedBaseTreeOid": baseline_tree,
"candidateRef": candidate_ref,
"path": "docs/result.txt",
"contentBase64": "bXVzdCBub3QgcHVibGlzaAo=",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&rejected, "base_tree_identity_mismatch");
assert!(!git_bare_succeeds(
&destination,
["rev-parse", candidate_ref]
));
}
#[test]
fn reads_one_exact_utf8_file_from_an_accepted_policy_v3_tree() {
let fixture = RepositoryFixture::sha1_with_commit();
fs::create_dir_all(fixture.root.join("config")).unwrap();
fs::write(
fixture.root.join("config/package-lock.json"),
b"{\"lockfileVersion\":3}\n",
)
.unwrap();
fixture.git(["add", "config/package-lock.json"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"tree file fixture",
]);
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("tree-file.git");
let imported = invoke_import(&fixture.root, &source_head, &destination);
let imported: serde_json::Value = serde_json::from_slice(&imported.stdout).unwrap();
let accepted = imported["baselineCommitOid"].as_str().unwrap();
let expected_tree = imported["baselineTreeOid"].as_str().unwrap();
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "read_tree_file",
"repositoryPath": destination,
"acceptedCommitOid": accepted,
"path": "config/package-lock.json",
"managedTreePolicyVersion": 3,
}));
assert!(output.status.success());
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(response["kind"], "tree_file_read");
assert_eq!(response["acceptedTreeOid"], expected_tree);
assert_eq!(response["content"], "{\"lockfileVersion\":3}\n");
assert_eq!(response["bytesRead"], 22);
assert_eq!(response["managedTreePolicyVersion"], 3);
}
#[test]
fn refuses_to_read_a_tree_file_from_an_unavailable_commit_identity() {
let fixture = RepositoryFixture::sha1_with_commit();
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "read_tree_file",
"repositoryPath": fixture.root,
"acceptedCommitOid": "0000000000000000000000000000000000000000",
"path": "hello.txt",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "accepted_commit_unavailable");
}
#[test]
fn imports_maka_attributes_under_managed_tree_policy_v3() {
let fixture = RepositoryFixture::sha1_with_commit();
fs::write(
fixture.root.join(".gitattributes"),
b"* text=auto eol=lf\n/.claude export-ignore\n/.maka-shots export-ignore\n/maka-proposal-zh-review.txt export-ignore\n",
)
.unwrap();
fixture.git(["add", ".gitattributes"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"attributes fixture",
]);
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let source_tree = fixture.git_output(["rev-parse", "HEAD^{tree}"]);
let destination = fixture.root.join("managed-attributes-v2.git");
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert!(
output.status.success(),
"helper failed: stdout={} stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(response["kind"], "source_imported");
assert_eq!(response["sourceTreeOid"], source_tree);
assert_eq!(response["baselineTreeOid"], source_tree);
assert_eq!(response["managedTreePolicyVersion"], 3);
}
#[test]
fn rejects_external_filter_attributes_before_claiming_the_destination() {
let fixture = RepositoryFixture::sha1_with_commit();
fs::write(fixture.root.join(".gitattributes"), b"*.bin filter=lfs\n").unwrap();
fixture.git(["add", ".gitattributes"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"unsupported attributes fixture",
]);
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("unsupported-attributes.git");
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "unsupported_source_attributes");
assert!(!destination.exists());
}
#[test]
fn rejects_bracket_glob_attributes_before_claiming_the_destination() {
assert_attributes_import_rejected(
b"/[ab].txt export-ignore\n/file[0-9].txt export-ignore\n",
"bracket-glob-attributes.git",
);
}
#[test]
fn rejects_non_git_blank_characters_before_claiming_the_destination() {
for (attributes, destination_name) in [
(
"\u{00a0}* text=auto eol=lf\n".as_bytes(),
"nbsp-attributes.git",
),
(b"\x0b* text=auto eol=lf\n".as_slice(), "vt-attributes.git"),
(b"\x0c* text=auto eol=lf\n".as_slice(), "ff-attributes.git"),
] {
assert_attributes_import_rejected(attributes, destination_name);
}
}
#[test]
fn rejects_a_git_ignored_2048_byte_attribute_line_before_claiming_the_destination() {
let attributes = format!("/{} export-ignore", "a".repeat(2033));
assert_eq!(attributes.len(), 2048);
assert_attributes_import_rejected(attributes.as_bytes(), "overlong-attribute-line.git");
}
#[test]
fn rejects_oversized_attributes_before_claiming_the_destination() {
let fixture = RepositoryFixture::sha1_with_commit();
fs::write(
fixture.root.join(".gitattributes"),
vec![b'#'; 64 * 1024 + 1],
)
.unwrap();
fixture.git(["add", ".gitattributes"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"oversized attributes fixture",
]);
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("oversized-attributes.git");
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "source_attributes_limit_exceeded");
assert!(!destination.exists());
}
fn assert_attributes_import_rejected(attributes: &[u8], destination_name: &str) {
let fixture = RepositoryFixture::sha1_with_commit();
fs::write(fixture.root.join(".gitattributes"), attributes).unwrap();
fixture.git(["add", ".gitattributes"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"unsupported attributes grammar fixture",
]);
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join(destination_name);
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "unsupported_source_attributes");
assert!(!destination.exists());
}
#[test]
fn rejects_a_foreign_bare_destination_without_modifying_it() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("foreign.git");
let initialized = Command::new("git")
.args(["init", "--bare"])
.arg(&destination)
.output()
.unwrap();
assert!(initialized.status.success());
let sentinel = destination.join("hooks/foreign-owner");
fs::write(&sentinel, b"preserve me\n").unwrap();
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/accepted",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "import_destination_not_fresh");
assert_eq!(fs::read(&sentinel).unwrap(), b"preserve me\n");
assert!(!git_bare_succeeds(
&destination,
["show-ref", "--verify", "refs/maka/accepted"]
));
}
#[test]
fn rejects_a_foreign_non_bare_destination_without_modifying_it() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("foreign-worktree");
fs::create_dir(&destination).unwrap();
let sentinel = destination.join("user-data.txt");
fs::write(&sentinel, b"preserve non-bare content\n").unwrap();
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/accepted",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "import_destination_not_fresh");
assert_eq!(fs::read(&sentinel).unwrap(), b"preserve non-bare content\n");
}
#[test]
fn rejects_the_source_repository_as_its_own_destination_without_modifying_it() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let sentinel = fixture.root.join("hooks/user-data");
fs::create_dir_all(sentinel.parent().unwrap()).unwrap();
fs::write(&sentinel, b"preserve source bytes\n").unwrap();
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": fixture.root,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "import_destination_not_fresh");
assert_eq!(fs::read(&sentinel).unwrap(), b"preserve source bytes\n");
}
#[test]
fn rejects_the_retired_managed_tree_policy_v1_before_creating_the_destination() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("unsupported-policy.git");
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 1,
}));
assert_helper_error(&output, "unsupported_managed_tree_policy");
assert!(!destination.exists());
}
#[test]
fn rejects_an_invalid_baseline_ref_before_creating_the_destination() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("invalid-ref.git");
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/a..b",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "invalid_baseline_ref");
assert!(!destination.exists());
}
#[test]
fn exactly_one_process_claims_a_fresh_import_destination() {
let first_source = RepositoryFixture::sha1_with_commit();
let second_source = RepositoryFixture::sha1_with_commit_content(b"second source\n");
let first_head = first_source.git_output(["rev-parse", "HEAD"]);
let second_head = second_source.git_output(["rev-parse", "HEAD"]);
let first_blob = first_source.git_output(["rev-parse", "HEAD:hello.txt"]);
let second_blob = second_source.git_output(["rev-parse", "HEAD:hello.txt"]);
let destination = first_source.root.join("contended.git");
let first_request = serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": first_source.root,
"expectedSourceHeadCommitOid": first_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
});
let second_request = serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": second_source.root,
"expectedSourceHeadCommitOid": second_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
});
let first = spawn_request(first_request);
let second = spawn_request(second_request);
let first_output = first.wait_with_output().unwrap();
let second_output = second.wait_with_output().unwrap();
let outputs = [first_output, second_output];
assert_eq!(
outputs
.iter()
.filter(|output| output.status.success())
.count(),
1
);
assert_eq!(
outputs
.iter()
.filter(|output| {
serde_json::from_slice::<serde_json::Value>(&output.stdout)
.is_ok_and(|response| response["reason"] == "import_destination_not_fresh")
})
.count(),
1
);
let imported = outputs
.iter()
.find(|output| output.status.success())
.and_then(|output| serde_json::from_slice::<serde_json::Value>(&output.stdout).ok())
.unwrap();
let winner_is_first = imported["sourceHeadCommitOid"] == first_head;
let winner_blob = if winner_is_first {
first_blob.as_str()
} else {
second_blob.as_str()
};
let loser_blob = if winner_is_first {
second_blob.as_str()
} else {
first_blob.as_str()
};
assert!(git_bare_succeeds(
&destination,
["cat-file", "-e", winner_blob]
));
assert!(!git_bare_succeeds(
&destination,
["cat-file", "-e", loser_blob]
));
}
#[test]
fn rejects_an_oversized_commit_before_creating_the_destination() {
let fixture = RepositoryFixture::sha1_with_oversized_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("oversized-commit.git");
let output = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&output, "commit_object_limit_exceeded");
assert!(!destination.exists());
}
#[test]
fn rejects_an_oversized_tree_before_creating_the_destination() {
let fixture = RepositoryFixture::sha1_with_oversized_tree();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let destination = fixture.root.join("oversized-tree.git");
let output = invoke_import(&fixture.root, &source_head, &destination);
assert_helper_error(&output, "source_tree_object_limit_exceeded");
assert!(!destination.exists());
}
#[test]
fn rejects_a_destination_below_an_aliased_parent() {
let fixture = RepositoryFixture::sha1_with_commit();
let source_head = fixture.git_output(["rev-parse", "HEAD"]);
let owned_parent = fixture.root.join("owned-parent");
let aliased_parent = fixture.root.join("aliased-parent");
fs::create_dir(&owned_parent).unwrap();
create_directory_alias(&owned_parent, &aliased_parent);
let destination = aliased_parent.join("managed.git");
let output = invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": fixture.root,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}));
assert_helper_error(&output, "import_destination_parent_untrusted");
assert!(!owned_parent.join("managed.git").exists());
}
fn invoke_helper(repository_path: &Path) -> Output {
invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "inspect_repository",
"repositoryPath": repository_path,
}))
}
fn invoke_request(request: serde_json::Value) -> Output {
spawn_request(request).wait_with_output().unwrap()
}
fn spawn_request(request: serde_json::Value) -> Child {
let mut child = Command::new(HELPER)
.env("PATH", "")
.env("GIT_CONFIG_COUNT", "1")
.env("GIT_CONFIG_KEY_0", "extensions.objectFormat")
.env("GIT_CONFIG_VALUE_0", "sha256")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.take()
.unwrap()
.write_all(serde_json::to_string(&request).unwrap().as_bytes())
.unwrap();
child
}
fn invoke_import(source: &Path, source_head: &str, destination: &Path) -> Output {
invoke_request(serde_json::json!({
"protocolVersion": 1,
"operation": "import_source_head",
"sourceRepositoryPath": source,
"expectedSourceHeadCommitOid": source_head,
"destinationRepositoryPath": destination,
"baselineRef": "refs/maka/baseline",
"managedTreePolicyVersion": 3,
}))
}
fn assert_helper_error(output: &Output, expected_reason: &str) {
assert_eq!(output.status.code(), Some(1));
let response: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(response["kind"], "helper_error");
assert_eq!(response["reason"], expected_reason);
}
#[cfg(unix)]
fn create_directory_alias(target: &Path, alias: &Path) {
std::os::unix::fs::symlink(target, alias).unwrap();
}
#[cfg(windows)]
fn create_directory_alias(target: &Path, alias: &Path) {
let output = Command::new("cmd")
.args(["/d", "/s", "/c", "mklink", "/J"])
.arg(alias)
.arg(target)
.output()
.unwrap();
assert!(
output.status.success(),
"junction fixture failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
fn git_bare_output<const N: usize>(repository: &Path, args: [&str; N]) -> String {
let output = Command::new("git")
.arg("--git-dir")
.arg(repository)
.args(args)
.output()
.unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
fn git_bare_bytes<const N: usize>(repository: &Path, args: [&str; N]) -> Vec<u8> {
let output = Command::new("git")
.arg("--git-dir")
.arg(repository)
.args(args)
.output()
.unwrap();
assert!(output.status.success());
output.stdout
}
fn git_bare_input_output<const N: usize>(
repository: &Path,
args: [&str; N],
input: &[u8],
) -> String {
let mut child = Command::new("git")
.arg("--git-dir")
.arg(repository)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(input).unwrap();
let output = child.wait_with_output().unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
fn replace_candidate_ref_tree(
repository: &Path,
candidate_ref: &str,
published: &serde_json::Value,
replacement_tree: &str,
) {
let original_commit = git_bare_bytes(
repository,
[
"cat-file",
"commit",
published["candidateCommitOid"].as_str().unwrap(),
],
);
let original_tree_line = format!("tree {}", published["candidateTreeOid"].as_str().unwrap());
let forged_commit_data = String::from_utf8(original_commit).unwrap().replacen(
&original_tree_line,
&format!("tree {replacement_tree}"),
1,
);
let forged_commit = git_bare_input_output(
repository,
["hash-object", "-t", "commit", "-w", "--stdin"],
forged_commit_data.as_bytes(),
);
git_bare_output(repository, ["update-ref", candidate_ref, &forged_commit]);
}
fn git_bare_succeeds<const N: usize>(repository: &Path, args: [&str; N]) -> bool {
Command::new("git")
.arg("--git-dir")
.arg(repository)
.args(args)
.status()
.unwrap()
.success()
}
fn loose_object_path(git_dir: &Path, oid: &str) -> PathBuf {
git_dir.join("objects").join(&oid[..2]).join(&oid[2..])
}
struct RepositoryFixture {
root: PathBuf,
}
#[derive(Clone, Copy)]
enum RawObjectKind {
Blob,
Tree,
}
struct RawTreeEntry<'a> {
mode: &'a str,
name: &'a str,
object_kind: RawObjectKind,
}
impl RepositoryFixture {
fn sha1_with_commit() -> Self {
Self::sha1_with_commit_content(b"hello from sha1\n")
}
fn sha1_with_commit_content(content: &[u8]) -> Self {
let fixture = Self::init("sha1");
fs::write(fixture.root.join("hello.txt"), content).unwrap();
fixture.git(["add", "hello.txt"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"fixture",
]);
fixture
}
fn sha1_with_raw_tree(entries: &[RawTreeEntry<'_>]) -> (Self, String) {
let fixture = Self::init("sha1");
let blob_oid = fixture.git_input_output(
["hash-object", "-t", "blob", "-w", "--stdin"],
b"raw tree blob\n",
);
let tree_oid =
fixture.git_input_output(["hash-object", "-t", "tree", "-w", "--stdin"], &[]);
let mut raw_tree = Vec::new();
for entry in entries {
raw_tree.extend_from_slice(entry.mode.as_bytes());
raw_tree.push(b' ');
raw_tree.extend_from_slice(entry.name.as_bytes());
raw_tree.push(0);
let oid = match entry.object_kind {
RawObjectKind::Blob => &blob_oid,
RawObjectKind::Tree => &tree_oid,
};
raw_tree.extend_from_slice(&decode_hex_oid(oid));
}
let root_tree_oid = fixture.git_input_output(
["hash-object", "--literally", "-t", "tree", "-w", "--stdin"],
&raw_tree,
);
let commit = format!(
"tree {root_tree_oid}\nauthor Maka Test <maka@example.invalid> 946684800 +0000\ncommitter Maka Test <maka@example.invalid> 946684800 +0000\n\nraw tree fixture\n"
);
let source_head = fixture.git_input_output(
["hash-object", "-t", "commit", "-w", "--stdin"],
commit.as_bytes(),
);
fixture.git(["update-ref", "HEAD", &source_head]);
(fixture, source_head)
}
fn sha1_with_oversized_commit() -> Self {
let fixture = Self::init("sha1");
let tree = fixture.git_input_output(["hash-object", "-t", "tree", "-w", "--stdin"], &[]);
let mut commit = format!(
"tree {tree}\nauthor Maka Test <maka@example.invalid> 946684800 +0000\ncommitter Maka Test <maka@example.invalid> 946684800 +0000\n\n"
)
.into_bytes();
commit.resize(1024 * 1024 + 1, b'x');
let commit_oid =
fixture.git_input_output(["hash-object", "-t", "commit", "-w", "--stdin"], &commit);
fixture.git(["update-ref", "HEAD", &commit_oid]);
fixture
}
fn sha1_with_oversized_tree() -> Self {
let fixture = Self::init("sha1");
let invalid_tree = vec![0_u8; 8 * 1024 * 1024 + 1];
let tree_oid = fixture.git_input_output(
["hash-object", "--literally", "-t", "tree", "-w", "--stdin"],
&invalid_tree,
);
let commit = format!(
"tree {tree_oid}\nauthor Maka Test <maka@example.invalid> 946684800 +0000\ncommitter Maka Test <maka@example.invalid> 946684800 +0000\n\noversized tree\n"
);
let commit_oid = fixture.git_input_output(
["hash-object", "-t", "commit", "-w", "--stdin"],
commit.as_bytes(),
);
fixture.git(["update-ref", "HEAD", &commit_oid]);
fixture
}
fn sha256_unborn() -> Self {
Self::init("sha256")
}
fn unknown_object_format() -> Self {
let fixture = Self::init("sha1");
fixture.git(["config", "core.repositoryFormatVersion", "1"]);
fixture.git(["config", "extensions.objectFormat", "sha512"]);
fixture
}
fn sha1_with_replacement_ref() -> (Self, String, String) {
let fixture = Self::sha1_with_commit();
let raw_commit = fixture.git_output(["rev-parse", "HEAD"]);
let raw_tree = fixture.git_output(["rev-parse", "HEAD^{tree}"]);
fs::write(fixture.root.join("hello.txt"), b"replacement content\n").unwrap();
fixture.git(["add", "hello.txt"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"replacement",
]);
let replacement_commit = fixture.git_output(["rev-parse", "HEAD"]);
fixture.git(["replace", &raw_commit, &replacement_commit]);
fixture.git(["checkout", "--detach", &raw_commit]);
(fixture, raw_commit, raw_tree)
}
fn sha1_with_mismatched_head_storage() -> (Self, String) {
let fixture = Self::sha1_with_commit_content(b"claimed content\n");
let claimed_head = fixture.git_output(["rev-parse", "HEAD"]);
fs::write(fixture.root.join("hello.txt"), b"replacement content\n").unwrap();
fixture.git(["add", "hello.txt"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"replacement object bytes",
]);
let replacement_head = fixture.git_output(["rev-parse", "HEAD"]);
fixture.git(["update-ref", "HEAD", &claimed_head]);
fixture.replace_loose_object(&claimed_head, &replacement_head);
(fixture, claimed_head)
}
fn sha1_with_mismatched_tree_storage() -> (Self, String) {
let fixture = Self::sha1_with_commit_content(b"claimed tree content\n");
let claimed_head = fixture.git_output(["rev-parse", "HEAD"]);
let claimed_tree = fixture.git_output(["rev-parse", "HEAD^{tree}"]);
fs::write(
fixture.root.join("hello.txt"),
b"replacement tree content\n",
)
.unwrap();
fixture.git(["add", "hello.txt"]);
fixture.git([
"-c",
"user.name=Maka Test",
"-c",
"user.email=maka@example.invalid",
"commit",
"-m",
"replacement tree bytes",
]);
let replacement_tree = fixture.git_output(["rev-parse", "HEAD^{tree}"]);
fixture.git(["update-ref", "HEAD", &claimed_head]);
fixture.replace_loose_object(&claimed_tree, &replacement_tree);
(fixture, claimed_head)
}
fn sha1_with_mismatched_blob_storage() -> (Self, String) {
let fixture = Self::sha1_with_commit_content(b"claimed blob content\n");
let claimed_head = fixture.git_output(["rev-parse", "HEAD"]);
let claimed_blob = fixture.git_output(["rev-parse", "HEAD:hello.txt"]);
fs::write(
fixture.root.join("hello.txt"),
b"replacement blob content\n",
)
.unwrap();
fixture.git(["add", "hello.txt"]);
let replacement_blob = fixture.git_output(["rev-parse", ":hello.txt"]);
fixture.git(["reset", "--hard", &claimed_head]);
fixture.replace_loose_object(&claimed_blob, &replacement_blob);
(fixture, claimed_head)
}
fn replace_loose_object(&self, target_oid: &str, replacement_oid: &str) {
let target = self.loose_object_path(target_oid);
fs::remove_file(&target).unwrap();
fs::copy(self.loose_object_path(replacement_oid), target).unwrap();
}
fn loose_object_path(&self, oid: &str) -> PathBuf {
self.root
.join(".git/objects")
.join(&oid[..2])
.join(&oid[2..])
}
fn init(object_format: &str) -> Self {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!(
"maka-gitoxide-helper-admission-{}-{nonce}-{}",
std::process::id(),
FIXTURE_SEQUENCE.fetch_add(1, Ordering::Relaxed),
));
fs::create_dir_all(&root).unwrap();
let root = canonicalize_fixture_root(root);
let fixture = Self { root };
fixture.git([
"init",
"--quiet",
&format!("--object-format={object_format}"),
]);
fixture
}
fn git<const N: usize>(&self, args: [&str; N]) {
let output = Command::new("git")
.arg("-C")
.arg(&self.root)
.args(args)
.output()
.unwrap();
assert!(
output.status.success(),
"git fixture command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
fn git_output<const N: usize>(&self, args: [&str; N]) -> String {
let output = Command::new("git")
.arg("-C")
.arg(&self.root)
.args(args)
.output()
.unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
fn git_input_output<const N: usize>(&self, args: [&str; N], input: &[u8]) -> String {
let mut child = Command::new("git")
.arg("-C")
.arg(&self.root)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(input).unwrap();
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"git fixture command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
}
fn decode_hex_oid(oid: &str) -> Vec<u8> {
oid.as_bytes()
.chunks_exact(2)
.map(|pair| {
let pair = std::str::from_utf8(pair).unwrap();
u8::from_str_radix(pair, 16).unwrap()
})
.collect()
}
#[cfg(unix)]
fn canonicalize_fixture_root(root: PathBuf) -> PathBuf {
fs::canonicalize(root).unwrap()
}
#[cfg(windows)]
fn canonicalize_fixture_root(root: PathBuf) -> PathBuf {
root
}
impl Drop for RepositoryFixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}