| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 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 | |
|
| 14 | 0× | fn fail(msg: impl Into<String>) -> RepoError { |
| 15 | 0× | RepoError::Oxy(oxy_core::repo::RepoError::Msg(msg.into())) |
| 16 | 0× | } |
| 17 | |
|
| 18 | |
|
| 19 | | pub struct Signer { |
| 20 | | |
| 21 | | pub author: Identity, |
| 22 | | pub hub_key: KeyId, |
| 23 | | pub hub_secret: SecretKey, |
| 24 | | pub hub_identity: Identity, |
| 25 | | } |
| 26 | |
|
| 27 | |
|
| 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 | |
|
| 38 | |
|
| 39 | 31× | pub async fn commit( |
| 40 | 31× | repos: &Repos, |
| 41 | 31× | repo: &RepoName, |
| 42 | 31× | base_bookmark: String, |
| 43 | 31× | bookmark: String, |
| 44 | 31× | edits: Vec<FileEdit>, |
| 45 | 31× | message: String, |
| 46 | 31× | signer: Signer, |
| 47 | 31× | ) -> Result<Committed> { |
| 48 | 31× | repos |
| 49 | 31× | .write(repo, move |r: &Repo, _| { |
| 50 | 31× | let view = r.view()?; |
| 51 | 31× | let base_heads = match view.bookmarks.get(&base_bookmark) { |
| 52 | 31× | Some(st) => r.store().get_state(st)?.heads, |
| 53 | 0× | None => return Err(fail(format!("no bookmark {base_bookmark}"))), |
| 54 | | }; |
| 55 | 31× | if bookmark != base_bookmark && view.bookmarks.contains_key(&bookmark) { |
| 56 | 0× | return Err(fail(format!("bookmark {bookmark} exists; pick another name"))); |
| 57 | 31× | } |
| 58 | 31× | let author = signer.author.hash(); |
| 59 | 31× | let change = r.build_change(&base_heads, &edits, &message, author)?; |
| 60 | 30× | let hash = change.hash(); |
| 61 | 30× | let sig = signer.hub_secret.sign(Domain::Change, &signer.hub_key, &hash); |
| 62 | 30× | let mut state = r.load_state(&base_heads)?; |
| 63 | 30× | state.add(change.clone()).map_err(|e| fail(e.to_string()))?; |
| 64 | 30× | let mut candidates = base_heads.clone(); |
| 65 | 30× | candidates.push(hash); |
| 66 | 30× | let heads = state.maximal(&candidates); |
| 67 | 30× | let update = r.host_ref_update(&bookmark, &heads, Some((&signer.hub_key, &signer.hub_secret)))?; |
| 68 | 30× | let (old_state, new_state) = (update.old_state.to_hex(), update.new_state.to_hex()); |
| 69 | 30× | let report = r.apply_hosted(Some((change, vec![sig])), &[signer.hub_identity.clone(), signer.author.clone()], update)?; |
| 70 | 30× | if let Some(refused) = report.results.iter().find(|x| !x.ok) { |
| 71 | 0× | return Err(fail(format!("the repository refused the change: {}", refused.reason))); |
| 72 | 30× | } |
| 73 | 30× | Ok(Committed { change: hash, bookmark, old_state, new_state, heads, report }) |
| 74 | 31× | }) |
| 75 | 31× | .await |
| 76 | 31× | } |
| 77 | |
|
| 78 | |
|
| 79 | |
|
| 80 | 12× | pub async fn move_bookmark(repos: &Repos, repo: &RepoName, bookmark: String, heads: Vec<ChangeHash>, signer: Signer) -> Result<PushReport> { |
| 81 | 12× | repos |
| 82 | 12× | .write(repo, move |r: &Repo, _| { |
| 83 | 12× | let update = r.host_ref_update(&bookmark, &heads, Some((&signer.hub_key, &signer.hub_secret)))?; |
| 84 | 12× | let report = r.apply_hosted(None, &[signer.hub_identity.clone(), signer.author.clone()], update)?; |
| 85 | 12× | if let Some(refused) = report.results.iter().find(|x| !x.ok) { |
| 86 | 0× | return Err(fail(format!("the repository refused the update: {}", refused.reason))); |
| 87 | 12× | } |
| 88 | 12× | Ok(report) |
| 89 | 12× | }) |
| 90 | 12× | .await |
| 91 | 12× | } |
| 92 | |
|
| 93 | |
|
| 94 | |
|
| 95 | |
|
| 96 | |
|
| 97 | |
|
| 98 | 1× | pub async fn land_resolved( |
| 99 | 1× | repos: &Repos, |
| 100 | 1× | repo: &RepoName, |
| 101 | 1× | bookmark: String, |
| 102 | 1× | heads: Vec<ChangeHash>, |
| 103 | 1× | edits: Vec<FileEdit>, |
| 104 | 1× | message: String, |
| 105 | 1× | signer: Signer, |
| 106 | 1× | ) -> Result<Vec<ChangeHash>> { |
| 107 | 1× | repos |
| 108 | 1× | .write(repo, move |r: &Repo, _| { |
| 109 | 1× | let change = r.build_change(&heads, &edits, &message, signer.author.hash())?; |
| 110 | 1× | let hash = change.hash(); |
| 111 | 1× | let sig = signer.hub_secret.sign(Domain::Change, &signer.hub_key, &hash); |
| 112 | 1× | let mut state = r.load_state(&heads)?; |
| 113 | 1× | state.add(change.clone()).map_err(|e| fail(e.to_string()))?; |
| 114 | 1× | let mut candidates = heads.clone(); |
| 115 | 1× | candidates.push(hash); |
| 116 | 1× | let new_heads = state.maximal(&candidates); |
| 117 | 1× | let update = r.host_ref_update(&bookmark, &new_heads, Some((&signer.hub_key, &signer.hub_secret)))?; |
| 118 | 1× | let report = r.apply_hosted(Some((change, vec![sig])), &[signer.hub_identity.clone(), signer.author.clone()], update)?; |
| 119 | 1× | if let Some(refused) = report.results.iter().find(|x| !x.ok) { |
| 120 | 0× | return Err(fail(format!("the repository refused the landing: {}", refused.reason))); |
| 121 | 1× | } |
| 122 | 1× | Ok(new_heads) |
| 123 | 1× | }) |
| 124 | 1× | .await |
| 125 | 1× | } |