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 [`Timeline`] trait for use in
//! [`Identities`][`Identity`].

use super::history_step::{
    AvatarUrlHistoryStep, EmailHistoryStep, IdentityHistoryStep, LoginNameHistoryStep,
    MetadataHistoryStep, NameHistoryStep,
};
use crate::{
    entities::identity::{Identity, identity_operation::IdentityOperationData},
    replica::entity::{Entity, operation::Operation, snapshot::timeline::Timeline},
};

/// The timeline of changes of this [`Identity`].
#[derive(Debug, Clone)]
pub struct IdentityTimeline {
    history: Vec<IdentityHistoryStep>,
}

impl Timeline<Identity> for IdentityTimeline {
    fn new() -> Self {
        Self { history: vec![] }
    }

    fn from_root_operation(operation: &Operation<Identity>) -> Self {
        let mut me = Self::new();

        let IdentityOperationData::Create { name } = operation.operation_data() else {
            unreachable!("This should only be called with the root operation.");
        };
        me.add_name_history(NameHistoryStep {
            author: operation.author(),
            name: name.to_owned(),
            at: operation.creation_time(),
        });

        me
    }

    fn add(&mut self, op: &Operation<Identity>) {
        match op.operation_data() {
            IdentityOperationData::Create { .. } => unreachable!("Already processed."),
            IdentityOperationData::SetName { name } => self.add_name_history(NameHistoryStep {
                author: op.author(),
                name: name.to_owned(),
                at: op.creation_time(),
            }),
            IdentityOperationData::SetEmail { email } => self.add_email_history(EmailHistoryStep {
                author: op.author(),
                email: email.to_owned(),
                at: op.creation_time(),
            }),
            IdentityOperationData::SetLoginName { login_name } => {
                self.add_login_name_history(LoginNameHistoryStep {
                    author: op.author(),
                    login_name: login_name.to_owned(),
                    at: op.creation_time(),
                });
            }
            IdentityOperationData::SetAvatarUrl { url } => {
                self.add_avatar_url_history(AvatarUrlHistoryStep {
                    author: op.author(),
                    avatar_url: url.to_owned(),
                    at: op.creation_time(),
                });
            }
            IdentityOperationData::SetMetadata { metadata } => {
                self.add_metadata_history(MetadataHistoryStep {
                    author: op.author(),
                    metadata: metadata.to_owned(),
                    at: op.creation_time(),
                });
            }
        }
    }

    fn history(&self) -> &[<Identity as Entity>::HistoryStep] {
        &self.history
    }
}

macro_rules! filter_history {
    ($history:expr, $type_name:ident) => {
        filter_history!(@iter $history, $type_name)
    };
    (@$mode:ident $history:expr, $type_name:ident) => {
        $history.$mode().filter_map(|h| {
            if let IdentityHistoryStep::$type_name(a) = h {
                Some(a)
            } else {
                None
            }
        })
    };
}

impl IdentityTimeline {
    /// Return an iterator over the [`NameHistorySteps`][`NameHistoryStep`].
    pub fn name_history(&self) -> impl Iterator<Item = &NameHistoryStep> {
        filter_history!(self.history, Name)
    }

    /// Return an iterator over the [`EmailHistorySteps`][`EmailHistoryStep`].
    pub fn email_history(&self) -> impl Iterator<Item = &EmailHistoryStep> {
        filter_history!(self.history, Email)
    }

    /// Return an iterator over the
    /// [`LoginNameHistorySteps`][`LoginNameHistoryStep`].
    pub fn login_name_history(&self) -> impl Iterator<Item = &LoginNameHistoryStep> {
        filter_history!(self.history, LoginName)
    }

    /// Return an iterator over the
    /// [`AvatarUrlHistorySteps`][`AvatarUrlHistoryStep`].
    pub fn avatar_url_history(&self) -> impl Iterator<Item = &AvatarUrlHistoryStep> {
        filter_history!(self.history, AvatarUrl)
    }

    /// Return an iterator over the
    /// [`MetadataHistorySteps`][`MetadataHistoryStep`].
    pub fn metadata_history(&self) -> impl Iterator<Item = &MetadataHistoryStep> {
        filter_history!(self.history, Metadata)
    }

    // Mutating

    /// Add an [`NameHistoryStep`] to this [`Timeline`].
    pub fn add_name_history(&mut self, item: NameHistoryStep) {
        self.history.push(IdentityHistoryStep::Name(item));
    }

    /// Add an [`EmailHistoryStep`] to this [`Timeline`].
    pub fn add_email_history(&mut self, item: EmailHistoryStep) {
        self.history.push(IdentityHistoryStep::Email(item));
    }

    /// Add an [`LoginNameHistoryStep`] to this [`Timeline`].
    pub fn add_login_name_history(&mut self, item: LoginNameHistoryStep) {
        self.history.push(IdentityHistoryStep::LoginName(item));
    }

    /// Add an [`AvatarUrlHistoryStep`] to this [`Timeline`].
    pub fn add_avatar_url_history(&mut self, item: AvatarUrlHistoryStep) {
        self.history.push(IdentityHistoryStep::AvatarUrl(item));
    }

    /// Add an [`MetadataHistoryStep`] to this [`Timeline`].
    pub fn add_metadata_history(&mut self, item: MetadataHistoryStep) {
        self.history.push(IdentityHistoryStep::Metadata(item));
    }
}