summary refs log tree commit diff stats
path: root/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-12-26 17:50:54 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-12-26 17:58:27 +0100
commitdfb5714045e99a09bf3f67890ae3cdeab47058b3 (patch)
tree5e76e310892ea7e36099312e25023aa154217a28 /pkgs/by-name/ba/back/src/git_bug/dag/mod.rs
parentfix(pkgs/back): Use rocket to manage the configuration values (diff)
downloadnixos-server-dfb5714045e99a09bf3f67890ae3cdeab47058b3.tar.gz
nixos-server-dfb5714045e99a09bf3f67890ae3cdeab47058b3.zip
feat(pkgs/back): Rewrite the `git-bug` interface code
The previous code was more or less reverse engineered, whilst this code
is based on the actually git-bug source code. This improves the whole
issue and operation handling immensely and also makes the code better
maintainable. Furthermore, it also adds support for the operations that
had not already used in `vhack.eu/nixos-server.git`.
Diffstat (limited to 'pkgs/by-name/ba/back/src/git_bug/dag/mod.rs')
-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)
+}