Oregami
Repositories/oxedyne/ore

oxedyne/ore/cli/src/sync.rs

30.8 KiB, 102 runs

created by r2848102244:17, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! `ore sync` -- two repositories on one filesystem, brought into agreement.
2//!
3//! The engine's `oxedyne_fe2o3_ore::sync` is a state machine over four messages
4//! and nothing else: it opens no file and holds no connection, so what carries
5//! the bytes is decided here. Version zero carries them the shortest distance
6//! there is. The other repository is a path this machine can already read and
7//! write -- a second checkout, a directory a file synchroniser mirrors, a
8//! mounted drive, a memory stick -- and this process runs both ends of the
9//! conversation.
10//!
11//! That is legitimate rather than a shortcut, because the protocol is
12//! peer-symmetric: there is no client and no server, no message one side may
13//! send that the other may not, and no state either session keeps that the
14//! other's bytes do not put there. Two sessions in one process therefore compute
15//! exactly what two sessions on two machines would. The messages are encoded to
16//! bytes and decoded back even so, so that what is exercised here is the wire
17//! format and not a shortcut around it, and so that the byte counts this verb
18//! reports are the bytes a network transport would carry.
19//!
20//! # Both ends converge, and both ends say so
21//!
22//! A sync is not a fetch and not a push. Each side sends what the other lacks,
23//! each absorbs what arrives, and both logs end holding the same operations,
24//! written to both sets of segments before the command returns.
25//!
26//! # One working copy is written
27//!
28//! The repository the command was run from is rendered and materialised at the
29//! merged state. The other one is not: it may be somebody else's checkout, and
30//! writing into it uninvited is not this tool's business. What is left there
31//! instead is [`Pending`], the marker that lets the next verb run there write the
32//! working copy forward safely -- see [`settle`], and see [`crate::repo`] for why
33//! the marker is needed at all.
34//!
35//! # Both ends can afterwards say what arrived
36//!
37//! Each end writes down where its log stood either side of the exchange, which
38//! is what `ore log --arrived` reads. It is bookkeeping of the tool's own and
39//! not history -- see [`crate::arrived`] -- and it is written at both ends for
40//! the reason the operations reach both: an exchange that only one end could
41//! account for would be a fetch.
42//!
43//! # Provenance crosses with the operations
44//!
45//! Two things travel besides the operations. The public keys each end knows are
46//! written into the other's configuration first, which is what lets a signature
47//! that arrives a moment later be checked; and the operations themselves cross
48//! sealed, this being the substitution the engine's sync module leaves to its
49//! caller -- see [`Party::seal_outgoing`]. What arrives is verified before a
50//! session is allowed to absorb any of it, and a batch holding one operation
51//! whose signature does not hold is refused whole, by name.
52//!
53//! Keys cross by the same route the operations do, which here is a filesystem
54//! both ends can read: each side reads the other's configuration and writes down
55//! what it has not seen. A transport that is not a shared filesystem would need
56//! the bindings in a message of their own, and that is the one part of this verb
57//! a network version would not inherit unchanged.
58//!
59//! # Choosing a mode
60//!
61//! Neither peer knows the difference before it has spoken, so the sketch is
62//! sized from a guess and the protocol is built to survive a bad one: an
63//! undersized table stalls, says so, and the frontier walk answers in the same
64//! turn without losing a round trip. [`choose`] makes the guess.
65//!
66//! # The other transport
67//!
68//! An argument beginning `http://` or `https://` is a relay rather than a path,
69//! and [`crate::relay`] carries the same exchange there. The capture below
70//! happens either way and first, so a network that is not there costs nothing
71//! but the attempt.
72
73use crate::arrived as delta;
74use crate::capture;
75use crate::collisions;
76use crate::keys::{
77 self,
78 Prov,
79 Trust,
80};
81use crate::relay;
82use crate::repo::{
83 Lock,
84 Pending,
85 Repo,
86 CONFIG_FILE,
87 ORE_DIR,
88};
89use crate::tree;
90use crate::verbs;
91
92use ore_store::store::Keep;
93use ore_store::store::{
94 arrived,
95 sealed,
96};
97
98use oxedyne_fe2o3_core::prelude::*;
99use oxedyne_fe2o3_ore::envelope::Envelope;
100use oxedyne_fe2o3_ore::id::OpId;
101use oxedyne_fe2o3_ore::log::OpLog;
102use oxedyne_fe2o3_ore::sync::{
103 Fallback,
104 Message,
105 Mode,
106 Session,
107 Step,
108};
109
110use std::collections::{
111 BTreeMap,
112 BTreeSet,
113 VecDeque,
114};
115use std::path::PathBuf;
116
117
118/// How many messages one exchange may carry before it is called a fault.
119///
120/// A sync is four messages each way at most. The bound is far above that and is
121/// there so that a session that somehow never converges stops rather than
122/// looping.
123pub const MESSAGE_LIMIT: usize = 64;
124
125
126/// What one exchange did.
127pub struct Exchange {
128 /// The mode the sessions ran in.
129 pub mode: Mode,
130 /// Operations the other end did not hold and now does.
131 pub sent: usize,
132 /// Operations this end did not hold and now does.
133 pub received: usize,
134 /// Operations both ends already held.
135 pub shared: usize,
136 /// Messages carried, both directions.
137 pub messages: usize,
138 /// Bytes those messages came to, encoded as they would cross a wire.
139 pub bytes: usize,
140 /// The first fallback either end made, if either made one.
141 pub fell_back: Option<Fallback>,
142}
143
144impl Exchange {
145 /// Reports whether the two logs already agreed.
146 pub fn is_empty(&self) -> bool {
147 self.sent == 0 && self.received == 0
148 }
149
150 /// Writes the mode as a line of text, saying what it was sized for and
151 /// whether it held.
152 pub fn mode_line(&self, held: usize) -> String {
153 let opened = match self.mode {
154 Mode::Walk => fmt!("frontier walk"),
155 Mode::Sketch { estimate, .. } => fmt!(
156 "sketch, sized for {} operation{} of difference over {} held",
157 estimate, if estimate == 1 { "" } else { "s" }, held,
158 ),
159 };
160 match self.fell_back {
161 None => opened,
162 Some(why) => fmt!(
163 "{}, which fell back to the frontier walk: {}", opened, why.why()),
164 }
165 }
166}
167
168
169/// What settling a pending marker did.
170pub enum Settled {
171 /// There was no marker: the working copy and the log agree about who is
172 /// ahead.
173 Nothing,
174 /// The working copy was written forward to the state the log now holds.
175 Written,
176 /// The marker was dropped without writing anything, because the working copy
177 /// had been edited since the sync left it.
178 Overtaken,
179 /// The marker stands, because this is not the working copy's own command.
180 Held(Pending),
181}
182
183
184/// Returns the mode two logs on this machine should open in.
185///
186/// The rule is the engine's, because a relay running the same session asks the
187/// same question of the same two shapes; see [`Mode::between`].
188pub fn choose(local: &OpLog, remote: &OpLog) -> Mode {
189 Mode::between(
190 local.len(),
191 local.frontier().len(),
192 remote.len(),
193 remote.frontier().len(),
194 )
195}
196
197
198/// One end of an exchange: the log being brought up to date, and everything the
199/// provenance of what crosses depends on.
200///
201/// It is a borrow of the parts of a [`Repo`] a sync touches rather than the
202/// repository itself, because both ends are open at once and only one of them
203/// is this working copy.
204pub struct Party<'a> {
205 /// What this end is called, in a message about it.
206 pub name: String,
207 /// The log being brought up to date.
208 pub log: &'a mut OpLog,
209 /// The envelope each sealed operation is held in, read for what this end
210 /// sends and written for what arrives.
211 pub envelopes: &'a mut BTreeMap<OpId, Envelope>,
212 /// What is known about who wrote each operation, extended as they arrive.
213 pub prov: &'a mut BTreeMap<OpId, Prov>,
214 /// The keys this end will accept a signature from, as they stand after the
215 /// two ends have taught each other.
216 pub trust: Trust,
217 /// Whether this end refuses an unsigned operation from the other.
218 pub require_signed: bool,
219}
220
221impl<'a> Party<'a> {
222
223 /// Replaces the bare entries of an outgoing message with the envelopes this
224 /// end holds for them.
225 ///
226 /// The session builds a send set out of the log, and a log holds records
227 /// rather than envelopes, so what it produces is bare. This is the
228 /// substitution the engine's sync module leaves to its caller: provenance
229 /// crosses as provenance, and an operation that was signed when it was
230 /// written is signed when it is sent on.
231 pub fn seal_outgoing(&self, msg: Message)
232 -> Outcome<Message>
233 {
234 let entries = match msg {
235 Message::Send { entries } => entries,
236 other => return Ok(other),
237 };
238 Ok(Message::Send { entries: res!(sealed(entries, self.envelopes)) })
239 }
240
241 /// Checks everything an arriving message carries before a session sees it.
242 ///
243 /// Three things happen here and all of them before anything is absorbed: a
244 /// sealed entry whose signature does not verify is refused by name, a bare
245 /// entry is refused where this end requires signatures, and the envelope of
246 /// everything that passes is kept so it can be written to the segments and
247 /// handed on.
248 pub fn check_incoming(&mut self, msg: &Message, from: &str)
249 -> Outcome<()>
250 {
251 let entries = msg.entries();
252 if entries.is_empty() {
253 return Ok(());
254 }
255 let whence = fmt!("what {} sent", from);
256 let mut bare: Vec<OpId> = Vec::new();
257 // Checked together, and refused one at a time by name if the set does not
258 // hold; see `keys::check_all`.
259 let checked = res!(keys::check_all(entries, &self.trust, &whence, Keep::Envelopes));
260 for got in &checked {
261 if got.prov == Prov::Bare {
262 bare.push(got.rec.id());
263 }
264 }
265 if self.require_signed && !bare.is_empty() {
266 let shown: Vec<String> = bare.iter().take(3).map(|id| fmt!("{}", id)).collect();
267 return Err(err!(
268 "{} requires signed operations, and {} of the {} {} sent are unsigned \
269 ({}{}). Nothing was absorbed. Either that repository signs its work with \
270 `ore key`, or this one stops requiring it.",
271 self.name, bare.len(), entries.len(), from,
272 shown.join(", "),
273 if bare.len() > shown.len() { fmt!(" and {} more", bare.len() - shown.len()) }
274 else { fmt!("") };
275 Invalid, Input, Security, Missing));
276 }
277 // What is written down is what is about to be absorbed. An operation this
278 // end already holds keeps the form it was written in here: upgrading a
279 // bare copy to a sealed one would mean rewriting a segment that is already
280 // on disk, and a log that only ever grows is worth more than that.
281 for got in checked {
282 let id = got.rec.id();
283 if self.log.contains(&id) {
284 continue;
285 }
286 if let Some(env) = got.env {
287 self.envelopes.insert(id, env);
288 }
289 self.prov.insert(id, got.prov);
290 }
291 Ok(())
292 }
293}
294
295
296/// Runs both ends of an exchange until each has said everything it owes.
297///
298/// The two sessions are handed nothing but encoded messages, in the order a
299/// transport would deliver them. Neither reads the other's log and neither reads
300/// the other's session; that they are in one process is a fact about this
301/// transport and about nothing above it.
302///
303/// Provenance rides along with the operations. What one end sends is sealed with
304/// whatever envelopes it holds, and what the other end receives is verified
305/// against the keys it knows before a session is allowed to absorb any of it.
306pub fn exchange(here: &mut Party, there: &mut Party, mode: Mode)
307 -> Outcome<Exchange>
308{
309 let here_len = here.log.len();
310 let there_len = there.log.len();
311 let mut here_sess = Session::new(mode);
312 let mut there_sess = Session::new(mode);
313 let mut to_there: VecDeque<Vec<u8>> = VecDeque::new();
314 let mut to_here: VecDeque<Vec<u8>> = VecDeque::new();
315 let mut messages = 0usize;
316 let mut bytes = 0usize;
317 let mut fell_back: Option<Fallback> = None;
318 let opening = res!(here_sess.open(here.log));
319 to_there.push_back(res!(res!(here.seal_outgoing(opening)).encode()));
320 loop {
321 if messages > MESSAGE_LIMIT {
322 return Err(err!(
323 "An exchange carried {} messages without converging, which no sync \
324 needs; the sessions are not making progress.", messages;
325 Bug, Excessive));
326 }
327 // Whichever queue holds something, oldest first. Which of the two is
328 // drained first decides nothing: a session answers what it is given.
329 let (buf, to) = match to_there.pop_front() {
330 Some(buf) => (buf, true),
331 None => match to_here.pop_front() {
332 Some(buf) => (buf, false),
333 None => break,
334 },
335 };
336 messages += 1;
337 bytes += buf.len();
338 let msg = res!(Message::decode(&buf));
339 // Verified by the end it arrived at, under the keys that end knows, before
340 // the session is given the chance to absorb anything.
341 let turn = if to {
342 res!(there.check_incoming(&msg, &here.name));
343 res!(there_sess.receive(there.log, msg))
344 } else {
345 res!(here.check_incoming(&msg, &there.name));
346 res!(here_sess.receive(here.log, msg))
347 };
348 if let Step::FellBack(why) = turn.step {
349 if fell_back.is_none() {
350 fell_back = Some(why);
351 }
352 }
353 for m in turn.send {
354 if to {
355 // The other end spoke, so the other end's envelopes are what its
356 // words are sealed with.
357 to_here.push_back(res!(res!(there.seal_outgoing(m)).encode()));
358 } else {
359 to_there.push_back(res!(res!(here.seal_outgoing(m)).encode()));
360 }
361 }
362 }
363 if !here_sess.is_converged() || !there_sess.is_converged() {
364 return Err(err!(
365 "An exchange ran out of messages with one end unfinished: this end {}, \
366 the other {}.",
367 if here_sess.is_converged() { "converged" } else { "did not" },
368 if there_sess.is_converged() { "converged" } else { "did not" };
369 Bug, Missing));
370 }
371 // What each end absorbed is what the other end genuinely owed it, whatever
372 // the send set was: the walk is loose and a receiver drops what it holds.
373 let sent = there_sess.ops_absorbed();
374 let received = here_sess.ops_absorbed();
375 if sent > here_len || received > there_len {
376 return Err(err!(
377 "An exchange handed over {} of {} operations one way and {} of {} the \
378 other, which is more than either log held.",
379 sent, here_len, received, there_len;
380 Bug, Range));
381 }
382 // Both ends compute the same intersection from opposite sides, so the two
383 // answers disagreeing means one of the counts is wrong.
384 let shared = here_len - sent;
385 if there_len - received != shared {
386 return Err(err!(
387 "An exchange makes the operations both ends held {} counted from here \
388 and {} counted from the other end.", shared, there_len - received;
389 Bug, Mismatch));
390 }
391 Ok(Exchange {
392 mode,
393 sent,
394 received,
395 shared,
396 messages,
397 bytes,
398 fell_back,
399 })
400}
401
402
403/// Deals with the marker a sync left in a repository it was not run from.
404///
405/// `write` says whether this is that working copy's own command. A verb run
406/// there may write the working copy forward; a peer syncing with it from
407/// somewhere else may not, and leaves the marker where it is.
408///
409/// Writing forward is conditional in either case. The marker records the state
410/// the working copy was left standing at, so a working copy that still renders
411/// that state exactly is one nobody has touched and nothing is lost by writing
412/// it forward. One that does not render it has been edited since, and those
413/// edits are the reader's own work: the marker is dropped, nothing is written,
414/// and the capture that follows records what is there.
415pub fn settle(repo: &mut Repo, write: bool)
416 -> Outcome<Settled>
417{
418 let pending = match res!(repo.read_pending()) {
419 Some(p) => p,
420 None => return Ok(Settled::Nothing),
421 };
422 let standing = res!(tree::at(&repo.log, &pending.frontier));
423 let ignore = res!(capture::Ignore::read(&repo.root));
424 let want = res!(standing.contents());
425 let (disk, _) = res!(capture::scan(&repo.root, &ignore, want.keys()));
426 if disk != want {
427 println!("this working copy was edited after a sync with {} brought operations \
428 into its history, so it is left exactly as it stands", pending.from);
429 res!(repo.clear_pending());
430 return Ok(Settled::Overtaken);
431 }
432 if !write {
433 return Ok(Settled::Held(pending));
434 }
435 let tree = res!(tree::whole(&repo.log));
436 let moved = res!(tree::materialise(&repo.root, &tree, tree::Surplus::Remove));
437 println!("a sync with {} brought operations into this history; the working copy is \
438 now the merged state", pending.from);
439 verbs::report_moved_files(&moved);
440 // The spill goes forward with the working copy. A state written here without
441 // it would leave the buried side of a collision missing at one end of a sync
442 // and present at the other, which is the one asymmetry the whole exchange
443 // exists to remove.
444 collisions::report(&res!(collisions::spill(repo, &tree)));
445 res!(repo.clear_pending());
446 println!();
447 Ok(Settled::Written)
448}
449
450
451/// Resolves the argument to the root of another Ore repository.
452///
453/// The path must be the root itself rather than somewhere inside it. A search up
454/// the ancestors is what [`Repo::find_root`] does for the working copy a person
455/// is standing in, where the intent is unmistakable; for an argument naming
456/// somebody else's repository it would mean a mistyped path quietly syncing with
457/// whatever repository happened to be above it.
458fn remote_root(path: &str)
459 -> Outcome<PathBuf>
460{
461 let root = match std::fs::canonicalize(path) {
462 Ok(p) => p,
463 Err(e) => return Err(err!(e,
464 "The path {:?} could not be resolved. `ore sync` takes the root of another \
465 Ore repository this machine can reach: a second checkout, a directory a \
466 file synchroniser mirrors, or a mounted drive.", path;
467 IO, File, Read)),
468 };
469 if !root.join(ORE_DIR).join(CONFIG_FILE).is_file() {
470 return Err(err!(
471 "{:?} is not an Ore repository: it holds no {}/{}. `ore sync` takes the \
472 root of another repository, not a directory within one and not a directory \
473 above one.", root.display(), ORE_DIR, CONFIG_FILE;
474 Invalid, Input, Missing));
475 }
476 Ok(root)
477}
478
479
480/// `ore sync` -- exchanges operations with another repository, and leaves both
481/// holding the same history.
482///
483/// `taking` is `--pull-only`: take what the other end has and hand over nothing.
484/// The exchange is otherwise the same one, and only this end moves, so the two
485/// are left agreeing only if this end was the only one behind.
486///
487/// It exists for two reasons. A relay classifies a request that hands nothing
488/// over as a read, so a `pull` grant is usable only by a caller offering
489/// nothing: without this a replica holding any work of its own is refused
490/// outright, the whole request and not its push half. And a sync over a local
491/// path writes to the other repository -- it captures that working copy and
492/// absorbs into its log -- which is not what somebody reading a contribution
493/// intends.
494pub fn sync(repo: &mut Repo, path: &str, taking: bool, dry: bool)
495 -> Outcome<()>
496{
497 // Capture first, as every verb does. Whatever this working copy holds is in
498 // the history before anything arrives, so nothing here can be lost by what
499 // does.
500 println!("here:");
501 let what = res!(capture::capture(repo));
502 verbs::report(&what);
503
504 // A relay is the other carrier of the same exchange, and it is reached after
505 // the capture for the reason the capture is first: whatever is here is in the
506 // history before anything else happens, network or no network.
507 if relay::Remote::is_url(path) {
508 // A rehearsal hands nothing over, whatever was asked: offering is a write,
509 // and a question that writes is not a question.
510 return relay::sync(repo, path, taking || dry, dry);
511 }
512
513 let root = res!(remote_root(path));
514 println!();
515 if root == repo.root {
516 println!("{} is this repository, so there is nothing to sync with; a sync \
517 needs two", root.display());
518 return Ok(());
519 }
520 // Taken for the whole command, and released however it ends. This end's lock
521 // was taken before the repository was opened, in main.
522 let _lock = res!(Lock::take(&root.join(ORE_DIR)));
523 // The far end of a path sync hands its operations over as surely as this end
524 // does, so its envelopes are wanted.
525 let mut remote = res!(Repo::open_at(&root, Keep::Envelopes));
526 // How much the other end held before this command touched it, which is what
527 // says at the end whether it owes itself a mark. Taken before its capture and
528 // not after, since a capture is one of the ways this command makes its log
529 // grow.
530 let there_held = remote.log.len();
531 if remote.cfg.replica == repo.cfg.replica {
532 return Err(err!(
533 "{:?} and this repository are both replica {}, which is what copying a \
534 working copy's {} directory leaves behind. Two repositories of one replica \
535 name mint the same operation identifiers for different operations, and \
536 nothing afterwards can tell those apart. Make the second one with `ore \
537 init` and bring this history into it with `ore sync`.",
538 root.display(), repo.cfg.replica, ORE_DIR;
539 Invalid, Data, Conflict));
540 }
541
542 println!("{}:", root.display());
543 // Whether the other end is to be touched at all. Taking without giving and
544 // asking what would arrive are both promises about that repository, and the
545 // first thing an ordinary sync does to it is the loudest: it captures its
546 // working copy, which turns whatever that person had deliberately not
547 // recorded into history under their own name. A relay has no working copy and
548 // needed no such care; a path does.
549 let shy = taking || dry;
550 let standing = if shy {
551 println!("left alone: nothing here is captured, written or recorded");
552 remote.log.frontier()
553 } else {
554 // The other end is captured too, so that its working copy is in its history
555 // before anything arrives there either. The exception is a working copy still
556 // standing where an earlier sync left it: it holds no work of its own to
557 // record, and capturing it would record the difference from the merged state
558 // as an edit.
559 let settled = res!(settle(&mut remote, false));
560 match &settled {
561 Settled::Held(pending) => {
562 println!("captured nothing; its working copy stands at the state an \
563 earlier sync left it at");
564 pending.frontier.clone()
565 },
566 _ => {
567 let theirs = res!(capture::capture(&mut remote));
568 verbs::report(&theirs);
569 remote.log.frontier()
570 },
571 }
572 };
573
574 // The keys cross before the operations do, in both directions, because what
575 // arrives is verified against what is known and a key learned afterwards
576 // would be learned too late. This is trust on first use: each end writes down
577 // what the other says its keys are, and nothing here asks for more than that.
578 // The two configurations are the channel, this transport being a filesystem
579 // both ends can read.
580 println!();
581 let learned_here = repo.learn_keys(&remote.cfg);
582 let learned_there = if shy { 0 } else { remote.learn_keys(&repo.cfg) };
583 // A rehearsal writes nothing at all, the learned keys included: a question
584 // that changed the configuration would be a poor question.
585 if !dry {
586 res!(repo.save_config());
587 }
588 if !shy {
589 res!(remote.save_config());
590 }
591 println!("keys {} known here ({} learned now), {} known there ({} learned now)",
592 repo.cfg.keys.len(), learned_here, remote.cfg.keys.len(), learned_there);
593
594 let here_before: BTreeSet<OpId> = repo.log.iter().map(|rec| rec.id()).collect();
595 let there_before: BTreeSet<OpId> = remote.log.iter().map(|rec| rec.id()).collect();
596 let here_was = repo.log.frontier();
597 let there_was = remote.log.frontier();
598 let here_held = repo.log.len();
599 let mode = choose(&repo.log, &remote.log);
600 // What must not be written is exchanged into copies instead. Both flags are
601 // this one mechanism aimed at different sides: taking without giving spares
602 // the other end, and a rehearsal spares both. The exchange itself is the
603 // exchange, so what is learned is what a real sync would have learned.
604 let mut spare_here = if dry { repo.log.clone() } else { OpLog::default() };
605 let mut spare_here_env = if dry { repo.envelopes.clone() } else { BTreeMap::new() };
606 let mut spare_here_prov = if dry { repo.prov.clone() } else { BTreeMap::new() };
607 let mut spare_there = if shy { remote.log.clone() } else { OpLog::default() };
608 let mut spare_there_env = if shy { remote.envelopes.clone() } else { BTreeMap::new() };
609 let mut spare_there_prov = if shy { remote.prov.clone() } else { BTreeMap::new() };
610 let done = {
611 let (h_log, h_env, h_prov) = if dry {
612 (&mut spare_here, &mut spare_here_env, &mut spare_here_prov)
613 } else {
614 (&mut repo.log, &mut repo.envelopes, &mut repo.prov)
615 };
616 let (t_log, t_env, t_prov) = if shy {
617 (&mut spare_there, &mut spare_there_env, &mut spare_there_prov)
618 } else {
619 (&mut remote.log, &mut remote.envelopes, &mut remote.prov)
620 };
621 let mut here = Party {
622 name: fmt!("{}", repo.root.display()),
623 trust: repo.cfg.trust(),
624 require_signed: repo.cfg.require_signed,
625 log: h_log,
626 envelopes: h_env,
627 prov: h_prov,
628 };
629 let mut there = Party {
630 name: fmt!("{}", remote.root.display()),
631 trust: remote.cfg.trust(),
632 require_signed: remote.cfg.require_signed,
633 log: t_log,
634 envelopes: t_env,
635 prov: t_prov,
636 };
637 res!(exchange(&mut here, &mut there, mode))
638 };
639
640 // Everything that arrived reaches the segments before the command says a word
641 // about what it did, at both ends.
642 let mine = if dry {
643 Vec::new()
644 } else {
645 let mine = arrived(&repo.log, &here_before, &repo.envelopes, &BTreeMap::new());
646 res!(repo.write_entries_from(&mine, None));
647 mine
648 };
649 let _ = &mine;
650 let theirs = if shy {
651 Vec::new()
652 } else {
653 let theirs = arrived(&remote.log, &there_before, &remote.envelopes, &BTreeMap::new());
654 res!(remote.write_entries_from(&theirs, None));
655 theirs
656 };
657 // What each end can afterwards be asked about this exchange. Both ends absorb,
658 // so both ends get a record, and it is written whether anything crossed or
659 // not: a sync that brought nothing is an answer to "what arrived" as much as
660 // one that brought everything.
661 if !dry {
662 res!(delta::record(&repo.root, &fmt!("{}", root.display()),
663 here_was.clone(), repo.log.frontier()));
664 }
665 if !shy {
666 res!(delta::record(&remote.root, &fmt!("{}", repo.root.display()),
667 there_was.clone(), remote.log.frontier()));
668 }
669 // The mark naming where the exchange left the two of them: one operation,
670 // authored here, written into both logs.
671 //
672 // After the exchange rather than before it. A mark written before the
673 // hand-over has only this end's frontier as its parents, so the operations
674 // arriving a moment later are concurrent with it and the frontier becomes two
675 // heads -- the same expensive mirror path, one step later.
676 //
677 // One mark rather than one per end, which was tried and does not converge. If
678 // each end names its own frontier, each ends holding a mark the other has not
679 // got; the next sync carries those two across and each end then writes another
680 // one, so the two logs differ by two operations after every sync there will
681 // ever be and `ore sync` run twice never says it has nothing to carry. It was
682 // measured going 7, 9, 11, 13 operations over four syncs of a repository
683 // nobody was editing. Both ends hold the same operations by this point, so
684 // this end's frontier is also that end's, and one mark parented on it leaves
685 // both standing at the same single mark -- which is the invariant, and a fixed
686 // point: the next sync exchanges nothing and writes nothing.
687 //
688 // Writing into somebody else's repository is not a new liberty here: an
689 // ordinary sync already captures their working copy, which is a far larger
690 // thing to do to it, and every operation this end sends is written there
691 // already. `--pull-only` and `--dry-run` both leave that end alone, which is
692 // what `shy` says, and then this end's mark is the ordinary one every command
693 // ends with.
694 let mut named: Option<OpId> = None;
695 if !shy && remote.log.len() > there_held {
696 if let Some(id) = res!(verbs::auto_mark(repo)) {
697 res!(remote.take(res!(repo.entry_of(&id))));
698 named = Some(id);
699 }
700 }
701 if !theirs.is_empty() {
702 // The other end's own memory of what was done to it, in the manner of the
703 // batch record every command keeps here.
704 res!(remote.record(&fmt!("sync {}", repo.root.display()), there_was));
705 res!(remote.save_pending(&Pending {
706 frontier: standing,
707 from: fmt!("{}", repo.root.display()),
708 }));
709 }
710
711 println!();
712 println!("mode {}", done.mode_line(here_held));
713 if dry {
714 println!("offered nothing: a rehearsal hands nothing over");
715 println!("would take {} operation{} this repository does not hold",
716 done.received, if done.received == 1 { "" } else { "s" });
717 } else if taking {
718 println!("offered nothing, by request");
719 if done.sent > 0 {
720 println!("kept back {} operation{} {} does not hold, which stay here until \
721 a sync offers them",
722 done.sent, if done.sent == 1 { "" } else { "s" }, root.display());
723 }
724 println!("received {} operation{} from replica {}",
725 done.received, if done.received == 1 { "" } else { "s" }, remote.cfg.replica);
726 } else if done.is_empty() {
727 println!("nothing to exchange: both repositories already held the same {} \
728 operation{}", done.shared, if done.shared == 1 { "" } else { "s" });
729 } else {
730 println!("sent {} operation{} to replica {}",
731 done.sent, if done.sent == 1 { "" } else { "s" }, remote.cfg.replica);
732 println!("received {} operation{} from replica {}",
733 done.received, if done.received == 1 { "" } else { "s" }, remote.cfg.replica);
734 println!("shared {} operation{} both already held",
735 done.shared, if done.shared == 1 { "" } else { "s" });
736 }
737 println!("traffic {} message{}, {} byte{}",
738 done.messages, if done.messages == 1 { "" } else { "s" },
739 done.bytes, if done.bytes == 1 { "" } else { "s" });
740 if shy {
741 println!("this repository {} {} operation{}, and {} is as it was",
742 if dry { "holds" } else { "now holds" },
743 if dry { spare_here.len() } else { repo.log.len() },
744 if repo.log.len() == 1 { "" } else { "s" },
745 root.display());
746 } else {
747 println!("both repositories now hold {} operation{}",
748 repo.log.len(), if repo.log.len() == 1 { "" } else { "s" });
749 }
750 // Said out loud, because it is the one operation this command wrote that the
751 // exchange did not carry, and it accounts for the difference between what was
752 // sent, received and shared and what both ends now hold.
753 if let Some(id) = named {
754 println!("named both ends at {}, so each stands at one point rather than \
755 at a frontier the git mirror would have to render whole", id);
756 }
757 println!("frontier {}", verbs::frontier_of(&repo.log.frontier()));
758
759 // A rehearsal stops here: nothing was written, so there is no working copy to
760 // bring forward, and what would have arrived is described from the copies
761 // before they are let go of.
762 if dry {
763 println!();
764 println!("nothing was written: this was a rehearsal, and {} was not touched",
765 root.display());
766 res!(delta::describe(&spare_here, &repo.root, &spare_here_prov,
767 repo.cfg.replica, &here_was, &spare_here.frontier(), "it would bring"));
768 println!();
769 println!("`ore sync {}` takes it", root.display());
770 return Ok(());
771 }
772
773 // This working copy is written to the merged state. The other one is not,
774 // and the marker left there is what lets the next verb run there write it
775 // forward without treading on anything done in the meantime.
776 let tree = res!(tree::whole(&repo.log));
777 let moved = res!(tree::materialise(&repo.root, &tree, tree::Surplus::Remove));
778 println!();
779 println!("the working copy here is now the merged state");
780 verbs::report_moved_files(&moved);
781 if !theirs.is_empty() {
782 println!("{} holds them in its log; its working copy is written the next time \
783 a verb runs there", root.display());
784 }
785 println!();
786 verbs::summarise(repo, &tree)
787}