summary refs log tree commit diff stats
path: root/pkgs/by-name/ba/back/src/git_bug/dag
diff options
context:
space:
mode:
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.rs143
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, &current_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)
+}