git_xcrypt/lib.rs
1//! Logic behind the `git-xcrypt` binary.
2//!
3//! The crate is split into a library and a thin binary so integration tests can
4//! drive the logic directly instead of only through a subprocess.
5//!
6//! Nothing here may write to `stdout`. On the filter path git treats our
7//! `stdout` as the file content itself, so a stray `println!` silently corrupts
8//! a user's file. Diagnostics go to `stderr`.
9
10use thiserror::Error;
11
12pub mod commands;
13pub mod crypto;
14pub mod git;
15pub mod rules;
16pub mod util;
17
18/// Errors returned by library operations.
19///
20/// The variants line up with the exit codes the binary reports, so a caller can
21/// map an error to a code without inspecting its message.
22#[derive(Debug, Error)]
23pub enum Error {
24 /// Reading the input or writing the output failed.
25 #[error("i/o failure: {0}")]
26 Io(#[from] std::io::Error),
27
28 /// The operating system refused to provide randomness.
29 #[error("could not draw randomness from the operating system: {0}")]
30 Entropy(String),
31
32 /// The content is not a file this build can read.
33 #[error("format error: {0}")]
34 Format(String),
35
36 /// The file belongs to a different repository key.
37 #[error(
38 "this file was encrypted with key {}, but the repository holds key {}",
39 hex(wanted),
40 hex(have)
41 )]
42 KeyMismatch {
43 /// Fingerprint the file asks for.
44 wanted: [u8; crypto::format::KEY_ID_LEN],
45 /// Fingerprint we actually hold.
46 have: [u8; crypto::format::KEY_ID_LEN],
47 },
48
49 /// Authentication failed, or the cipher refused the input.
50 #[error("{0}")]
51 Crypto(String),
52
53 /// The repository is not in a state this command can act on.
54 #[error("{0}")]
55 Config(String),
56
57 /// No repository key is present.
58 #[error("no repository key; run `git-xcrypt init` or `git-xcrypt unlock <key-file>`")]
59 NoKey,
60
61 /// The command line asked for something impossible.
62 #[error("{0}")]
63 Usage(String),
64}
65
66impl Error {
67 /// The process exit code this error reports.
68 ///
69 /// Callers map errors to codes here rather than at each call site, so the
70 /// set stays consistent across every command.
71 #[must_use]
72 pub fn exit_code(&self) -> u8 {
73 match self {
74 Self::Usage(_) | Self::Io(_) | Self::Entropy(_) => util::exit::USAGE,
75 Self::Config(_) => util::exit::CONFIG,
76 Self::NoKey => util::exit::NO_KEY,
77 Self::Format(_) | Self::KeyMismatch { .. } | Self::Crypto(_) => util::exit::FORMAT,
78 }
79 }
80}
81
82/// Result alias for library operations.
83pub type Result<T> = std::result::Result<T, Error>;
84
85/// Renders a key fingerprint the way every user-facing message shows it.
86fn hex(bytes: &[u8]) -> String {
87 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
88}
89
90/// Formats a key fingerprint for display.
91#[must_use]
92pub fn format_key_id(key_id: &[u8; crypto::format::KEY_ID_LEN]) -> String {
93 hex(key_id)
94}