// Back - An extremely simple git issue tracking system. Inspired by tvix's // panettone // // Copyright (C) 2024 Benedikt Peetz // SPDX-License-Identifier: AGPL-3.0-or-later // // This file is part of Back. // // You should have received a copy of the License along with this program. // If not, see . use std::path::Path; use gix::{bstr::ByteSlice, refs::Target, Commit, Id, ObjectId, Repository}; use crate::error; use super::issue::{ entity::{Entity, RawEntity}, CollapsedIssue, RawCollapsedIssue, }; #[derive(Debug)] pub struct Dag { entities: Vec, } impl Dag { pub fn collapse(self) -> CollapsedIssue { let raw_collapsed_issue = self.entities.into_iter().rev().fold( RawCollapsedIssue::default(), |mut collapsed_issue, entity| { collapsed_issue.append_entity(entity); collapsed_issue }, ); CollapsedIssue::from(raw_collapsed_issue) } /// Construct a DAG from the root child upwards. pub fn construct(repo: &Repository, id: ObjectId) -> Self { let mut entities = vec![]; let base_commit = repo .find_object(id) .expect("The object with this id should exist.") .try_into_commit() .expect("The git-bug's data model enforces this."); entities.push(Self::commit_to_operations(repo, &base_commit)); let mut current_commit = base_commit; while let Some(parent_id) = Self::try_get_parent(repo, ¤t_commit) { entities.push(Self::commit_to_operations(repo, &parent_id)); current_commit = parent_id; } Self { entities: { entities .into_iter() .map(|(raw_entity, id)| Entity::from_raw(repo, raw_entity, id)) .collect() }, } } fn commit_to_operations<'b>(repo: &Repository, id: &Commit<'b>) -> (RawEntity, Id<'b>) { let tree_obj = repo .find_object( id.tree_id() .expect("All of git-bug's commits should have trees attached to them'"), ) .expect("The object with this id should exist.") .try_into_tree() .expect("git-bug's data model enforces this."); let ops_ref = tree_obj .find_entry("ops") .expect("All of git-bug's trees should contain a 'ops' json file"); let issue_data = repo .find_object(ops_ref.object_id()) .expect("The object with this id should exist.") .try_into_blob() .expect("The git-bug's data model enforces this.") .data .clone(); let operations = serde_json::from_str( issue_data .to_str() .expect("git-bug's ensures, that this is valid json."), ) .expect("The returned json should be valid"); (operations, id.id()) } fn try_get_parent<'a>(repo: &'a Repository, base_commit: &Commit<'a>) -> Option> { let count = base_commit.parent_ids().count(); match count { 0 => None, 1 => { let parent = base_commit.parent_ids().last().expect("One does exist"); let parent_id = parent.object().expect("The object exists").id; Some( repo.find_object(parent_id) .expect("This is a valid id") .try_into_commit() .expect("This should be a commit"), ) } other => { unreachable!( "Each commit, used by git-bug should only have one parent, but found: {other}" ); } } } } pub fn issues_from_repository(repo: &Repository) -> error::Result> { let dags = repo .refs .iter()? .prefixed(Path::new("refs/bugs/"))? .map(|val| { let reference = val.expect("All `git-bug` references in 'refs/bugs' should be objects"); if let Target::Object(id) = reference.target { Dag::construct(repo, id) } else { unreachable!("All 'refs/bugs/' should contain a clear target."); } }) .collect::>(); Ok(dags) }