mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 23:18:24 +00:00
purgatory: when state data recieved sync across repositoies
This commit is contained in:
@@ -766,32 +766,159 @@ impl PushAuthorizationTests {
|
||||
///
|
||||
/// GRASP-01: "respecting the recursive maintainer set"
|
||||
///
|
||||
/// This test verifies that when a maintainer publishes a state event, it updates
|
||||
/// the git repository state for all other maintainers' views. This ensures git
|
||||
/// repositories always reflect the state according to nostr events (including state
|
||||
/// from recursive maintainers).
|
||||
/// This test verifies that when a maintainer publishes a state event, the purgatory
|
||||
/// feature correctly copies git commits between repos so all authorized maintainers'
|
||||
/// repositories always reflect the state according to nostr events.
|
||||
///
|
||||
/// ## Implementation Note
|
||||
/// ## Implementation
|
||||
///
|
||||
/// This test is a stub for the purgatory feature. It will be implemented as part
|
||||
/// of GRASP-02 purgatory functionality.
|
||||
/// This test:
|
||||
/// 1. Uses RecursiveMaintainerStateDataPushed fixture which:
|
||||
/// - Creates owner repo + state (ValidRepo, OwnerStateDataPushed)
|
||||
/// - Creates maintainer announcement (separate repo for maintainer)
|
||||
/// - Pushes recursive maintainer's git data to owner's repo
|
||||
/// 2. Clones the maintainer's repository (not the owner's)
|
||||
/// 3. Verifies that the maintainer's repo contains the recursive maintainer's state
|
||||
///
|
||||
/// ## Fixture Compatibility
|
||||
///
|
||||
/// This test will use:
|
||||
/// - `MaintainerStateDataPushed` - maintainer's state event with git data pushed
|
||||
/// - Multiple maintainer clones to verify state propagation
|
||||
#[allow(dead_code)]
|
||||
/// This proves purgatory is working: git data was pushed to owner's repo, and purgatory
|
||||
/// synced it to the maintainer's repo based on the state event.
|
||||
pub async fn test_push_of_state_by_maintainer_updates_other_maintainer_repos(
|
||||
_client: &AuditClient,
|
||||
_relay_domain: &str,
|
||||
client: &AuditClient,
|
||||
relay_domain: &str,
|
||||
) -> TestResult {
|
||||
TestResult::new(
|
||||
"test_push_of_state_by_maintainer_updates_other_maintainer_repos",
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.fail("Not yet implemented - requires purgatory feature (GRASP-02)")
|
||||
let test_name = "test_push_of_state_by_maintainer_updates_other_maintainer_repos";
|
||||
let ctx = TestContext::new(client);
|
||||
|
||||
// Get the RecursiveMaintainerStateDataPushed fixture which:
|
||||
// 1. Creates owner repo + owner state + git data
|
||||
// 2. Creates maintainer announcement (separate repo)
|
||||
// 3. Pushes recursive maintainer's git data to owner's repo
|
||||
// 4. Purgatory should then sync to maintainer's repo
|
||||
let recursive_state = match ctx
|
||||
.get_fixture(FixtureKind::RecursiveMaintainerStateDataPushed)
|
||||
.await
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return TestResult::new(
|
||||
test_name,
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.fail(format!(
|
||||
"Failed to get RecursiveMaintainerStateDataPushed fixture: {}",
|
||||
e
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Small delay to ensure state processing completes
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
// Extract repo_id from the recursive maintainer's state event
|
||||
let repo_id = match recursive_state
|
||||
.tags
|
||||
.iter()
|
||||
.find(|t| t.kind() == TagKind::d())
|
||||
.and_then(|t| t.content())
|
||||
{
|
||||
Some(id) => id.to_string(),
|
||||
None => {
|
||||
return TestResult::new(
|
||||
test_name,
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.fail("No repo identifier in recursive maintainer state event")
|
||||
}
|
||||
};
|
||||
|
||||
// Get the maintainer's npub
|
||||
let maintainer_npub = match client.maintainer_keys().public_key().to_bech32() {
|
||||
Ok(npub) => npub,
|
||||
Err(e) => {
|
||||
return TestResult::new(
|
||||
test_name,
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.fail(format!(
|
||||
"Failed to convert maintainer pubkey to npub: {}",
|
||||
e
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Use the known recursive maintainer deterministic commit hash from the fixture
|
||||
let expected_commit = crate::RECURSIVE_MAINTAINER_DETERMINISTIC_COMMIT_HASH;
|
||||
|
||||
// Clone the maintainer's repository (NOT the owner's)
|
||||
// This is the key test: git data was pushed to owner's repo, does maintainer's repo have it?
|
||||
let clone_path = match clone_repo(relay_domain, &maintainer_npub, &repo_id) {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
return TestResult::new(
|
||||
test_name,
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.fail(format!("Failed to clone maintainer's repo: {}", e))
|
||||
}
|
||||
};
|
||||
|
||||
let cleanup = || {
|
||||
let _ = fs::remove_dir_all(&clone_path);
|
||||
};
|
||||
|
||||
// Verify that the maintainer's repo contains the recursive maintainer's commit
|
||||
// This proves purgatory copied it from owner's repo
|
||||
let commit_exists_output = Command::new("git")
|
||||
.args(["cat-file", "-t", expected_commit])
|
||||
.current_dir(&clone_path)
|
||||
.output();
|
||||
|
||||
let commit_exists = match commit_exists_output {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
let obj_type = String::from_utf8_lossy(&output.stdout);
|
||||
obj_type.trim() == "commit"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
cleanup();
|
||||
return TestResult::new(
|
||||
test_name,
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.fail(format!("Failed to check if commit exists: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
cleanup();
|
||||
|
||||
if commit_exists {
|
||||
TestResult::new(
|
||||
test_name,
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.pass()
|
||||
} else {
|
||||
TestResult::new(
|
||||
test_name,
|
||||
"GRASP-01:git-http:purgatory",
|
||||
"Maintainer state updates propagate to other maintainer repos",
|
||||
)
|
||||
.fail(format!(
|
||||
"Maintainer's repo does not contain recursive maintainer's commit {}. \
|
||||
Git data was pushed to owner's repo, but purgatory did not copy it to \
|
||||
maintainer's repo. This indicates the purgatory feature is not working correctly.",
|
||||
expected_commit
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that non-maintainer state event is ignored
|
||||
|
||||
+106
-1
@@ -125,7 +125,27 @@ impl StatePolicy {
|
||||
{
|
||||
let repo_path =
|
||||
self.ctx.git_data_path.join(annoucement.repo_path().clone());
|
||||
// TODO - if repo_path != repo_with_git_data, pass as a datasource for missing data?
|
||||
|
||||
if !repo_path.exists() {
|
||||
// eg if annoucement doesnt list repo (but stored as its in maintainer set)
|
||||
continue;
|
||||
}
|
||||
// If repo_path != repo_with_git_data, copy missing oids first
|
||||
if repo_path != repo_with_git_data {
|
||||
if let Err(e) = self.copy_missing_oids(
|
||||
&repo_with_git_data,
|
||||
&repo_path,
|
||||
&state,
|
||||
) {
|
||||
tracing::warn!(
|
||||
"Failed to copy oids from {} to {}: {}",
|
||||
repo_with_git_data.display(),
|
||||
repo_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result = self.align_repository_with_state(&repo_path, &state);
|
||||
repo_count += 1;
|
||||
tracing::info!(
|
||||
@@ -335,6 +355,91 @@ impl StatePolicy {
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Copy missing OIDs from a source repository to a target repository
|
||||
///
|
||||
/// Identifies commits referenced in the state that are missing from the target
|
||||
/// repository and copies them from the source repository using git fetch.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `source_repo` - Path to repository containing the commits
|
||||
/// * `target_repo` - Path to repository to receive the commits
|
||||
/// * `state` - Repository state containing commit references
|
||||
///
|
||||
/// # Returns
|
||||
/// Ok(()) on success, Err with error message on failure
|
||||
fn copy_missing_oids(
|
||||
&self,
|
||||
source_repo: &Path,
|
||||
target_repo: &Path,
|
||||
state: &RepositoryState,
|
||||
) -> Result<(), String> {
|
||||
use std::process::Command;
|
||||
|
||||
// Collect all commits referenced in the state
|
||||
let mut commits_to_check = Vec::new();
|
||||
|
||||
for branch in &state.branches {
|
||||
if !branch.commit.starts_with("ref: ") {
|
||||
commits_to_check.push(&branch.commit);
|
||||
}
|
||||
}
|
||||
|
||||
for tag in &state.tags {
|
||||
if !tag.commit.starts_with("ref: ") {
|
||||
commits_to_check.push(&tag.commit);
|
||||
}
|
||||
}
|
||||
|
||||
// Identify missing commits
|
||||
let mut missing_commits = Vec::new();
|
||||
for commit in commits_to_check {
|
||||
if !git::oid_exists(target_repo, commit) {
|
||||
missing_commits.push(commit);
|
||||
}
|
||||
}
|
||||
|
||||
if missing_commits.is_empty() {
|
||||
tracing::debug!(
|
||||
"No missing commits to copy from {} to {}",
|
||||
source_repo.display(),
|
||||
target_repo.display()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Copying {} missing commits from {} to {}",
|
||||
missing_commits.len(),
|
||||
source_repo.display(),
|
||||
target_repo.display()
|
||||
);
|
||||
|
||||
// Fetch each missing commit from source to target
|
||||
for commit in &missing_commits {
|
||||
let output = Command::new("git")
|
||||
.args([
|
||||
"fetch",
|
||||
source_repo.to_str().ok_or("Invalid source path")?,
|
||||
commit,
|
||||
])
|
||||
.current_dir(target_repo)
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to execute git fetch: {}", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!(
|
||||
"git fetch failed for commit {}: {}",
|
||||
commit, stderr
|
||||
));
|
||||
}
|
||||
|
||||
tracing::debug!("Copied commit {} to {}", commit, target_repo.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn find_repo_with_git_data(
|
||||
|
||||
@@ -72,7 +72,4 @@ isolated_push_test!(
|
||||
);
|
||||
isolated_push_test!(test_head_set_after_state_event_with_existing_commit);
|
||||
isolated_push_test!(test_head_set_after_git_push_with_required_oids);
|
||||
|
||||
// Note: test_push_of_state_by_maintainer_updates_other_maintainer_repos is not included
|
||||
// as it's a stub for the purgatory feature. It can be run manually once implemented:
|
||||
// isolated_push_test!(test_push_of_state_by_maintainer_updates_other_maintainer_repos);
|
||||
isolated_push_test!(test_push_of_state_by_maintainer_updates_other_maintainer_repos);
|
||||
|
||||
Reference in New Issue
Block a user