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