grit_lib/push_report.rs
1//! Per-reference push results.
2//!
3//! After a push, each requested reference update resolves to one
4//! [`PushRefStatus`] (up to date, updated, or one of the rejection reasons),
5//! carried with its old and new values in a [`PushRefResult`]. Callers render
6//! these themselves; `grit push` maps them to its human, JSON and Markdown output.
7
8use crate::objects::ObjectId;
9
10/// The resolved outcome of a single reference update during a push.
11#[derive(Clone, Debug, PartialEq, Eq)]
12#[cfg_attr(feature = "serde-models", derive(serde::Serialize))]
13#[cfg_attr(feature = "serde-models", serde(rename_all = "snake_case"))]
14pub enum PushRefStatus {
15 /// The reference was already at the requested value (`=`, `[up to date]`).
16 UpToDate,
17 /// A successful update (`*` new, `-` delete, ` ` fast-forward, `+` forced).
18 Ok,
19 /// Client-side rejection: the update is not a fast-forward and `--force`
20 /// was not given (`!`, `[rejected] (non-fast-forward)`).
21 RejectNonFastForward,
22 /// Rejected because the new ref already exists (`!`, `[rejected] (already exists)`).
23 RejectAlreadyExists,
24 /// Rejected because the remote has the ref but we need to fetch first.
25 RejectFetchFirst,
26 /// Rejected because a forced update is required.
27 RejectNeedsForce,
28 /// Rejected because force-with-lease found stale info.
29 RejectStale,
30 /// The remote `receive-pack` declined the update (`!`, `[remote rejected] (<reason>)`).
31 RemoteRejected,
32 /// Client-side rejection: a delete was requested but the ref is absent on the remote
33 /// (`!`, `[rejected] (remote ref does not exist)`).
34 RejectRemoteRefMissing,
35 /// Part of an atomic push that failed because another ref was rejected
36 /// (`!`, `[rejected] (atomic push failed)`).
37 AtomicPushFailed,
38}
39
40impl PushRefStatus {
41 /// Whether this status represents a hard failure (causes a non-zero exit).
42 #[must_use]
43 pub fn is_error(&self) -> bool {
44 !matches!(self, PushRefStatus::UpToDate | PushRefStatus::Ok)
45 }
46}
47
48/// One reference's resolved push result, ready for display.
49#[derive(Clone, Debug)]
50#[cfg_attr(feature = "serde-models", derive(serde::Serialize))]
51pub struct PushRefResult {
52 /// The local ref name (source side), e.g. `refs/heads/next`. `None` for deletions.
53 pub local_ref: Option<String>,
54 /// The remote ref name (destination side), e.g. `refs/heads/next`.
55 pub remote_ref: String,
56 /// Old value of the remote ref (`None`/zero for a new ref).
57 pub old_oid: Option<ObjectId>,
58 /// New value of the remote ref (`None`/zero for a deletion).
59 pub new_oid: Option<ObjectId>,
60 /// Whether this update was a forced (non-fast-forward but `--force`d) update.
61 pub forced: bool,
62 /// Whether this update deletes the remote ref.
63 pub deletion: bool,
64 /// The resolved status.
65 pub status: PushRefStatus,
66 /// Extra reason text (used for `[remote rejected]`).
67 pub message: Option<String>,
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn only_up_to_date_and_ok_are_successes() {
76 assert!(!PushRefStatus::UpToDate.is_error());
77 assert!(!PushRefStatus::Ok.is_error());
78 for status in [
79 PushRefStatus::RejectNonFastForward,
80 PushRefStatus::RejectAlreadyExists,
81 PushRefStatus::RejectFetchFirst,
82 PushRefStatus::RejectNeedsForce,
83 PushRefStatus::RejectStale,
84 PushRefStatus::RemoteRejected,
85 PushRefStatus::RejectRemoteRefMissing,
86 PushRefStatus::AtomicPushFailed,
87 ] {
88 assert!(status.is_error(), "{status:?}");
89 }
90 }
91}