OXYGENHUB

Coverage / crates/hub/src/repos/edits.rs 89.0%

81 of 91 lines run, 12 of 30 functions · run #541 on main · run missed

1
//! Changes the hub makes for a signed-in user (hub spec §6.9, §6.10, §7.6):
2
//! built on the target state, authored by the user's linked identity,
3
//! signed by the hub key as that identity's delegate, and applied through
4
//! the same checks as a push.
5
6
use super::{RepoError, Repos};
7
use crate::repos::RepoName;
8
use oxy_core::hash::{ChangeHash, KeyId};
9
use oxy_core::identity::{Domain, Identity, SecretKey};
10
use oxy_core::repo::{FileEdit, PushReport, Repo};
11
12
type Result<T> = std::result::Result<T, RepoError>;
13
140×
fn fail(msg: impl Into<String>) -> RepoError {
150×
    RepoError::Oxy(oxy_core::repo::RepoError::Msg(msg.into()))
160×
}
17
18
/// Who the change is for and who signs it.
19
pub struct Signer {
20
    /// The user's identity that lists the hub key as a delegate.
21
    pub author: Identity,
22
    pub hub_key: KeyId,
23
    pub hub_secret: SecretKey,
24
    pub hub_identity: Identity,
25
}
26
27
/// What a web edit did.
28
pub struct Committed {
29
    pub change: ChangeHash,
30
    pub bookmark: String,
31
    pub old_state: String,
32
    pub new_state: String,
33
    pub heads: Vec<ChangeHash>,
34
    pub report: PushReport,
35
}
36
37
/// Commit `edits` on top of `base_bookmark`'s state into `bookmark`
38
/// (the same bookmark, or a new one for a proposal).
3932×
pub async fn commit(
4032×
    repos: &Repos,
4132×
    repo: &RepoName,
4232×
    base_bookmark: String,
4332×
    bookmark: String,
4432×
    edits: Vec<FileEdit>,
4532×
    message: String,
4632×
    signer: Signer,
4732×
) -> Result<Committed> {
4832×
    repos
4932×
        .write(repo, move |r: &Repo, _| {
5032×
            let view = r.view()?;
5132×
            let base_heads = match view.bookmarks.get(&base_bookmark) {
5232×
                Some(st) => r.store().get_state(st)?.heads,
530×
                None => return Err(fail(format!("no bookmark {base_bookmark}"))),
54
            };
5532×
            if bookmark != base_bookmark && view.bookmarks.contains_key(&bookmark) {
560×
                return Err(fail(format!("bookmark {bookmark} exists; pick another name")));
5732×
            }
5832×
            let author = signer.author.hash();
5932×
            let change = r.build_change(&base_heads, &edits, &message, author)?;
6031×
            let hash = change.hash();
6131×
            let sig = signer.hub_secret.sign(Domain::Change, &signer.hub_key, &hash);
6231×
            let mut state = r.load_state(&base_heads)?;
6331×
            state.add(change.clone()).map_err(|e| fail(e.to_string()))?;
6431×
            let mut candidates = base_heads.clone();
6531×
            candidates.push(hash);
6631×
            let heads = state.maximal(&candidates);
6731×
            let update = r.host_ref_update(&bookmark, &heads, Some((&signer.hub_key, &signer.hub_secret)))?;
6831×
            let (old_state, new_state) = (update.old_state.to_hex(), update.new_state.to_hex());
6931×
            let report = r.apply_hosted(Some((change, vec![sig])), &[signer.hub_identity.clone(), signer.author.clone()], update)?;
7031×
            if let Some(refused) = report.results.iter().find(|x| !x.ok) {
710×
                return Err(fail(format!("the repository refused the change: {}", refused.reason)));
7231×
            }
7331×
            Ok(Committed { change: hash, bookmark, old_state, new_state, heads, report })
7432×
        })
7532×
        .await
7632×
}
77
78
/// Move `bookmark` to `heads` with an attestation signed by the hub key on
79
/// the user's behalf (landing a proposal under a policy).
8012×
pub async fn move_bookmark(repos: &Repos, repo: &RepoName, bookmark: String, heads: Vec<ChangeHash>, signer: Signer) -> Result<PushReport> {
8112×
    repos
8212×
        .write(repo, move |r: &Repo, _| {
8312×
            let update = r.host_ref_update(&bookmark, &heads, Some((&signer.hub_key, &signer.hub_secret)))?;
8412×
            let report = r.apply_hosted(None, &[signer.hub_identity.clone(), signer.author.clone()], update)?;
8512×
            if let Some(refused) = report.results.iter().find(|x| !x.ok) {
860×
                return Err(fail(format!("the repository refused the update: {}", refused.reason)));
8712×
            }
8812×
            Ok(report)
8912×
        })
9012×
        .await
9112×
}
92
93
/// Land onto `bookmark` the state with `heads`, its conflicts settled by
94
/// `edits` (oxygen#145: union files' adjacent insertions, both sides kept):
95
/// a resolution change on `heads`, authored by the user and signed by the
96
/// hub key as their delegate, and the bookmark moved to it in one update.
97
/// The new heads.
981×
pub async fn land_resolved(
991×
    repos: &Repos,
1001×
    repo: &RepoName,
1011×
    bookmark: String,
1021×
    heads: Vec<ChangeHash>,
1031×
    edits: Vec<FileEdit>,
1041×
    message: String,
1051×
    signer: Signer,
1061×
) -> Result<Vec<ChangeHash>> {
1071×
    repos
1081×
        .write(repo, move |r: &Repo, _| {
1091×
            let change = r.build_change(&heads, &edits, &message, signer.author.hash())?;
1101×
            let hash = change.hash();
1111×
            let sig = signer.hub_secret.sign(Domain::Change, &signer.hub_key, &hash);
1121×
            let mut state = r.load_state(&heads)?;
1131×
            state.add(change.clone()).map_err(|e| fail(e.to_string()))?;
1141×
            let mut candidates = heads.clone();
1151×
            candidates.push(hash);
1161×
            let new_heads = state.maximal(&candidates);
1171×
            let update = r.host_ref_update(&bookmark, &new_heads, Some((&signer.hub_key, &signer.hub_secret)))?;
1181×
            let report = r.apply_hosted(Some((change, vec![sig])), &[signer.hub_identity.clone(), signer.author.clone()], update)?;
1191×
            if let Some(refused) = report.results.iter().find(|x| !x.ok) {
1200×
                return Err(fail(format!("the repository refused the landing: {}", refused.reason)));
1211×
            }
1221×
            Ok(new_heads)
1231×
        })
1241×
        .await
1251×
}