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 | |
| 96 | mod arrived; |
| 97 | mod capture; |
| 98 | mod collisions; |
| 99 | mod gitimport; |
| 100 | mod guard; |
| 101 | mod listing; |
| 102 | mod mirror; |
| 103 | mod note; |
| 104 | mod place; |
| 105 | mod relay; |
| 106 | mod repo; |
| 107 | mod stat; |
| 108 | mod revert; |
| 109 | mod reviewed; |
| 110 | mod sync; |
| 111 | mod tree; |
| 112 | mod verbs; |
| 113 | |
| 114 | pub use ore_store::store::Keep; |
| 115 | use ore_store::keys; |
| 116 | use ore_store::repack; |
| 117 | use ore_store::store::{ |
| 118 | Store, |
| 119 | Verify, |
| 120 | }; |
| 121 | |
| 122 | use crate::repo::{ |
| 123 | Config, |
| 124 | Lock, |
| 125 | Repo, |
| 126 | CONFIG_FILE, |
| 127 | ORE_DIR, |
| 128 | PENDING_FILE, |
| 129 | }; |
| 130 | |
| 131 | use oxedyne_fe2o3_core::prelude::*; |
| 132 | |
| 133 | use std::env; |
| 134 | use std::str::FromStr; |
| 135 | use std::path::{ |
| 136 | Path, |
| 137 | PathBuf, |
| 138 | }; |
| 139 | |
| 140 | |
| 141 | /// What the tool prints when it is asked for nothing it knows. |
| 142 | const USAGE: &str = "\ |
| 143 | ore -- version control whose unit of history is the whole edit |
| 144 | |
| 145 | usage: |
| 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 | |
| 180 | maintenance, 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 | |
| 197 | Every verb but init begins by capturing the working copy, so nothing has to be |
| 198 | staged and nothing is ever lost by the next command. |
| 199 | |
| 200 | Every command that appends anything ends by marking the point it reached, with a |
| 201 | name that is the time it was written: @2026-08-17T04:12:09Z. That keeps the git |
| 202 | mirror on its cheap path, which it can only stay on by never once leaving it, and |
| 203 | it is why a mark somebody chose the name of may not begin with @. A command that |
| 204 | appends nothing writes nothing. The marks are history like everything else, so |
| 205 | they sync; ore back offers the ones a person named. |
| 206 | |
| 207 | Every operation a repository with a key writes is signed, and the signature |
| 208 | covers the operation, its identifier and its parents together. Replaying refuses |
| 209 | a history whose signatures do not hold, naming the operation; who and log mark |
| 210 | each one + verified, ? signed by an unknown key, or - unsigned; and a sync |
| 211 | carries the signatures across and teaches each end the other's public keys, on |
| 212 | first use and with nothing else vouching for them. The secret key lives |
| 213 | unencrypted in .ore/key, mode 0600. init --unsigned makes a repository that |
| 214 | writes unsigned operations, which is what every repository made before signing |
| 215 | existed does, and ore key is how one leaves that state. |
| 216 | |
| 217 | sync takes the root of another Ore repository this machine can reach: a second |
| 218 | checkout, a directory a file synchroniser mirrors, a mounted drive. It is |
| 219 | bidirectional -- both logs end holding the same operations -- and it writes the |
| 220 | working copy it was run from. The other repository's files are written the next |
| 221 | time a verb runs there. |
| 222 | |
| 223 | sync also takes a relay, named https://<host>/<account>/<repository>. A relay is |
| 224 | a machine that holds a log and authors nothing, so two replicas that are never |
| 225 | online together still converge by visiting it once each. Requests are signed with |
| 226 | this replica's key and refused by name where the relay's access list does not |
| 227 | name it. A relay that is unreachable costs the capture this command makes first |
| 228 | and nothing else: nothing is queued, and running the command again is the whole |
| 229 | of the retry. `ore-relay` is the program that runs one. |
| 230 | |
| 231 | sync --pull-only takes what the other end has and hands over nothing, so only |
| 232 | this end moves and the two are left agreeing only if this end was the only one |
| 233 | behind. It says so as it finishes, naming what it kept back. A relay reads a |
| 234 | request that hands nothing over as a read, and asks for push the moment one |
| 235 | carries an operation, refusing the whole request rather than its push half; so a |
| 236 | pull grant is usable by a replica holding work of its own only this way. Reading |
| 237 | another repository without writing to it is the other use: an ordinary sync over |
| 238 | a path captures that working copy and absorbs into its log. |
| 239 | |
| 240 | sync --dry-run answers what a sync would bring and keeps none of it. It hands |
| 241 | nothing over, whatever else was asked, because offering is a write and a question |
| 242 | that writes is not a question; it takes what it is owed into copies, says how |
| 243 | many operations, which files, which authors and what the renderer would notice, |
| 244 | and 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 |
| 246 | speaks is the one exception, and that is this working copy's own work. |
| 247 | |
| 248 | A repository may keep a git mirror. `ore init --mirror <path>` names one, and so |
| 249 | does a \"mirror\" line in .ore/config afterwards; there is no export verb, because |
| 250 | every verb that appends operations brings the mirror current when it finishes. |
| 251 | Each mark becomes a commit, a mark somebody named takes a lightweight tag as |
| 252 | well, work no mark names yet becomes one further commit, and the branch only ever |
| 253 | moves forward. Pushing the mirror to a forge is git's business. |
| 254 | |
| 255 | flags says what the renderer noticed, and goes on saying it, because a flag is a |
| 256 | fact about the history that no later edit retracts. Each one says where its |
| 257 | content is on the line beneath it, as a file and a line number; content that |
| 258 | renders 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>` |
| 260 | puts a flag down, naming any operation the flag names, or `all` for every one of |
| 261 | them; `ore flags --new` then shows only what has not been put down. That record |
| 262 | is this working copy's own: deleting .ore/reviewed makes every flag new again. |
| 263 | |
| 264 | note says something about content rather than about a point in history, which is |
| 265 | what makes it a review comment and not a commit message. The place is named the |
| 266 | way 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 |
| 268 | out of one command can be handed straight to this one. What is remembered is the |
| 269 | content and not the coordinate: edit around the note and it narrows to what |
| 270 | survived, move that content, into another file included, and the note goes with |
| 271 | it, delete the content and the note says the content it was about is gone. A note |
| 272 | is history like anything else, so it syncs, and it is signed. |
| 273 | |
| 274 | log --arrived says what the last sync brought and nothing else: how many |
| 275 | operations, which files they reached, whose work they were, and what the arrival |
| 276 | flagged. One sync is remembered, the last. |
| 277 | |
| 278 | undo is a new edit that restores an earlier state: the history only grows, so |
| 279 | undoing twice is redoing. With no argument it puts back the state from before |
| 280 | the last command that appended anything; with a mark, the state that mark names, |
| 281 | which is `ore back` with the arrival recorded rather than left for the next |
| 282 | verb. |
| 283 | |
| 284 | revert undoes one operation rather than a whole state, by writing its inverse as |
| 285 | new edits plus one record naming what they undid. Some operations have no |
| 286 | inverse and are refused with the reason: a file's deletion, because nothing |
| 287 | revives a file; a mark, a note, a proposal and a remark, because nothing un-says |
| 288 | what somebody said. Restoring deleted text can only put back a copy -- nothing |
| 289 | here un-buries -- so ore who follows the record back and names whoever wrote the |
| 290 | bytes first alongside whoever restored them. |
| 291 | |
| 292 | Before it writes, revert says what goes with the work being undone: what else |
| 293 | was written inside those bytes, what moved them since, and what notes are about |
| 294 | them. It says that whether or not it found any, because no flag reports it |
| 295 | afterwards. Every flag about two authors disagreeing fires only where neither |
| 296 | could see the other, and a revert is written after everything it touches, so it |
| 297 | is concurrent with nothing; reading ore flags afterwards is not a check on what |
| 298 | a 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. |
| 308 | fn 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. |
| 319 | fn 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. |
| 569 | fn 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. |
| 603 | fn 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. |
| 617 | fn 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. |
| 655 | fn with_repo<F>(here: &Path, said: &str, run: F) |
| 656 | -> Outcome<()> |
| 657 | where |
| 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. |
| 677 | fn 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. |
| 699 | fn with_repo_sending<F>(here: &Path, said: &str, run: F) |
| 700 | -> Outcome<()> |
| 701 | where |
| 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. |
| 713 | fn with_repo_keyless<F>(here: &Path, said: &str, allow: bool, keep: Keep, run: F) |
| 714 | -> Outcome<()> |
| 715 | where |
| 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. |
| 827 | fn 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. |
| 836 | fn 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. |
| 853 | fn 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 | } |