Oregami
Repositories/oxedyne/ore

oxedyne/ore/cli/src/main.rs

37.7 KiB, 173 runs

created by r2848102244:13, 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` -- the command line tool for Ore, a version control system whose unit
2//! of history is a whole edit rather than a snapshot.
3//!
4//! The engine is `oxedyne_fe2o3_ore`, which does no I/O at all: it holds the
5//! operation vocabulary, the log, the convergent sequence, the two byte formats
6//! and the git fast-import parser. This tool is the frontend that gives those
7//! parts a filesystem, a working copy and a set of verbs.
8//!
9//! # The verbs
10//!
11//! ```text
12//! ore init [<dir>] create a repository, minting a replica and a key
13//! ore key mint this replica's signing key, or replace it
14//! ore mark <name> [<message>]
15//! capture and name this point in history, and say what
16//! was going on
17//! ore log [--arrived] the history, newest first, or what the last sync
18//! [--auto] delivered; --auto lists the marks this tool wrote as
19//! well as the ones somebody named
20//! ore flags [--new] what the renderer noticed, or "clean"
21//! [--reviewed <op>] put a flag down, so that --new stops showing it
22//! ore who <file> which operation wrote each stretch of a file
23//! ore note <place> <text> say something about the content at a place in a file
24//! ore back <mark> put the working copy back to the state at a mark
25//! ore undo [<mark>] put back an earlier state, and record the return
26//! ore revert <op> write the inverse of one operation, and a record
27//! [--dry-run] saying what it undid
28//! ore import <git-repo> read a git repository, history and all
29//! ore sync <repo|url> exchange operations with another repository.
30//! [--pull-only] take what it has and hand over nothing
31//! [--dry-run] say what a sync would bring, and write nothing
32//! ```
33//!
34//! # Every line knows who wrote it
35//!
36//! A repository with a key seals each operation it writes into an envelope that
37//! covers the operation, the identifier that names it and the parents that place
38//! it. Replaying checks every signature it meets and refuses a history holding
39//! one that does not hold, naming the operation; `ore who` and `ore log` mark
40//! each operation as verified, signed by an unknown key, or unsigned; and a sync
41//! carries the seals across and teaches each end the other's public keys. See
42//! [`keys`] for the scheme, the key file and what trust is being claimed.
43//!
44//! # Capture is implicit
45//!
46//! There is no staging and no commit. Every verb but `init` and `import` begins
47//! by comparing the working copy with what the history says and appending
48//! whatever differs, so nothing a person did between two commands can be lost by
49//! the next one. `import` is the exception because it refuses a history that
50//! holds anything.
51//! `mark` names a point that has already been captured; it does not gather one.
52//!
53//! # Nothing is ever removed
54//!
55//! `back` moves the working copy and leaves the history alone. The log only
56//! grows: going back to an older state and carrying on records the return as
57//! more operations, and both states remain in the history afterwards. `undo` is
58//! the same thing said outright -- it is a new edit that restores an earlier
59//! state, so undoing twice is redoing.
60//!
61//! # A command is a batch
62//!
63//! Every command that appends operations records, beside the log, the frontier
64//! it began at. That is what lets `undo` say what "the last thing this working
65//! copy did" was, which the operation graph cannot: causality is a property of
66//! the graph and a command is a property of whatever ran it. The record is this
67//! working copy's own and is described in [`repo`].
68//!
69//! # Two repositories, and which of them is written
70//!
71//! `sync` is the one verb that touches a repository other than the one it was
72//! run in. Both logs end holding the same operations; only the working copy the
73//! command was run from is written. What is left in the other is a marker saying
74//! its log has moved ahead of its files, which the next verb run there acts on
75//! -- see [`sync::settle`].
76//!
77//! # A git mirror, and no verb for it
78//!
79//! `.ore/config` may name a mirror: a git repository this tool keeps current
80//! with the history, so a project can live in Ore while a git forge still serves
81//! its readers. It is configuration and not a verb, for the reason capture is
82//! not a verb -- a bridge you have to remember is a bridge that is stale when it
83//! matters. Every verb that appends operations brings the mirror up to date when
84//! it finishes, each mark becoming a commit and a tag; see [`mirror`]. A mirror
85//! that cannot be written is reported and does not fail the verb.
86//!
87//! # And the one verb that touches a network
88//!
89//! `sync` again, and only when its argument is a URL. A relay is the same
90//! exchange over a socket: a machine that holds a log, runs the same session over
91//! it that any peer would, and authors nothing. It is what lets two replicas that
92//! are never awake at the same time converge, each by visiting it once. Nothing
93//! else in this tool ever names the network, and a relay that is unreachable
94//! costs the attempt and nothing more -- see [`relay`].
95
96mod arrived;
97mod capture;
98mod collisions;
99mod gitimport;
100mod guard;
101mod listing;
102mod mirror;
103mod note;
104mod place;
105mod relay;
106mod repo;
107mod stat;
108mod revert;
109mod reviewed;
110mod sync;
111mod tree;
112mod verbs;
113
114pub use ore_store::store::Keep;
115use ore_store::keys;
116use ore_store::repack;
117use ore_store::store::{
118 Store,
119 Verify,
120};
121
122use crate::repo::{
123 Config,
124 Lock,
125 Repo,
126 CONFIG_FILE,
127 ORE_DIR,
128 PENDING_FILE,
129};
130
131use oxedyne_fe2o3_core::prelude::*;
132
133use std::env;
134use std::str::FromStr;
135use std::path::{
136 Path,
137 PathBuf,
138};
139
140
141/// What the tool prints when it is asked for nothing it knows.
142const USAGE: &str = "\
143ore -- version control whose unit of history is the whole edit
144
145usage:
146 ore init [<dir>] [--unsigned] [--mirror <path>]
147 create a repository, minting a replica and a key
148 ore key mint this replica's signing key, or replace it
149 [--veil [<key>]] the other key: mint the content key that veils what
150 this repository sends to a relay, print the one it
151 holds, or install one handed over by somebody who does
152 [--veil-key] mint this replica's veil key, which a content key can
153 be wrapped to so that it crosses a relay unread
154 [--wrap <replica>] wrap the content key for a replica whose veil key this
155 repository has learned; the next sync deposits it
156 ore mark <name> [<message>]
157 capture and name this point in history, and say what
158 was going on; the message is everything after the name
159 ore log [--arrived] the history, newest first; --arrived says what the
160 [--auto] last sync delivered and nothing else, and --auto lists
161 the marks this tool wrote as well as the named ones
162 ore flags [--new] what the renderer noticed, or \"clean\"; --new hides
163 [--reviewed <op>] the flags already marked reviewed, and --reviewed
164 marks every flag naming an operation, or all of them
165 ore who <file> which operation wrote each stretch of a file
166 ore note <place> <text> say something about the content at a place in a file,
167 written <file>:<line>, <file>:<first>-<last>, or
168 <file>:<from>..<to> for bytes
169 ore back <mark> put the working copy back to the state at a mark
170 ore undo [<mark>] put back an earlier state, and record the return
171 ore revert <op> write the inverse of one operation, and a record
172 [--dry-run] saying what it undid; --dry-run says what it would
173 write and writes none of it
174 ore import <git-repo> read a git repository, history and all
175 ore sync <repo|url> exchange operations with another repository, or with
176 a relay named https://<host>/<account>/<repository>
177 [--pull-only] take what the other end has and hand over nothing
178 [--dry-run] say what a sync would bring, and write nothing
179
180maintenance, which is not one of the verbs and records no history:
181 ore repack rewrite the segments, keeping every operation and
182 [--pack] every signature exactly as they are; --pack compresses
183 [--segment-bytes <n>]
184 each segment's records together and running it again
185 [--unverified] without --pack takes that back, --segment-bytes says
186 how many bytes of records to put in each segment, and
187 --unverified is what a relay does, holding no keys to
188 check anything with
189 ore repack --verify read every segment the slow way, hashing every record
190 and checking every signature on no verdict, and file
191 fresh ones; moves no byte and writes no history. A
192 verdict goes off after a week, so a command reading a
193 log nothing has swept pays for this itself; the point
194 of running it on a timer is that it costs four in the
195 morning instead of somebody's afternoon
196
197Every verb but init begins by capturing the working copy, so nothing has to be
198staged and nothing is ever lost by the next command.
199
200Every command that appends anything ends by marking the point it reached, with a
201name that is the time it was written: @2026-08-17T04:12:09Z. That keeps the git
202mirror on its cheap path, which it can only stay on by never once leaving it, and
203it is why a mark somebody chose the name of may not begin with @. A command that
204appends nothing writes nothing. The marks are history like everything else, so
205they sync; ore back offers the ones a person named.
206
207Every operation a repository with a key writes is signed, and the signature
208covers the operation, its identifier and its parents together. Replaying refuses
209a history whose signatures do not hold, naming the operation; who and log mark
210each one + verified, ? signed by an unknown key, or - unsigned; and a sync
211carries the signatures across and teaches each end the other's public keys, on
212first use and with nothing else vouching for them. The secret key lives
213unencrypted in .ore/key, mode 0600. init --unsigned makes a repository that
214writes unsigned operations, which is what every repository made before signing
215existed does, and ore key is how one leaves that state.
216
217sync takes the root of another Ore repository this machine can reach: a second
218checkout, a directory a file synchroniser mirrors, a mounted drive. It is
219bidirectional -- both logs end holding the same operations -- and it writes the
220working copy it was run from. The other repository's files are written the next
221time a verb runs there.
222
223sync also takes a relay, named https://<host>/<account>/<repository>. A relay is
224a machine that holds a log and authors nothing, so two replicas that are never
225online together still converge by visiting it once each. Requests are signed with
226this replica's key and refused by name where the relay's access list does not
227name it. A relay that is unreachable costs the capture this command makes first
228and nothing else: nothing is queued, and running the command again is the whole
229of the retry. `ore-relay` is the program that runs one.
230
231sync --pull-only takes what the other end has and hands over nothing, so only
232this end moves and the two are left agreeing only if this end was the only one
233behind. It says so as it finishes, naming what it kept back. A relay reads a
234request that hands nothing over as a read, and asks for push the moment one
235carries an operation, refusing the whole request rather than its push half; so a
236pull grant is usable by a replica holding work of its own only this way. Reading
237another repository without writing to it is the other use: an ordinary sync over
238a path captures that working copy and absorbs into its log.
239
240sync --dry-run answers what a sync would bring and keeps none of it. It hands
241nothing over, whatever else was asked, because offering is a write and a question
242that writes is not a question; it takes what it is owed into copies, says how
243many operations, which files, which authors and what the renderer would notice,
244and lets the copies go. Nothing is written: not the operations, not the record
245--arrived reads, not the keys it learned. The capture this verb makes before it
246speaks is the one exception, and that is this working copy's own work.
247
248A repository may keep a git mirror. `ore init --mirror <path>` names one, and so
249does a \"mirror\" line in .ore/config afterwards; there is no export verb, because
250every verb that appends operations brings the mirror current when it finishes.
251Each mark becomes a commit, a mark somebody named takes a lightweight tag as
252well, work no mark names yet becomes one further commit, and the branch only ever
253moves forward. Pushing the mirror to a forge is git's business.
254
255flags says what the renderer noticed, and goes on saying it, because a flag is a
256fact about the history that no later edit retracts. Each one says where its
257content is on the line beneath it, as a file and a line number; content that
258renders nowhere -- bytes two authors both deleted, an edit an arbitration buried
259-- says so rather than naming a place it is not in. `ore flags --reviewed <op>`
260puts a flag down, naming any operation the flag names, or `all` for every one of
261them; `ore flags --new` then shows only what has not been put down. That record
262is this working copy's own: deleting .ore/reviewed makes every flag new again.
263
264note says something about content rather than about a point in history, which is
265what makes it a review comment and not a commit message. The place is named the
266way flags and who print one -- <file>:<line>, <file>:<first>-<last>, or
267<file>:<from>..<to> where a file's bytes are not lines -- so a coordinate read
268out of one command can be handed straight to this one. What is remembered is the
269content and not the coordinate: edit around the note and it narrows to what
270survived, move that content, into another file included, and the note goes with
271it, delete the content and the note says the content it was about is gone. A note
272is history like anything else, so it syncs, and it is signed.
273
274log --arrived says what the last sync brought and nothing else: how many
275operations, which files they reached, whose work they were, and what the arrival
276flagged. One sync is remembered, the last.
277
278undo is a new edit that restores an earlier state: the history only grows, so
279undoing twice is redoing. With no argument it puts back the state from before
280the last command that appended anything; with a mark, the state that mark names,
281which is `ore back` with the arrival recorded rather than left for the next
282verb.
283
284revert undoes one operation rather than a whole state, by writing its inverse as
285new edits plus one record naming what they undid. Some operations have no
286inverse and are refused with the reason: a file's deletion, because nothing
287revives a file; a mark, a note, a proposal and a remark, because nothing un-says
288what somebody said. Restoring deleted text can only put back a copy -- nothing
289here un-buries -- so ore who follows the record back and names whoever wrote the
290bytes first alongside whoever restored them.
291
292Before it writes, revert says what goes with the work being undone: what else
293was written inside those bytes, what moved them since, and what notes are about
294them. It says that whether or not it found any, because no flag reports it
295afterwards. Every flag about two authors disagreeing fires only where neither
296could see the other, and a revert is written after everything it touches, so it
297is concurrent with nothing; reading ore flags afterwards is not a check on what
298a revert did. --dry-run gives the same account and writes none of it.";
299
300
301/// Turns off the library's own logging, which a command line tool is not a
302/// consumer of.
303///
304/// The default is to trace, which is right for a server watching itself and
305/// wrong for a tool whose output a person is reading: the bytes of an HTTP
306/// exchange are not what somebody who typed `ore sync` asked to see. Everything
307/// this tool wants said, it says itself.
308fn quieten()
309 -> Outcome<()>
310{
311 let mut cfg = log_get_config!();
312 cfg.file = None;
313 log_set_config!(cfg);
314 log_set_level!("error");
315 Ok(())
316}
317
318/// Runs the verb the arguments name.
319fn run(args: &[String])
320 -> Outcome<()>
321{
322 let verb = match args.first() {
323 Some(v) => v.as_str(),
324 None => {
325 println!("{}", USAGE);
326 return Ok(());
327 },
328 };
329 let rest = &args[1..];
330 // The whole command rather than the verb alone, so that the batch record can
331 // say `ore mark second` and not merely `ore mark`.
332 let said = args.join(" ");
333 let here = match env::current_dir() {
334 Ok(d) => d,
335 Err(e) => return Err(err!(e,
336 "The current directory could not be read.";
337 IO, File, Read)),
338 };
339 match verb {
340 "init" => {
341 let signed = !rest.iter().any(|a| a == "--unsigned");
342 let at = rest.iter().position(|a| a == "--mirror");
343 let mirror = match at {
344 Some(i) => match rest.get(i + 1) {
345 Some(path) => Some(path.clone()),
346 None => return Err(err!(
347 "`ore init --mirror` needs one argument: where to keep the git \
348 mirror.";
349 Invalid, Input, Missing)),
350 },
351 None => None,
352 };
353 // The mirror's own path is not the directory to initialise.
354 let taken = at.map(|i| i + 1);
355 let dir = match rest.iter().enumerate()
356 .find(|(i, a)| !a.starts_with("--") && Some(*i) != taken)
357 {
358 Some((_, d)) => PathBuf::from(d),
359 None => here,
360 };
361 verbs::init(&dir, signed, mirror)
362 },
363 "key" => {
364 // `--veil` with nothing after it means mint or show, and with a key
365 // after it means install that one, so the option carries an option.
366 // Another option is not a key: `ore key --veil --veil-key` asks for
367 // two things and gets told so rather than installing "--veil-key" as
368 // a content key.
369 let veiling = match rest.iter().position(|a| a == "--veil") {
370 Some(i) => Some(match rest.get(i + 1) {
371 Some(next) if !next.starts_with("--") => Some(next.clone()),
372 _ => None,
373 }),
374 None => None,
375 };
376 let veil_key = rest.iter().any(|a| a == "--veil-key");
377 let wrapping = match rest.iter().position(|a| a == "--wrap") {
378 Some(i) => match rest.get(i + 1) {
379 Some(next) if !next.starts_with("--") => Some(next.clone()),
380 _ => return Err(err!(
381 "`ore key --wrap` takes the replica to wrap the content key \
382 for, which is the number `ore log` prints beside every \
383 operation that replica wrote.";
384 Invalid, Input, Missing)),
385 },
386 None => None,
387 };
388 let asked = veiling.is_some() as u8 + veil_key as u8 + wrapping.is_some() as u8;
389 if asked > 1 {
390 return Err(err!(
391 "`ore key` does one thing at a time. It has three kinds of key -- \
392 the signing key, the content key that veils what goes to a relay, \
393 and the veil key a content key is wrapped to -- and naming two of \
394 them in one command would leave which happened to which unsaid.";
395 Invalid, Input));
396 }
397 let ask = match (veiling, veil_key, wrapping) {
398 (Some(given), _, _) => verbs::KeyAsk::Veil(given),
399 (_, true, _) => verbs::KeyAsk::VeilKey,
400 (_, _, Some(who)) => verbs::KeyAsk::Wrap(who),
401 _ => verbs::KeyAsk::Signing,
402 };
403 with_repo_keyless(&here, &said, true, Keep::Nothing, |repo| verbs::key(repo, ask))
404 },
405 "mark" => {
406 let name = res!(one(rest, "mark", "a name for this point in history"));
407 // Everything after the name is the message, in the manner of `ore note`:
408 // taking the rest rather than insisting on one quoted argument is the
409 // forgiving choice, and all it costs is that runs of spaces inside an
410 // unquoted message are not preserved.
411 let body = message(&rest[1..]);
412 with_repo(&here, &said, |repo| verbs::mark(repo, &name, body))
413 },
414 "log" => {
415 if rest.iter().any(|a| a == "--arrived") {
416 with_repo_sending(&here, &said, arrived::arrived)
417 } else {
418 let auto = rest.iter().any(|a| a == "--auto");
419 // The reading first, and the log only where the reading will not do.
420 // See [`from_listing`], which says out loud whenever it will not.
421 match res!(from_listing(&here, auto)) {
422 true => Ok(()),
423 false => with_repo(&here, &said, |repo| verbs::log(repo, auto)),
424 }
425 }
426 },
427 "flags" => {
428 let asked = verbs::Asked {
429 new_only: rest.iter().any(|a| a == "--new"),
430 mark: match rest.iter().position(|a| a == "--reviewed") {
431 Some(i) => match rest.get(i + 1) {
432 Some(which) => Some(which.clone()),
433 None => return Err(err!(
434 "`ore flags --reviewed` needs one argument: an operation a \
435 flag names, or `{}` for every flag there is.", reviewed::ALL;
436 Invalid, Input, Missing)),
437 },
438 None => None,
439 },
440 };
441 with_repo(&here, &said, |repo| verbs::flags(repo, &asked))
442 },
443 "who" => {
444 let path = res!(one(rest, "who", "the file to account for"));
445 with_repo(&here, &said, |repo| verbs::who(repo, &path))
446 },
447 "note" => {
448 let asked = res!(note::Asked::read(rest));
449 with_repo(&here, &said, |repo| note::note(repo, &asked))
450 },
451 "back" => {
452 let name = res!(one(rest, "back", "the mark to go back to"));
453 with_repo(&here, &said, |repo| verbs::back(repo, &name))
454 },
455 "undo" => {
456 let mark = rest.first().cloned();
457 with_repo(&here, &said, |repo| verbs::undo(repo, mark.as_deref()))
458 },
459 "revert" => {
460 let asked = res!(revert::Asked::read(rest));
461 with_repo(&here, &said, |repo| revert::revert(repo, &asked))
462 },
463 "import" => {
464 let path = res!(one(rest, "import", "the git repository to read"));
465 with_repo(&here, &said, |repo| gitimport::import(repo, &PathBuf::from(path)))
466 },
467 "sync" => {
468 let taking = rest.iter().any(|a| a == "--pull-only");
469 let dry = rest.iter().any(|a| a == "--dry-run");
470 let named: Vec<String> = rest.iter()
471 .filter(|a| *a != "--pull-only" && *a != "--dry-run")
472 .cloned()
473 .collect();
474 let path = res!(one(&named, "sync",
475 "the other repository, or the URL of a relay, to sync with"));
476 with_repo_sending(&here, &said, |repo| sync::sync(repo, &path, taking, dry))
477 },
478 // NOT one of the verbs, and listed apart from them: a repack records no
479 // history, answers no question about the work, and is the one command
480 // here that opens the repository WITHOUT capturing the working copy.
481 // Writing an operation into a history it was about to rewrite is the
482 // worst thing this of all commands could do.
483 "repack" => {
484 // A sweep and a rewrite are both maintenance on the container and
485 // neither records history, which is why they share a word. They are
486 // not the same job: this one moves no byte and only says whether the
487 // bytes that are there are the bytes that were written.
488 if rest.iter().any(|a| a == "--verify") {
489 let dir = match rest.iter().find(|a| !a.starts_with("--")) {
490 Some(d) => PathBuf::from(d),
491 None => here,
492 };
493 return verbs::sweep(&dir, rest.iter().any(|a| a == "--unverified"));
494 }
495 let mut plan = repack::Plan::default();
496 let at = rest.iter().position(|a| a == "--segment-bytes");
497 if let Some(i) = at {
498 let said = res!(rest.get(i + 1).ok_or_else(|| err!(
499 "`ore repack --segment-bytes` needs a number of bytes after it.";
500 Invalid, Input, Missing)));
501 plan.segment_bytes = match said.parse::<u64>() {
502 Ok(n) if n > 0 => n,
503 _ => return Err(err!(
504 "`ore repack --segment-bytes` wants a positive number of bytes, \
505 and was given {:?}.", said;
506 Invalid, Input)),
507 };
508 }
509 let taken = at.map(|i| i + 1);
510 let dir = match rest.iter().enumerate()
511 .find(|(i, a)| !a.starts_with("--") && Some(*i) != taken)
512 {
513 Some((_, d)) => PathBuf::from(d),
514 None => here,
515 };
516 // Both directions are a repack: a store packed today is unpacked by
517 // running it the other way, which is what makes the choice revisable
518 // rather than a door. The default is plain, because that is what an
519 // append writes and a default that changed the bytes would be a
520 // decision taken by whoever typed the shortest command.
521 plan.packing = match rest.iter().any(|a| a == "--pack") {
522 true => repack::Packing::Packed,
523 false => repack::Packing::Plain,
524 };
525 verbs::repack(&dir, rest.iter().any(|a| a == "--unverified"), &plan)
526 },
527 "help" | "-h" | "--help" => {
528 println!("{}", USAGE);
529 Ok(())
530 },
531 other => Err(err!(
532 "There is no verb {:?}. There are twelve: init, key, mark, log, flags, \
533 who, note, back, undo, revert, import, sync. There is also `ore repack`, \
534 which is maintenance on the container and not one of them.", other;
535 Invalid, Input)),
536 }
537}
538
539/// Answers `ore log` from the reading beside the log, where that reading still
540/// describes the store and the working copy has not moved.
541///
542/// A listing needs no operation's content: how many there are, what the frontier
543/// is, how many are signed, and every mark with the work counted between them.
544/// Reading the store to say that costs 465 ms and 161 MB on a history of 35,408
545/// operations, and 80% of it is recomputing the SHA-256 each record is framed
546/// with. So the reading is kept, under a cursor that says which bytes it was
547/// taken from, and put back to the store before a word of it is believed. See
548/// [`listing`].
549///
550/// # It says when it did not take this path, every time
551///
552/// A cache that quietly falls back is a cache nobody can tell is not working, and
553/// this feature shipped inert on the server side of the same idea earlier the
554/// same day. So every way of not using the reading writes a line on standard
555/// error naming which -- there is no reading, the store has moved on, the working
556/// copy has changed, a sync is waiting to be settled, the git mirror is behind --
557/// and the whole log is then read exactly as it always was.
558///
559/// # What it must not skip
560///
561/// Everything [`with_repo`] does after the verb is about a command that appended
562/// something: the automatic mark, the batch record, the working copy index being
563/// carried forward, the git mirror. This path is taken only where nothing will be
564/// appended -- the working copy is what the history says, and a listing writes
565/// nothing -- so each of those is not skipped but empty. The two that are not
566/// automatically empty are asked outright: a sync marker waiting to be settled,
567/// and a git mirror standing behind the history. Either sends the command down
568/// the whole path.
569fn from_listing(here: &Path, auto: bool)
570 -> Outcome<bool>
571{
572 let root = res!(Repo::find_root(here));
573 let dir = root.join(ORE_DIR);
574 let _lock = res!(Lock::take(&dir));
575 let cfg = res!(Config::read(&dir));
576 let store = Store::at(&dir);
577 let held = match res!(listing::current(&dir, &store)) {
578 listing::Held::Current(l) => l,
579 listing::Held::Stale(why) => return whole_log(&why),
580 };
581 if dir.join(PENDING_FILE).is_file() {
582 return whole_log("a sync has brought operations here that the working copy \
583 has not been put to yet");
584 }
585 if !res!(mirror::settled(&dir, &cfg, &held.frontier)) {
586 return whole_log("the git mirror stands behind the history");
587 }
588 let ignore = res!(capture::Ignore::read(&root));
589 if !res!(capture::unchanged(&root, &dir, &ignore, &held.frontier)) {
590 return whole_log("the working copy is not where the index last measured it");
591 }
592 // A repository with no signing key says so once per command, wherever the
593 // command was answered from.
594 let signer = res!(keys::Signing::read(&dir));
595 res!(unsigned_note(&root, &cfg, &signer, false));
596 verbs::report(&capture::Captured::default());
597 println!();
598 res!(verbs::recount(&held, &root, cfg.replica, auto));
599 Ok(true)
600}
601
602/// Says that the reading was not used, and why, and asks for the whole log.
603fn whole_log(why: &str)
604 -> Outcome<bool>
605{
606 eprintln!("ore: the history listing was not used and the whole log was read, \
607 because {}", why);
608 Ok(false)
609}
610
611/// Says once, for the whole command, that this repository signs nothing.
612///
613/// A person who has not minted a key is one person, and telling them a hundred
614/// times is telling them nothing. `allow` is set only by `ore key` -- it is the
615/// verb the refusal tells a person to run, and a policy that forbade its own
616/// remedy would be a repository nobody could rescue.
617fn unsigned_note(
618 root: &Path,
619 cfg: &Config,
620 signer: &Option<keys::Signing>,
621 allow: bool,
622)
623 -> Outcome<()>
624{
625 if signer.is_some() {
626 return Ok(());
627 }
628 if cfg.require_signed && !allow {
629 return Err(err!(
630 "{:?} requires every operation to be signed and holds no signing key, so \
631 no verb can write anything. Run `ore key` to mint one, or set \
632 require_signed to false in {:?}.",
633 root, root.join(ORE_DIR).join(CONFIG_FILE);
634 Invalid, Configuration, Missing, Key));
635 }
636 println!("note: this repository is unsigned, so its operations say who wrote \
637 them only as far as you trust the file they are in; `ore key` mints a \
638 signing key");
639 Ok(())
640}
641
642/// Opens the repository, runs a verb against it, and records the operations
643/// that verb appended as one batch.
644///
645/// The batch is recorded whether the verb succeeded or not, because the
646/// operations it appended are in the log either way: a record reaches the
647/// segment the moment it is authored, so a command that fails part way leaves
648/// behind exactly what it had already announced. The verb's own failure is what
649/// is reported, the batch record being a convenience and the history being the
650/// thing that matters.
651///
652/// The lock is taken before the repository is opened rather than after, since
653/// what it guards against is another command appending to the segments this one
654/// is about to replay.
655fn with_repo<F>(here: &Path, said: &str, run: F)
656 -> Outcome<()>
657where
658 F: FnOnce(&mut Repo) -> Outcome<()>,
659{
660 with_repo_keyless(here, said, false, Keep::Nothing, run)
661}
662
663/// Reads back what a command just appended, so that the cursor names those bytes
664/// too.
665///
666/// **The records are reconciled, not discarded.** The log already holds them --
667/// they went into it as they were authored -- so what comes off the disk is put
668/// to the one question that says whether the read and the writing agree: are
669/// there exactly as many new operations on the disk as this command added? A
670/// cursor kept over bytes that said something else would be a listing filed
671/// against a store it was not taken from, which is the whole fault
672/// [`ore_store::store::Consumed`] exists to prevent, arrived at by a shorter
673/// road.
674///
675/// Where they disagree the cursor is left exactly where it was, which the caller
676/// reads as a store that has moved, and the listing is thrown away.
677fn carry_cursor(repo: &mut Repo, keep: Keep, appended: usize)
678 -> Outcome<()>
679{
680 let trust = repo.cfg.trust();
681 let got = res!(repo.store.read_since(&repo.cursor, Verify::Signatures(&trust), keep));
682 if got.records.len() != appended {
683 return Err(err!(
684 "This command appended {} operation{} and reading the segments back \
685 found {}. The reading is not carried past bytes it cannot account for.",
686 appended, if appended == 1 { "" } else { "s" }, got.records.len();
687 Invalid, Data, Mismatch));
688 }
689 repo.cursor = got.cursor;
690 Ok(())
691}
692
693/// As [`with_repo`], for a verb that will hand operations to another replica.
694///
695/// An envelope is the whole encoded record a second time, so a replay that keeps
696/// every one holds a second copy of the log. Only `sync`, the relay commands and
697/// `log --arrived` ever look at them, and every other verb was paying 47.8 MB on
698/// the history this was measured against for something it never read.
699fn with_repo_sending<F>(here: &Path, said: &str, run: F)
700 -> Outcome<()>
701where
702 F: FnOnce(&mut Repo) -> Outcome<()>,
703{
704 with_repo_keyless(here, said, false, Keep::Envelopes, run)
705}
706
707/// As [`with_repo`], with a say in whether a keyless repository may run the
708/// verb at all where the configuration requires signatures.
709///
710/// Only `ore key` sets `allow` -- it is the verb the refusal tells a person to
711/// run, and a policy that forbade its own remedy would be a repository nobody
712/// could rescue.
713fn with_repo_keyless<F>(here: &Path, said: &str, allow: bool, keep: Keep, run: F)
714 -> Outcome<()>
715where
716 F: FnOnce(&mut Repo) -> Outcome<()>,
717{
718 let root = res!(Repo::find_root(here));
719 let _lock = res!(Lock::take(&root.join(ORE_DIR)));
720 let mut repo = res!(Repo::open_at(&root, keep));
721 res!(unsigned_note(&root, &repo.cfg, &repo.signer, allow));
722 // A sync run from elsewhere leaves the log here ahead of the working copy,
723 // and this is the next verb it spoke of.
724 res!(sync::settle(&mut repo, true));
725 let before = repo.log.frontier();
726 let held = repo.log.len();
727 let mut ran = run(&mut repo);
728 // The mark that ends every command that appended anything, written before the
729 // mirror is looked at rather than after: a mirror shown an unmarked frontier
730 // takes its expensive path, and having taken it once it takes it ever after.
731 // A command that appended nothing writes nothing, so asking a question costs
732 // an operation no more than it did before. See [`verbs::auto_mark`].
733 if ran.is_ok() && repo.log.len() > held {
734 if let Err(e) = verbs::auto_mark(&mut repo) {
735 ran = Err(e);
736 }
737 }
738 // The index the capture left survives a command that only marked -- which is
739 // every command that appended anything, since each one ends with a mark. The
740 // mark moves the frontier and moves nothing else, so what is stale about the
741 // index is one field, and only that field is rewritten: every file fact stays
742 // exactly as it was measured. See [`stat::restamp`].
743 if ran.is_ok() {
744 let now = repo.log.frontier();
745 if let Err(e) = stat::restamp(&repo.dir, &before, &now,
746 repo.log.iter().skip(held).map(|rec| &rec.op))
747 {
748 eprintln!("ore: the working copy index was not brought forward, and the \
749 history is unaffected: {}", e.plain());
750 }
751 }
752 // A command that appended has moved the store past the cursor it read under,
753 // so it reads the few bytes it wrote back and carries the cursor over them.
754 // That is what `stat::restamp` does for the working copy index, done the only
755 // way a cursor can be: by reading. A cursor is what the read that stopped
756 // there says, never what a caller worked out, and an append is a write rather
757 // than a read however well it knows what it put down.
758 //
759 // It is cheap because it is only the tail: every sealed segment the cursor
760 // names is skipped unread. Without it a mark left no listing at all and the
761 // next command paid a whole read -- 0.335 s on the fe2o3 history against
762 // 0.02 s from a listing -- and `ore mark` now runs on every commit in every
763 // tree on this machine.
764 //
765 // Whether the cursor still names the store, which decides whether anything
766 // derived under it may be kept. A command that appended nothing never moved
767 // it; one that appended has just been asked to carry it, and the answer is
768 // that attempt's own, not something worked out afterwards.
769 let mut carried = repo.log.frontier() == before;
770 if ran.is_ok() && !carried {
771 let appended = repo.log.len() - held;
772 carried = match carry_cursor(&mut repo, keep, appended) {
773 Ok(()) => true,
774 Err(e) => {
775 // Not the command's failure. Nothing derived is kept, exactly as
776 // nothing was before any of this, and the next command reads the
777 // whole log.
778 eprintln!("ore: the reading was not carried past what this command \
779 wrote, so the next command will read the whole log: {}", e.plain());
780 false
781 },
782 };
783 }
784 // The reading this command took, kept beside the log so that the next command
785 // to want one need not take it again -- and only where the cursor it was taken
786 // under still names the store. Derived state is written at a moment it is
787 // known to be true or it is not written. See [`listing`].
788 //
789 // Derived state, so a working copy that cannot be written to is one that reads
790 // the log every time, which is what every working copy did until this was
791 // written.
792 if ran.is_ok() {
793 match carried {
794 true => {
795 if let Err(e) = listing::Listing::of(&repo).keep(&repo.dir) {
796 eprintln!("ore: the history listing was not kept, and the history is \
797 unaffected: {}", e.plain());
798 }
799 },
800 false => listing::Listing::forget(&repo.dir),
801 }
802 }
803 let kept = repo.record(said, before);
804 // The mirror is brought current after the verb and after the record, and a
805 // mirror that cannot be written is said out loud rather than failing the
806 // command: the history is the thing that matters, and a full disk under
807 // somebody's backup mount must not stop an edit being recorded.
808 if ran.is_ok() {
809 match mirror::update(&repo) {
810 Ok(Some(report)) => verbs::report_mirror(&report),
811 Ok(None) => (),
812 Err(e) => eprintln!("ore: the git mirror was not brought \
813 current, and the history is unaffected: {}", e.plain()),
814 }
815 }
816 match ran {
817 Err(e) => Err(e),
818 Ok(()) => kept,
819 }
820}
821
822/// Joins what a verb was told after its named argument into one message, or
823/// nothing where it was told nothing.
824///
825/// Bytes rather than a string, because that is what the operation carries: the
826/// history does not decode what somebody wrote about their own work.
827fn message(rest: &[String]) -> Option<Vec<u8>> {
828 let text = rest.join(" ");
829 match text.trim().is_empty() {
830 true => None,
831 false => Some(text.into_bytes()),
832 }
833}
834
835/// Takes the one argument a verb needs, or says what is missing.
836fn one(rest: &[String], verb: &str, what: &str)
837 -> Outcome<String>
838{
839 match rest.first() {
840 Some(v) => Ok(v.clone()),
841 None => Err(err!(
842 "`ore {}` needs one argument: {}.", verb, what;
843 Invalid, Input, Missing)),
844 }
845}
846
847/// Runs the verb and reports a failure on standard error.
848///
849/// The plain form of the error is used rather than the display form, which
850/// carries ANSI colour and the file and line of every frame: what a person
851/// wants on a failed command is the sentence the code wrote, outermost context
852/// first and innermost detail last.
853fn main() {
854 let args: Vec<String> = env::args().skip(1).collect();
855 if let Err(e) = quieten() {
856 eprintln!("ore: {}", e.plain());
857 std::process::exit(1);
858 }
859 match run(&args) {
860 Ok(()) => (),
861 Err(e) => {
862 eprintln!("ore: {}", e.plain());
863 std::process::exit(1);
864 },
865 }
866}