| |
self
|
| |
}
|
| |
|
| + |
/// Set the CI event to use.
|
| + |
pub fn ci_event(mut self, event: &'a CiEvent) -> Self {
|
| + |
self.ci_event = Some(event);
|
| + |
self
|
| + |
}
|
| + |
|
| + |
/// Create a [`Request::Trigger``] message from a [`crate::ci_event::Civet`].
|
| + |
pub fn build_trigger_from_ci_event(self) -> Result<Request, MessageError> {
|
| + |
let profile = self.profile.ok_or(MessageError::NoProfile)?;
|
| + |
|
| + |
match self.ci_event {
|
| + |
None => Err(MessageError::CiEventNotSet),
|
| + |
Some(CiEvent::BranchCreated {
|
| + |
from_node,
|
| + |
repo,
|
| + |
branch,
|
| + |
tip,
|
| + |
}) => {
|
| + |
let rad_repo = profile.storage.repository(*repo)?;
|
| + |
let project_info = rad_repo.project()?;
|
| + |
|
| + |
let common = EventCommonFields {
|
| + |
version: PROTOCOL_VERSION,
|
| + |
event_type: EventType::Push,
|
| + |
repository: Repository {
|
| + |
id: *repo,
|
| + |
name: project_info.name().to_string(),
|
| + |
description: project_info.description().to_string(),
|
| + |
private: !rad_repo.identity()?.visibility.is_public(),
|
| + |
default_branch: project_info.default_branch().to_string(),
|
| + |
delegates: rad_repo.delegates()?.iter().copied().collect(),
|
| + |
},
|
| + |
};
|
| + |
|
| + |
let did = Did::from(*from_node);
|
| + |
let pusher = did_to_author(profile, &did)?;
|
| + |
|
| + |
let push = PushEvent {
|
| + |
pusher,
|
| + |
before: *tip, // Branch created: we only use the tip
|
| + |
after: *tip,
|
| + |
branch: push_branch(branch),
|
| + |
commits: vec![*tip], // Branch created, only use tip.
|
| + |
};
|
| + |
Ok(Request::Trigger {
|
| + |
common,
|
| + |
push: Some(push),
|
| + |
patch: None,
|
| + |
})
|
| + |
}
|
| + |
Some(CiEvent::BranchUpdated {
|
| + |
from_node,
|
| + |
repo,
|
| + |
branch,
|
| + |
tip,
|
| + |
old_tip,
|
| + |
}) => {
|
| + |
let rad_repo = profile.storage.repository(*repo)?;
|
| + |
let git_repo =
|
| + |
radicle_surf::Repository::open(paths::repository(&profile.storage, repo))?;
|
| + |
let project_info = rad_repo.project()?;
|
| + |
|
| + |
let common = EventCommonFields {
|
| + |
version: PROTOCOL_VERSION,
|
| + |
event_type: EventType::Push,
|
| + |
repository: Repository {
|
| + |
id: *repo,
|
| + |
name: project_info.name().to_string(),
|
| + |
description: project_info.description().to_string(),
|
| + |
private: !rad_repo.identity()?.visibility.is_public(),
|
| + |
default_branch: project_info.default_branch().to_string(),
|
| + |
delegates: rad_repo.delegates()?.iter().copied().collect(),
|
| + |
},
|
| + |
};
|
| + |
|
| + |
let did = Did::from(*from_node);
|
| + |
let pusher = did_to_author(profile, &did)?;
|
| + |
|
| + |
let mut commits: Vec<Oid> = git_repo
|
| + |
.history(tip)?
|
| + |
.take_while(|c| {
|
| + |
if let Ok(c) = c {
|
| + |
c.id != *old_tip
|
| + |
} else {
|
| + |
false
|
| + |
}
|
| + |
})
|
| + |
.map(|r| r.map(|c| c.id))
|
| + |
.collect::<Result<Vec<Oid>, _>>()?;
|
| + |
if commits.is_empty() {
|
| + |
commits = vec![*old_tip];
|
| + |
}
|
| + |
|
| + |
let push = PushEvent {
|
| + |
pusher,
|
| + |
before: *tip, // Branch created: we only use the tip
|
| + |
after: *tip,
|
| + |
branch: push_branch(branch),
|
| + |
commits,
|
| + |
};
|
| + |
|
| + |
Ok(Request::Trigger {
|
| + |
common,
|
| + |
push: Some(push),
|
| + |
patch: None,
|
| + |
})
|
| + |
}
|
| + |
Some(CiEvent::PatchCreated {
|
| + |
from_node,
|
| + |
repo,
|
| + |
patch: patch_id,
|
| + |
new_tip,
|
| + |
}) => {
|
| + |
let rad_repo = profile.storage.repository(*repo)?;
|
| + |
let git_repo =
|
| + |
radicle_surf::Repository::open(paths::repository(&profile.storage, repo))?;
|
| + |
let project_info = rad_repo.project()?;
|
| + |
|
| + |
let common = EventCommonFields {
|
| + |
version: PROTOCOL_VERSION,
|
| + |
event_type: EventType::Patch,
|
| + |
repository: Repository {
|
| + |
id: *repo,
|
| + |
name: project_info.name().to_string(),
|
| + |
description: project_info.description().to_string(),
|
| + |
private: !rad_repo.identity()?.visibility.is_public(),
|
| + |
default_branch: project_info.default_branch().to_string(),
|
| + |
delegates: rad_repo.delegates()?.iter().copied().collect(),
|
| + |
},
|
| + |
};
|
| + |
|
| + |
let did = Did::from(*from_node);
|
| + |
let author = did_to_author(profile, &did)?;
|
| + |
|
| + |
let patch_cob = patch::Patches::open(&rad_repo)?
|
| + |
.get(patch_id)?
|
| + |
.ok_or(MessageError::Trigger)?;
|
| + |
|
| + |
let revisions: Vec<Revision> = patch_cob
|
| + |
.revisions()
|
| + |
.map(|(rid, r)| {
|
| + |
Ok::<Revision, MessageError>(Revision {
|
| + |
id: rid.into(),
|
| + |
author: did_to_author(profile, r.author().id())?,
|
| + |
description: r.description().to_string(),
|
| + |
base: *r.base(),
|
| + |
oid: r.head(),
|
| + |
timestamp: r.timestamp().as_secs(),
|
| + |
})
|
| + |
})
|
| + |
.collect::<Result<Vec<Revision>, MessageError>>()?;
|
| + |
let patch_author_pk = radicle::crypto::PublicKey::from(author.id);
|
| + |
let patch_latest_revision = patch_cob
|
| + |
.latest_by(&patch_author_pk)
|
| + |
.ok_or(MessageError::Trigger)?;
|
| + |
let patch_base = patch_latest_revision.1.base();
|
| + |
let commits: Vec<Oid> = git_repo
|
| + |
.history(*new_tip)?
|
| + |
.take_while(|c| {
|
| + |
if let Ok(c) = c {
|
| + |
c.id != *patch_base
|
| + |
} else {
|
| + |
false
|
| + |
}
|
| + |
})
|
| + |
.map(|r| r.map(|c| c.id))
|
| + |
.collect::<Result<Vec<Oid>, _>>()?;
|
| + |
|
| + |
let patch = Patch {
|
| + |
id: **patch_id,
|
| + |
author,
|
| + |
title: patch_cob.title().to_string(),
|
| + |
state: State {
|
| + |
status: patch_cob.state().to_string(),
|
| + |
conflicts: match patch_cob.state() {
|
| + |
patch::State::Open { conflicts, .. } => conflicts.to_vec(),
|
| + |
_ => vec![],
|
| + |
},
|
| + |
},
|
| + |
before: *patch_base,
|
| + |
after: *new_tip,
|
| + |
commits,
|
| + |
target: patch_cob.target().head(&rad_repo)?,
|
| + |
labels: patch_cob.labels().map(|l| l.name().to_string()).collect(),
|
| + |
assignees: patch_cob.assignees().collect(),
|
| + |
revisions,
|
| + |
};
|
| + |
|
| + |
Ok(Request::Trigger {
|
| + |
common,
|
| + |
push: None,
|
| + |
patch: Some(PatchEvent {
|
| + |
action: PatchAction::Created,
|
| + |
patch,
|
| + |
}),
|
| + |
})
|
| + |
}
|
| + |
Some(CiEvent::PatchUpdated {
|
| + |
from_node,
|
| + |
repo,
|
| + |
patch: patch_id,
|
| + |
new_tip,
|
| + |
}) => {
|
| + |
let rad_repo = profile.storage.repository(*repo)?;
|
| + |
let git_repo =
|
| + |
radicle_surf::Repository::open(paths::repository(&profile.storage, repo))?;
|
| + |
let project_info = rad_repo.project()?;
|
| + |
|
| + |
let common = EventCommonFields {
|
| + |
version: PROTOCOL_VERSION,
|
| + |
event_type: EventType::Patch,
|
| + |
repository: Repository {
|
| + |
id: *repo,
|
| + |
name: project_info.name().to_string(),
|
| + |
description: project_info.description().to_string(),
|
| + |
private: !rad_repo.identity()?.visibility.is_public(),
|
| + |
default_branch: project_info.default_branch().to_string(),
|
| + |
delegates: rad_repo.delegates()?.iter().copied().collect(),
|
| + |
},
|
| + |
};
|
| + |
|
| + |
let did = Did::from(*from_node);
|
| + |
let author = did_to_author(profile, &did)?;
|
| + |
|
| + |
let patch_cob = patch::Patches::open(&rad_repo)?
|
| + |
.get(patch_id)?
|
| + |
.ok_or(MessageError::Trigger)?;
|
| + |
|
| + |
let revisions: Vec<Revision> = patch_cob
|
| + |
.revisions()
|
| + |
.map(|(rid, r)| {
|
| + |
Ok::<Revision, MessageError>(Revision {
|
| + |
id: rid.into(),
|
| + |
author: did_to_author(profile, r.author().id())?,
|
| + |
description: r.description().to_string(),
|
| + |
base: *r.base(),
|
| + |
oid: r.head(),
|
| + |
timestamp: r.timestamp().as_secs(),
|
| + |
})
|
| + |
})
|
| + |
.collect::<Result<Vec<Revision>, MessageError>>()?;
|
| + |
let patch_author_pk = radicle::crypto::PublicKey::from(author.id);
|
| + |
let patch_latest_revision = patch_cob
|
| + |
.latest_by(&patch_author_pk)
|
| + |
.ok_or(MessageError::Trigger)?;
|
| + |
let patch_base = patch_latest_revision.1.base();
|
| + |
let commits: Vec<Oid> = git_repo
|
| + |
.history(*new_tip)?
|
| + |
.take_while(|c| {
|
| + |
if let Ok(c) = c {
|
| + |
c.id != *patch_base
|
| + |
} else {
|
| + |
false
|
| + |
}
|
| + |
})
|
| + |
.map(|r| r.map(|c| c.id))
|
| + |
.collect::<Result<Vec<Oid>, _>>()?;
|
| + |
|
| + |
let patch = Patch {
|
| + |
id: **patch_id,
|
| + |
author,
|
| + |
title: patch_cob.title().to_string(),
|
| + |
state: State {
|
| + |
status: patch_cob.state().to_string(),
|
| + |
conflicts: match patch_cob.state() {
|
| + |
patch::State::Open { conflicts, .. } => conflicts.to_vec(),
|
| + |
_ => vec![],
|
| + |
},
|
| + |
},
|
| + |
before: *patch_base,
|
| + |
after: *new_tip,
|
| + |
commits,
|
| + |
target: patch_cob.target().head(&rad_repo)?,
|
| + |
labels: patch_cob.labels().map(|l| l.name().to_string()).collect(),
|
| + |
assignees: patch_cob.assignees().collect(),
|
| + |
revisions,
|
| + |
};
|
| + |
|
| + |
Ok(Request::Trigger {
|
| + |
common,
|
| + |
push: None,
|
| + |
patch: Some(PatchEvent {
|
| + |
action: PatchAction::Updated,
|
| + |
patch,
|
| + |
}),
|
| + |
})
|
| + |
}
|
| + |
_ => Err(MessageError::UnknownCiEvent(self.ci_event.unwrap().clone())),
|
| + |
}
|
| + |
}
|
| + |
|
| |
/// Create a [`Request::Trigger`] message.
|
| |
pub fn build_trigger(self) -> Result<Request, MessageError> {
|
| |
let profile = self.profile.ok_or(MessageError::NoProfile)?;
|
| |
Ok(())
|
| |
}
|
| |
}
|
| + |
|
| + |
#[cfg(test)]
|
| + |
pub mod trigger_from_ci_event_tests {
|
| + |
use crate::ci_event::CiEvent;
|
| + |
use crate::msg::{EventType, Request, RequestBuilder};
|
| + |
use radicle::git::RefString;
|
| + |
use radicle::patch::{MergeTarget, Patches};
|
| + |
use radicle::prelude::Did;
|
| + |
use radicle::storage::ReadRepository;
|
| + |
|
| + |
use crate::test::{MockNode, TestResult};
|
| + |
|
| + |
#[test]
|
| + |
fn trigger_push_from_branch_created() -> TestResult<()> {
|
| + |
let mock_node = MockNode::new()?;
|
| + |
let profile = mock_node.profile()?;
|
| + |
|
| + |
let project = mock_node.node().project();
|
| + |
let (_, repo_head) = project.repo.head()?;
|
| + |
let cmt = radicle::test::fixtures::commit(
|
| + |
"my test commit",
|
| + |
&[repo_head.into()],
|
| + |
&project.backend,
|
| + |
);
|
| + |
|
| + |
let ci_event = CiEvent::BranchCreated {
|
| + |
from_node: *profile.id(),
|
| + |
repo: project.id,
|
| + |
branch: RefString::try_from(
|
| + |
"refs/namespaces/$nid/refs/heads/master".replace("$nid", &profile.id().to_string()),
|
| + |
)?,
|
| + |
tip: cmt,
|
| + |
};
|
| + |
|
| + |
let req = RequestBuilder::default()
|
| + |
.profile(&profile)
|
| + |
.ci_event(&ci_event)
|
| + |
.build_trigger_from_ci_event()?;
|
| + |
let Request::Trigger {
|
| + |
common,
|
| + |
push,
|
| + |
patch,
|
| + |
} = req;
|
| + |
|
| + |
assert!(patch.is_none());
|
| + |
assert!(push.is_some());
|
| + |
assert_eq!(common.event_type, EventType::Push);
|
| + |
assert_eq!(common.repository.id, project.id);
|
| + |
assert_eq!(common.repository.name, project.repo.project()?.name());
|
| + |
|
| + |
let push = push.unwrap();
|
| + |
assert_eq!(push.after, cmt);
|
| + |
assert_eq!(push.before, cmt); // in this case of branch creation
|
| + |
assert_eq!(
|
| + |
push.branch,
|
| + |
"master".replace("$nid", &profile.id().to_string())
|
| + |
);
|
| + |
assert_eq!(push.commits, vec![cmt]);
|
| + |
assert_eq!(push.pusher.id, Did::from(profile.id()));
|
| + |
|
| + |
Ok(())
|
| + |
}
|
| + |
|
| + |
#[test]
|
| + |
fn trigger_push_from_branch_updated() -> TestResult<()> {
|
| + |
let mock_node = MockNode::new()?;
|
| + |
let profile = mock_node.profile()?;
|
| + |
|
| + |
let project = mock_node.node().project();
|
| + |
let (_, repo_head) = project.repo.head()?;
|
| + |
let cmt = radicle::test::fixtures::commit(
|
| + |
"my test commit",
|
| + |
&[repo_head.into()],
|
| + |
&project.backend,
|
| + |
);
|
| + |
|
| + |
let ci_event = CiEvent::BranchUpdated {
|
| + |
from_node: *profile.id(),
|
| + |
repo: project.id,
|
| + |
branch: RefString::try_from(
|
| + |
"refs/namespaces/$nid/refs/heads/master".replace("$nid", &profile.id().to_string()),
|
| + |
)?,
|
| + |
old_tip: repo_head,
|
| + |
tip: cmt,
|
| + |
};
|
| + |
|
| + |
let req = RequestBuilder::default()
|
| + |
.profile(&profile)
|
| + |
.ci_event(&ci_event)
|
| + |
.build_trigger_from_ci_event()?;
|
| + |
let Request::Trigger {
|
| + |
common,
|
| + |
push,
|
| + |
patch,
|
| + |
} = req;
|
| + |
|
| + |
assert!(patch.is_none());
|
| + |
assert!(push.is_some());
|
| + |
assert_eq!(common.event_type, EventType::Push);
|
| + |
assert_eq!(common.repository.id, project.id);
|
| + |
assert_eq!(common.repository.name, project.repo.project()?.name());
|
| + |
|
| + |
let push = push.unwrap();
|
| + |
assert_eq!(push.after, cmt);
|
| + |
assert_eq!(push.before, cmt); // in this case of branch creation
|
| + |
assert_eq!(
|
| + |
push.branch,
|
| + |
"master".replace("$nid", &profile.id().to_string())
|
| + |
);
|
| + |
assert_eq!(push.commits, vec![cmt]);
|
| + |
assert_eq!(push.pusher.id, Did::from(profile.id()));
|
| + |
|
| + |
Ok(())
|
| + |
}
|
| + |
|
| + |
#[test]
|
| + |
fn trigger_patch_from_patch_created() -> TestResult<()> {
|
| + |
let mock_node = MockNode::new()?;
|
| + |
let profile = mock_node.profile()?;
|
| + |
|
| + |
let project = mock_node.node().project();
|
| + |
let (_, repo_head) = project.repo.head()?;
|
| + |
let cmt = radicle::test::fixtures::commit(
|
| + |
"my test commit",
|
| + |
&[repo_head.into()],
|
| + |
&project.backend,
|
| + |
);
|
| + |
|
| + |
let node = mock_node.node();
|
| + |
|
| + |
let mut patches = Patches::open(&project.repo)?;
|
| + |
let mut cache = radicle::cob::cache::NoCache;
|
| + |
let patch_cob = patches.create(
|
| + |
"my patch title",
|
| + |
"my patch description",
|
| + |
MergeTarget::Delegates,
|
| + |
repo_head,
|
| + |
cmt,
|
| + |
&[],
|
| + |
&mut cache,
|
| + |
&node.signer,
|
| + |
)?;
|
| + |
|
| + |
let ci_event = CiEvent::PatchCreated {
|
| + |
from_node: *profile.id(),
|
| + |
repo: project.id,
|
| + |
patch: *patch_cob.id(),
|
| + |
new_tip: cmt,
|
| + |
};
|
| + |
|
| + |
let req = RequestBuilder::default()
|
| + |
.profile(&profile)
|
| + |
.ci_event(&ci_event)
|
| + |
.build_trigger_from_ci_event()?;
|
| + |
let Request::Trigger {
|
| + |
common,
|
| + |
push,
|
| + |
patch,
|
| + |
} = req;
|
| + |
|
| + |
assert!(patch.is_some());
|
| + |
assert!(push.is_none());
|
| + |
assert_eq!(common.event_type, EventType::Patch);
|
| + |
assert_eq!(common.repository.id, project.id);
|
| + |
assert_eq!(common.repository.name, project.repo.project()?.name());
|
| + |
|
| + |
let patch = patch.unwrap();
|
| + |
assert_eq!(patch.action.as_str(), "created");
|
| + |
assert_eq!(patch.patch.id.to_string(), patch_cob.id.to_string());
|
| + |
assert_eq!(patch.patch.title, patch_cob.title());
|
| + |
assert_eq!(patch.patch.state.status, patch_cob.state().to_string());
|
| + |
assert_eq!(patch.patch.target, repo_head);
|
| + |
assert_eq!(patch.patch.revisions.len(), 1);
|
| + |
let rev = patch.patch.revisions.first().unwrap();
|
| + |
assert_eq!(rev.id.to_string(), patch_cob.id.to_string());
|
| + |
assert_eq!(rev.base, repo_head);
|
| + |
assert_eq!(rev.oid, cmt);
|
| + |
assert_eq!(rev.author.id, Did::from(profile.id()));
|
| + |
assert_eq!(rev.description, patch_cob.description());
|
| + |
assert_eq!(rev.timestamp, patch_cob.timestamp().as_secs());
|
| + |
assert_eq!(patch.patch.after, cmt);
|
| + |
assert_eq!(patch.patch.before, repo_head);
|
| + |
assert_eq!(patch.patch.commits, vec![cmt]);
|
| + |
|
| + |
Ok(())
|
| + |
}
|
| + |
|
| + |
#[test]
|
| + |
fn trigger_patch_from_patch_updated() -> TestResult<()> {
|
| + |
let mock_node = MockNode::new()?;
|
| + |
let profile = mock_node.profile()?;
|
| + |
|
| + |
let project = mock_node.node().project();
|
| + |
let (_, repo_head) = project.repo.head()?;
|
| + |
let cmt = radicle::test::fixtures::commit(
|
| + |
"my test commit",
|
| + |
&[repo_head.into()],
|
| + |
&project.backend,
|
| + |
);
|
| + |
|
| + |
let node = mock_node.node();
|
| + |
|
| + |
let mut patches = Patches::open(&project.repo)?;
|
| + |
let mut cache = radicle::cob::cache::NoCache;
|
| + |
let patch_cob = patches.create(
|
| + |
"my patch title",
|
| + |
"my patch description",
|
| + |
MergeTarget::Delegates,
|
| + |
repo_head,
|
| + |
cmt,
|
| + |
&[],
|
| + |
&mut cache,
|
| + |
&node.signer,
|
| + |
)?;
|
| + |
|
| + |
let ci_event = CiEvent::PatchUpdated {
|
| + |
from_node: *profile.id(),
|
| + |
repo: project.id,
|
| + |
patch: *patch_cob.id(),
|
| + |
new_tip: cmt,
|
| + |
};
|
| + |
|
| + |
let req = RequestBuilder::default()
|
| + |
.profile(&profile)
|
| + |
.ci_event(&ci_event)
|
| + |
.build_trigger_from_ci_event()?;
|
| + |
let Request::Trigger {
|
| + |
common,
|
| + |
push,
|
| + |
patch,
|
| + |
} = req;
|
| + |
|
| + |
assert!(patch.is_some());
|
| + |
assert!(push.is_none());
|
| + |
assert_eq!(common.event_type, EventType::Patch);
|
| + |
assert_eq!(common.repository.id, project.id);
|
| + |
assert_eq!(common.repository.name, project.repo.project()?.name());
|
| + |
|
| + |
let patch = patch.unwrap();
|
| + |
assert_eq!(patch.action.as_str(), "updated");
|
| + |
assert_eq!(patch.patch.id.to_string(), patch_cob.id.to_string());
|
| + |
assert_eq!(patch.patch.title, patch_cob.title());
|
| + |
assert_eq!(patch.patch.state.status, patch_cob.state().to_string());
|
| + |
assert_eq!(patch.patch.target, repo_head);
|
| + |
assert_eq!(patch.patch.revisions.len(), 1);
|
| + |
let rev = patch.patch.revisions.first().unwrap();
|
| + |
assert_eq!(rev.id.to_string(), patch_cob.id.to_string());
|
| + |
assert_eq!(rev.base, repo_head);
|
| + |
assert_eq!(rev.oid, cmt);
|
| + |
assert_eq!(rev.author.id, Did::from(profile.id()));
|
| + |
assert_eq!(rev.description, patch_cob.description());
|
| + |
assert_eq!(rev.timestamp, patch_cob.timestamp().as_secs());
|
| + |
assert_eq!(patch.patch.after, cmt);
|
| + |
assert_eq!(patch.patch.before, repo_head);
|
| + |
assert_eq!(patch.patch.commits, vec![cmt]);
|
| + |
|
| + |
Ok(())
|
| + |
}
|
| + |
}
|