crates/cli/git-ents/src/lib.rs
| 1 | //! `git-ents`: the local root, the CLI-complete milestone |
| 2 | //! (`docs/development-plan.adoc`, phase 6). |
| 3 | //! |
| 4 | //! This crate's one responsibility is composition and porcelain: it wires |
| 5 | //! the four seams every other crate defines a trait for — `RefStore` |
| 6 | //! ([`gix_ref_store`]), the object store (gitoxide's own `Find`/`Write`), |
| 7 | //! `EventSink` ([`ents_receive`]), and `Executor` ([`ents_effect`]) — into |
| 8 | //! two composition roots ([`root`]), and exposes a subcommand surface |
| 9 | //! above them. No business logic lives here that a library crate should |
| 10 | //! own instead: every command module is a thin caller of `ents-gate`, |
| 11 | //! `ents-receive`, `ents-effect`, `ents-anchor`, or `ents-sync`. |
| 12 | //! |
| 13 | //! # Spec coverage |
| 14 | //! |
| 15 | //! From `docs/spec/roots.adoc`: |
| 16 | //! |
| 17 | //! - `roots.composition`, `roots.local` — [`root::LocalRoot`]: the plain |
| 18 | //! CLI's composition root (loose-ref `RefStore`, the local odb, a null |
| 19 | //! `EventSink`, the advisory gate). |
| 20 | //! - `roots.config-isolation` — every trait implementation is selected in |
| 21 | //! [`root`] alone; no command module branches on configuration. |
| 22 | //! - `roots.worktree-update` — [`commands::setup`] sets |
| 23 | //! `receive.denyCurrentBranch=updateInstead` on the local repository, the |
| 24 | //! integration-test-harness edge case that spec section names. |
| 25 | //! |
| 26 | //! The development plan's phase-6 row additionally doubles this crate as |
| 27 | //! the single-node hosted root: [`root::HostedRoot`] and [`hook`] wire |
| 28 | //! loose refs and a real odb behind git's own `receive-pack`, an in-memory |
| 29 | //! `EventSink` reconciled at boot (`receive.reconstructible`), and a |
| 30 | //! `SpriteExecutor`. See those modules' own docs for the design this |
| 31 | //! deployment shape requires — the git-serving-transport case is |
| 32 | //! deliberately not `roots.hosted` (`git-ents-server`, phase 8, which |
| 33 | //! replaces the store itself); it is this same crate's wiring, reused, |
| 34 | //! per the plan's own framing. |
| 35 | //! |
| 36 | //! # Examples |
| 37 | //! |
| 38 | //! An end-to-end local write: enroll an admin member, then use it to |
| 39 | //! enroll a second member, mirroring what `git ents members add` does. |
| 40 | //! |
| 41 | //! ``` |
| 42 | //! use ents_model::{MemberId, Provenance}; |
| 43 | //! use ents_receive::{Identity, Mode, propose_entity}; |
| 44 | //! use git_ents::mutate::outcome_to_result; |
| 45 | //! use git_ents::root::LocalRoot; |
| 46 | //! use git_ents::sign::Signer; |
| 47 | //! use gix_ref_store::RefStoreRead; |
| 48 | //! |
| 49 | //! # let dir = tempfile::tempdir().expect("tempdir"); |
| 50 | //! # gix::init(dir.path()).expect("init"); |
| 51 | //! # let key_path = dir.path().join("id_ed25519"); |
| 52 | //! # { |
| 53 | //! # use ssh_key::private::{Ed25519Keypair, KeypairData}; |
| 54 | //! # let pair = Ed25519Keypair::from_seed(&[3; 32]); |
| 55 | //! # let key = ssh_key::PrivateKey::new(KeypairData::from(pair), "t").expect("well-formed"); |
| 56 | //! # key.write_openssh_file(&key_path, ssh_key::LineEnding::LF).expect("write"); |
| 57 | //! # } |
| 58 | //! let root = LocalRoot::open(dir.path()).expect("opens"); |
| 59 | //! let signer = Signer::load(&key_path).expect("loads"); |
| 60 | //! let actor = gix::actor::Signature { |
| 61 | //! name: "jdc".into(), email: "jdc@ents.test".into(), |
| 62 | //! time: gix::date::Time { seconds: 1_000, offset: 0 }, |
| 63 | //! }; |
| 64 | //! let identity = Identity { actor, author: None, sign: &|payload| signer.sign(payload) }; |
| 65 | //! |
| 66 | //! let member = ents_model::Member::new("jdc", signer.public_openssh(), Provenance::AdminRegistered); |
| 67 | //! let name = ents_model::namespace::member_ref(&MemberId::new("jdc")).expect("valid"); |
| 68 | //! let outcome = propose_entity( |
| 69 | //! &root.refs, &root.objects, &root.events, name.clone(), &member, |
| 70 | //! &identity, "Enroll jdc", root.mode(), |
| 71 | //! ).expect("evaluates"); |
| 72 | //! outcome_to_result(outcome, None).expect("bootstrap admits the first member"); |
| 73 | //! assert!(root.refs.get(name.as_ref()).expect("reads").is_some()); |
| 74 | //! ``` |
| 75 | |
| 76 | pub mod cli; |
| 77 | pub mod commands; |
| 78 | pub mod compose; |
| 79 | pub mod error; |
| 80 | pub mod exe; |
| 81 | pub mod hook; |
| 82 | pub mod mutate; |
| 83 | pub mod package; |
| 84 | pub mod root; |
| 85 | pub mod sign; |
| 86 | |
| 87 | pub use error::{Error, Result}; |