git-ents.gitmain
⌘K
foforge
mod.rs106 lines · 3.4 KB · rusthistorycomment on this file
1//! One module per `git ents` subcommand family — [`crate::cli`]'s
2//! definitions given a body. Each function here is a thin caller into a
3//! library crate: [`crate::exe`] dispatches to these, never the other way
4//! around, so the same logic is callable from a test without a terminal.
5#![expect(
6 clippy::let_underscore_must_use,
7 reason = "rendering an advisory-gate verdict to a writer is best-effort; a broken pipe here \
8 is not actionable"
9)]
10
11pub mod account;
12pub mod bootstrap;
13pub mod comment;
14pub mod effect;
15pub mod inbox;
16pub mod issue;
17pub mod login;
18pub mod lsp;
19pub mod members;
20pub mod redact;
21pub mod review;
22pub mod serve;
23pub mod setup;
24pub mod toolchain;
25
26use std::path::PathBuf;
27
28use gix_hash::ObjectId;
29use gix_object::{CommitRef, Find, Kind};
30
31use crate::error::{Error, Result};
32use crate::root::LocalRoot;
33use crate::sign::Signer;
34
35/// The tree of the commit at `oid` — every command that reads back a typed
36/// entity needs this, and neither `ents_receive` nor `ents_effect` exports
37/// their own copy publicly, so it is a small, shared utility here rather
38/// than duplicated per command module.
39///
40/// # Errors
41///
42/// [`Error::NotFound`] if `oid` is missing or not a commit.
43pub(crate) fn commit_tree(objects: &impl Find, oid: ObjectId) -> Result<ObjectId> {
44 let mut buf = Vec::new();
45 let data = objects
46 .try_find(&oid, &mut buf)
47 .map_err(|source| Error::InvalidArgument(source.to_string()))?
48 .ok_or_else(|| Error::NotFound {
49 what: oid.to_string(),
50 })?;
51 if data.kind != Kind::Commit {
52 return Err(Error::NotFound {
53 what: oid.to_string(),
54 });
55 }
56 let commit = CommitRef::from_bytes(data.data, oid.kind())
57 .map_err(|source| Error::InvalidArgument(source.to_string()))?;
58 Ok(commit.tree())
59}
60
61/// Resolve `--key` (or the repository's `user.signingkey`, or the default
62/// `~/.ssh/id_ed25519`) into a loaded [`Signer`] — the one place every
63/// write-side command turns an optional key path into a usable identity.
64///
65/// # Errors
66///
67/// See [`crate::sign::resolve_key_path`] and [`Signer::load`].
68pub fn signer(root: &LocalRoot, key: Option<PathBuf>) -> Result<Signer> {
69 let repo = gix::open(&root.path)?;
70 let path = crate::sign::resolve_key_path(&repo, key.as_deref())?;
71 Signer::load(&path)
72}
73
74/// The commit author/committer signature every mutation this CLI produces
75/// carries: the current wall-clock time, under a fixed name/email derived
76/// from the signer's own key fingerprint (this crate never depends on
77/// `user.name`/`user.email` being configured, mirroring
78/// `gix-ref-store`'s own reflog-identity rationale).
79#[must_use]
80pub fn actor(signer: &Signer) -> gix::actor::Signature {
81 let seconds = std::time::SystemTime::now()
82 .duration_since(std::time::UNIX_EPOCH)
83 .map(|d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX))
84 .unwrap_or_default();
85 gix::actor::Signature {
86 name: "git-ents".into(),
87 email: format!("{}@git-ents.local", short_fingerprint(signer)).into(),
88 time: gix::date::Time { seconds, offset: 0 },
89 }
90}
91
92fn short_fingerprint(signer: &Signer) -> String {
93 let key = signer.public_openssh();
94 let hex = key
95 .split_whitespace()
96 .nth(1)
97 .unwrap_or(&key)
98 .chars()
99 .take(12)
100 .collect::<String>();
101 if hex.is_empty() {
102 "member".to_owned()
103 } else {
104 hex
105 }
106}