git_bug/entities/issue/snapshot/
history_step.rs

1// git-bug-rs - A rust library for interfacing with git-bug repositories
2//
3// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
4// SPDX-License-Identifier: GPL-3.0-or-later
5//
6// This file is part of git-bug-rs/git-gub.
7//
8// You should have received a copy of the License along with this program.
9// If not, see <https://www.gnu.org/licenses/agpl.txt>.
10
11//! Implementation of the [`HistoryStep`] trait for use in
12//! [`Issues`][`super::Issue`].
13
14use crate::{
15    entities::issue::{
16        Issue,
17        data::{label::Label, status::Status},
18        issue_operation::File,
19    },
20    replica::entity::{
21        id::entity_id::EntityId, identity::IdentityStub,
22        snapshot::timeline::history_step::HistoryStep, timestamp::TimeStamp,
23    },
24};
25
26/// Like a [`Vec`] but with the guarantee that it contains at least one element.
27#[derive(Debug)]
28pub struct NonEmptyVec<T> {
29    first: T,
30    rest: Vec<T>,
31}
32
33impl<T: Clone> Clone for NonEmptyVec<T> {
34    fn clone(&self) -> Self {
35        Self {
36            first: self.first.clone(),
37            rest: self.rest.clone(),
38        }
39    }
40}
41
42impl<T> NonEmptyVec<T> {
43    pub(super) fn new(item: T) -> Self {
44        Self {
45            first: item,
46            rest: vec![],
47        }
48    }
49
50    /// Like `first` for an [`Vec`], but with the guarantee, that a first item always
51    /// exists.
52    pub fn first(&self) -> &T {
53        &self.first
54    }
55
56    /// Like `last` for an [`Vec`], but with the guarantee, that a last item always
57    /// exists.
58    pub fn last(&self) -> &T {
59        self.rest.last().unwrap_or(&self.first)
60    }
61
62    pub(super) fn push(&mut self, item: T) {
63        self.rest.push(item);
64    }
65}
66
67/// This is needed to keep an global order on all the events.
68///
69/// Otherwise, we would know which comment came before the other comments, but
70/// not whether an label change came before an certain comment.
71#[derive(Debug, Clone)]
72pub enum IssueHistoryStep {
73    /// A Comment change in the history.
74    Comment(CommentItem),
75
76    /// A Status change in the history.
77    Status(StatusHistoryStep),
78
79    /// A Label change in the history.
80    Label(LabelHistoryStep),
81
82    /// A Body change in the history.
83    Body(BodyHistoryStep),
84
85    /// A Title change in the history.
86    Title(TitleHistoryStep),
87}
88
89impl HistoryStep for IssueHistoryStep {
90    fn author(&self) -> IdentityStub {
91        match self {
92            IssueHistoryStep::Comment(comment_item) => comment_item.history.first().author,
93            IssueHistoryStep::Status(status_history_step) => status_history_step.author,
94            IssueHistoryStep::Label(label_history_step) => label_history_step.author,
95            IssueHistoryStep::Body(body_history_step) => body_history_step.author,
96            IssueHistoryStep::Title(title_history_step) => title_history_step.author,
97        }
98    }
99
100    fn at(&self) -> TimeStamp {
101        match self {
102            IssueHistoryStep::Comment(comment_item) => comment_item.history.last().at,
103            IssueHistoryStep::Status(status_history_step) => status_history_step.at,
104            IssueHistoryStep::Label(label_history_step) => label_history_step.at,
105            IssueHistoryStep::Body(body_history_step) => body_history_step.at,
106            IssueHistoryStep::Title(title_history_step) => title_history_step.at,
107        }
108    }
109}
110
111/// A Comment in the history.
112#[derive(Debug, Clone)]
113pub struct CommentItem {
114    pub(crate) id: EntityId<Issue>,
115    pub(crate) history: NonEmptyVec<CommentHistoryStep>,
116}
117
118/// One version of this comment in the history.
119#[derive(Debug, Clone)]
120pub struct CommentHistoryStep {
121    /// The author of the change.
122    pub author: IdentityStub,
123
124    /// The new message of the comment.
125    pub message: String,
126
127    /// The new files associated with the comment.
128    pub files: Vec<File>,
129
130    /// When this change happened.
131    pub at: TimeStamp,
132}
133
134/// One version of the status in the history.
135#[derive(Debug, Clone, Copy)]
136pub struct StatusHistoryStep {
137    /// The author of the change.
138    pub author: IdentityStub,
139
140    /// The new status.
141    pub status: Status,
142
143    /// When this change happened.
144    pub at: TimeStamp,
145}
146
147/// One version of the labels in the history.
148#[derive(Debug, Clone)]
149pub struct LabelHistoryStep {
150    /// The author of the change.
151    pub author: IdentityStub,
152
153    /// The labels that have been added.
154    pub added: Vec<Label>,
155
156    /// The labels that have been removed.
157    pub removed: Vec<Label>,
158
159    /// When this change happened.
160    pub at: TimeStamp,
161}
162
163/// One version of the Body in the history.
164#[derive(Debug, Clone)]
165pub struct BodyHistoryStep {
166    /// The author of the change.
167    pub author: IdentityStub,
168
169    /// The new message of the body.
170    pub message: String,
171
172    /// The new files associated with the body.
173    pub files: Vec<File>,
174
175    /// When this change happened.
176    pub at: TimeStamp,
177}
178
179/// One version of the title in the history.
180#[derive(Debug, Clone)]
181pub struct TitleHistoryStep {
182    /// The author of the change.
183    pub author: IdentityStub,
184
185    /// The new title.
186    pub title: String,
187
188    /// When this change happened.
189    pub at: TimeStamp,
190}