diff options
Diffstat (limited to 'pkgs/by-name/ba/back/src/git_bug/dag')
-rw-r--r-- | pkgs/by-name/ba/back/src/git_bug/dag/mod.rs | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs b/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs new file mode 100644 index 0000000..9c158a7 --- /dev/null +++ b/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs @@ -0,0 +1,143 @@ +// Back - An extremely simple git issue tracking system. Inspired by tvix's +// panettone +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// 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 <https://www.gnu.org/licenses/agpl.txt>. + +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<Entity>, +} + +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<Commit<'a>> { + 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<Vec<Dag>> { + 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::<Vec<Dag>>(); + + Ok(dags) +} |