From d4b8241808393747d09894812bb792cdcbf65cff Mon Sep 17 00:00:00 2001 From: Rahix Date: Fri, 30 May 2025 21:35:00 +0200 Subject: [PATCH 1/4] Allow running the manager without a token This is useful for local testing. --- techtree-manager/src/main.rs | 48 ++++++++++++++++++++++++---------- techtree-manager/src/render.rs | 7 ++++- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/techtree-manager/src/main.rs b/techtree-manager/src/main.rs index 6303643..bfb830a 100644 --- a/techtree-manager/src/main.rs +++ b/techtree-manager/src/main.rs @@ -20,7 +20,7 @@ pub struct Context { /// URL of the repository page pub repo_url: url::Url, /// URL of the repository with authentication information attached - pub repo_auth_url: url::Url, + pub repo_auth_url: Option, /// Human readable timestamp of this manager run pub timestamp: String, } @@ -42,13 +42,15 @@ async fn run() -> anyhow::Result<()> { let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); log::info!("Timestamp of this run is {timestamp}"); - let token = - std::env::var("GITHUB_TOKEN").context("Failed accessing GITHUB_TOKEN auth token")?; + let token = std::env::var("GITHUB_TOKEN").ok(); + if token.is_none() { + log::warn!("No GITHUB_TOKEN, only performing read-only operations!"); + } - let server_url = url::Url::parse( - &std::env::var("GITHUB_SERVER_URL") - .context("Failed reading GITHUB_SERVER_URL server url")?, - ) + let server_url = url::Url::parse(&std::env::var("GITHUB_SERVER_URL").unwrap_or_else(|_e| { + log::warn!("Using FAFO URL as a default GITHUB_SERVER_URL!"); + "https://git.fa-fo.de".to_string() + })) .context("Failed parsing GITHUB_SERVER_URL as a url")?; let repo_url = server_url @@ -57,11 +59,19 @@ async fn run() -> anyhow::Result<()> { meta.issue.repository.owner, meta.issue.repository.name )) .unwrap(); - let mut repo_auth_url = repo_url.clone(); - repo_auth_url.set_username("forgejo-actions").unwrap(); - repo_auth_url.set_password(Some(&token)).unwrap(); - let auth = forgejo_api::Auth::Token(&token); + let repo_auth_url = token.as_ref().map(|token| { + let mut repo_auth_url = repo_url.clone(); + repo_auth_url.set_username("forgejo-actions").unwrap(); + repo_auth_url.set_password(Some(&token)).unwrap(); + repo_auth_url + }); + + let auth = if let Some(token) = token.as_ref() { + forgejo_api::Auth::Token(token) + } else { + forgejo_api::Auth::None + }; let forgejo = Forgejo::new(auth, server_url).context("Could not create Forgejo API access object")?; @@ -103,9 +113,14 @@ async fn run() -> anyhow::Result<()> { let rendered_tree = render::render(&ctx, &tree) .await .context("Failed to render the techtree")?; - render::publish(&ctx, &rendered_tree) - .await - .context("Failed to publish rendered tree to git")?; + + if token.is_some() { + render::publish(&ctx, &rendered_tree) + .await + .context("Failed to publish rendered tree to git")?; + } else { + log::warn!("Skipped publishing the rendered tree."); + } // Wiki is disabled because the tree is too big for mermaid to handle if false { @@ -115,6 +130,11 @@ async fn run() -> anyhow::Result<()> { .context("Failed to update the techtree wiki page")?; } + if token.is_none() { + log::warn!("Not running issue updates without token."); + return Ok(()); + } + 'issues: for issue in tree.iter_issues() { let subtree = tree.subtree_for_issue(issue).unwrap(); let hash = subtree.stable_hash(); diff --git a/techtree-manager/src/render.rs b/techtree-manager/src/render.rs index f3d75b9..f9ebe81 100644 --- a/techtree-manager/src/render.rs +++ b/techtree-manager/src/render.rs @@ -103,7 +103,12 @@ pub async fn publish(ctx: &crate::Context, rendered: &RenderedTree) -> anyhow::R .arg("push") .arg("--force") .arg("--quiet") - .arg(ctx.repo_auth_url.to_string()) + .arg( + ctx.repo_auth_url + .as_ref() + .expect("cannot publish without authentication token") + .to_string(), + ) .arg("HEAD:refs/heads/render") .status() .context("Failed to push rendered graph to forgejo repository")?; From 92e8ea066f3840a05ef7ab41041a57b4dce258e1 Mon Sep 17 00:00:00 2001 From: Rahix Date: Mon, 28 Jul 2025 15:28:46 +0200 Subject: [PATCH 2/4] Hint about TECHTREE_FAKE=1 trick Make it easier to get started with the techtree-manager codebase by hinting at the TECHTREE_FAKE=1 variable for local development. --- techtree-manager/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/techtree-manager/src/main.rs b/techtree-manager/src/main.rs index bfb830a..9d6caff 100644 --- a/techtree-manager/src/main.rs +++ b/techtree-manager/src/main.rs @@ -30,7 +30,9 @@ async fn run() -> anyhow::Result<()> { log::warn!("Fake tree!"); event_meta::fake() } else { - event_meta::get_issue_event_meta_from_env().context("Failed reading issue event data")? + event_meta::get_issue_event_meta_from_env() + .context("Maybe you want to run with TECHTREE_FAKE=1?") + .context("Failed reading issue event data")? }; log::info!( From 1f889245afb5fec6d31943e1f503a4edb725602a Mon Sep 17 00:00:00 2001 From: Rahix Date: Fri, 30 May 2025 21:48:07 +0200 Subject: [PATCH 3/4] Make tree serde-serializable --- techtree-manager/Cargo.lock | 1 + techtree-manager/Cargo.toml | 2 +- techtree-manager/src/tree.rs | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/techtree-manager/Cargo.lock b/techtree-manager/Cargo.lock index 05548ee..2cfb715 100644 --- a/techtree-manager/Cargo.lock +++ b/techtree-manager/Cargo.lock @@ -964,6 +964,7 @@ dependencies = [ "hashbrown", "indexmap", "serde", + "serde_derive", ] [[package]] diff --git a/techtree-manager/Cargo.toml b/techtree-manager/Cargo.toml index 4625209..bba5bc6 100644 --- a/techtree-manager/Cargo.toml +++ b/techtree-manager/Cargo.toml @@ -13,7 +13,7 @@ chrono = "0.4.41" env_logger = { version = "0.11.8", default-features = false, features = ["auto-color", "color", "humantime"] } forgejo-api = { git = "https://git.fa-fo.de/rahix/forgejo-api.git", rev = "a3f6452cfe774898a89ac66be393e5205f5e12b7" } log = "0.4.27" -petgraph = "0.8.1" +petgraph = { version = "0.8.1", features = ["serde-1"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" sha256 = "1.6.0" diff --git a/techtree-manager/src/tree.rs b/techtree-manager/src/tree.rs index cf693cc..0574f73 100644 --- a/techtree-manager/src/tree.rs +++ b/techtree-manager/src/tree.rs @@ -113,8 +113,10 @@ impl std::fmt::Display for ElementStatus { pub type ElementIndex = petgraph::graph::NodeIndex; +#[derive(serde::Serialize)] pub struct Tree { graph: petgraph::Graph, + #[serde(skip)] issue_map: BTreeMap, } From 95194149291fd138cc0fe14001bb4d068de73cde Mon Sep 17 00:00:00 2001 From: Rahix Date: Mon, 28 Jul 2025 15:38:36 +0200 Subject: [PATCH 4/4] Push all ultimate elements to the bottom of the graph In dot rendering, put all ultimate elements (those that nothing further depends on) on the same rank, meaning the very bottom. This helps to visualize our ultimate goals more easily and also shows elements that are probably a requisite to something else that has not yet been modelled. --- techtree-manager/src/tree.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/techtree-manager/src/tree.rs b/techtree-manager/src/tree.rs index 0574f73..f87873c 100644 --- a/techtree-manager/src/tree.rs +++ b/techtree-manager/src/tree.rs @@ -160,6 +160,16 @@ impl Tree { Subtree::new_for_element(self, element) } + pub fn iter_ultimate_elements<'a>(&'a self) -> impl Iterator + 'a { + self.graph.node_indices().filter(|index| { + // If there are no incoming edges, then this is an ultimate element! + self.graph + .neighbors_directed(*index, petgraph::Direction::Incoming) + .next() + .is_none() + }) + } + pub fn to_dot(&self) -> String { let dot = petgraph::dot::Dot::with_attr_getters( &self.graph, @@ -173,9 +183,16 @@ impl Tree { &|_g, (_, element)| element.to_dot_node_attributes(None), ); + let ultimate_elements: Vec<_> = self + .iter_ultimate_elements() + .map(|idx| idx.index().to_string()) + .collect(); + let ultimate_element_list = ultimate_elements.join("; "); + format!( r#"digraph {{ ranksep=1.2 + {{ rank=same; {ultimate_element_list}; }} {:?} }} "#,