git-bug 0.2.4

A rust library for interfacing with git-bug repositories
Documentation
// git-bug-rs - A rust library for interfacing with git-bug repositories
//
// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of git-bug-rs/git-gub.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/agpl.txt>.

//! Implementation of the [`HistoryStep`] trait for use in
//! [`Issues`][`super::Issue`].

use crate::{
    entities::issue::{
        Issue,
        data::{label::Label, status::Status},
        issue_operation::File,
    },
    replica::entity::{
        id::entity_id::EntityId, identity::IdentityStub,
        snapshot::timeline::history_step::HistoryStep, timestamp::TimeStamp,
    },
};

/// Like a [`Vec`] but with the guarantee that it contains at least one element.
#[derive(Debug)]
pub struct NonEmptyVec<T> {
    first: T,
    rest: Vec<T>,
}

impl<T: Clone> Clone for NonEmptyVec<T> {
    fn clone(&self) -> Self {
        Self {
            first: self.first.clone(),
            rest: self.rest.clone(),
        }
    }
}

impl<T> NonEmptyVec<T> {
    pub(super) fn new(item: T) -> Self {
        Self {
            first: item,
            rest: vec![],
        }
    }

    /// Like `first` for an [`Vec`], but with the guarantee, that a first item always
    /// exists.
    pub fn first(&self) -> &T {
        &self.first
    }

    /// Like `last` for an [`Vec`], but with the guarantee, that a last item always
    /// exists.
    pub fn last(&self) -> &T {
        self.rest.last().unwrap_or(&self.first)
    }

    pub(super) fn push(&mut self, item: T) {
        self.rest.push(item);
    }
}

/// This is needed to keep an global order on all the events.
///
/// Otherwise, we would know which comment came before the other comments, but
/// not whether an label change came before an certain comment.
#[derive(Debug, Clone)]
pub enum IssueHistoryStep {
    /// A Comment change in the history.
    Comment(CommentItem),

    /// A Status change in the history.
    Status(StatusHistoryStep),

    /// A Label change in the history.
    Label(LabelHistoryStep),

    /// A Body change in the history.
    Body(BodyHistoryStep),

    /// A Title change in the history.
    Title(TitleHistoryStep),
}

impl HistoryStep for IssueHistoryStep {
    fn author(&self) -> IdentityStub {
        match self {
            IssueHistoryStep::Comment(comment_item) => comment_item.history.first().author,
            IssueHistoryStep::Status(status_history_step) => status_history_step.author,
            IssueHistoryStep::Label(label_history_step) => label_history_step.author,
            IssueHistoryStep::Body(body_history_step) => body_history_step.author,
            IssueHistoryStep::Title(title_history_step) => title_history_step.author,
        }
    }

    fn at(&self) -> TimeStamp {
        match self {
            IssueHistoryStep::Comment(comment_item) => comment_item.history.last().at,
            IssueHistoryStep::Status(status_history_step) => status_history_step.at,
            IssueHistoryStep::Label(label_history_step) => label_history_step.at,
            IssueHistoryStep::Body(body_history_step) => body_history_step.at,
            IssueHistoryStep::Title(title_history_step) => title_history_step.at,
        }
    }
}

/// A Comment in the history.
#[derive(Debug, Clone)]
pub struct CommentItem {
    pub(crate) id: EntityId<Issue>,
    pub(crate) history: NonEmptyVec<CommentHistoryStep>,
}

/// One version of this comment in the history.
#[derive(Debug, Clone)]
pub struct CommentHistoryStep {
    /// The author of the change.
    pub author: IdentityStub,

    /// The new message of the comment.
    pub message: String,

    /// The new files associated with the comment.
    pub files: Vec<File>,

    /// When this change happened.
    pub at: TimeStamp,
}

/// One version of the status in the history.
#[derive(Debug, Clone, Copy)]
pub struct StatusHistoryStep {
    /// The author of the change.
    pub author: IdentityStub,

    /// The new status.
    pub status: Status,

    /// When this change happened.
    pub at: TimeStamp,
}

/// One version of the labels in the history.
#[derive(Debug, Clone)]
pub struct LabelHistoryStep {
    /// The author of the change.
    pub author: IdentityStub,

    /// The labels that have been added.
    pub added: Vec<Label>,

    /// The labels that have been removed.
    pub removed: Vec<Label>,

    /// When this change happened.
    pub at: TimeStamp,
}

/// One version of the Body in the history.
#[derive(Debug, Clone)]
pub struct BodyHistoryStep {
    /// The author of the change.
    pub author: IdentityStub,

    /// The new message of the body.
    pub message: String,

    /// The new files associated with the body.
    pub files: Vec<File>,

    /// When this change happened.
    pub at: TimeStamp,
}

/// One version of the title in the history.
#[derive(Debug, Clone)]
pub struct TitleHistoryStep {
    /// The author of the change.
    pub author: IdentityStub,

    /// The new title.
    pub title: String,

    /// When this change happened.
    pub at: TimeStamp,
}