186 lines
6 KiB
Rust
186 lines
6 KiB
Rust
#![allow(dead_code)]
|
|
#![allow(clippy::useless_format)]
|
|
#![allow(clippy::needless_return)]
|
|
#![deny(clippy::as_conversions)]
|
|
|
|
use anyhow::Context as _;
|
|
use forgejo_api::Forgejo;
|
|
|
|
mod collect;
|
|
mod event_meta;
|
|
mod issue;
|
|
mod render;
|
|
mod tree;
|
|
mod wiki;
|
|
|
|
pub struct Context {
|
|
/// API Accessor object
|
|
pub forgejo: forgejo_api::Forgejo,
|
|
/// Repository Owner
|
|
pub owner: String,
|
|
/// Repository Name
|
|
pub repo: String,
|
|
/// URL of the repository page
|
|
pub repo_url: url::Url,
|
|
/// URL of the repository with authentication information attached
|
|
pub repo_auth_url: Option<url::Url>,
|
|
/// Human readable timestamp of this manager run
|
|
pub timestamp: String,
|
|
}
|
|
|
|
async fn run() -> anyhow::Result<()> {
|
|
let meta = if std::env::var("TECHTREE_FAKE").ok().is_some() {
|
|
log::warn!("Fake tree!");
|
|
event_meta::fake()
|
|
} else {
|
|
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!(
|
|
"Running due to event \"{:?}\" on issue #{} ...",
|
|
meta.action,
|
|
meta.issue.number
|
|
);
|
|
|
|
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").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").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
|
|
.join(&format!(
|
|
"{}/{}",
|
|
meta.issue.repository.owner, meta.issue.repository.name
|
|
))
|
|
.with_context(|| {
|
|
format!("failed building repository URL from GITHUB_SERVER_URL: {server_url}")
|
|
})?;
|
|
|
|
let repo_auth_url = token
|
|
.as_ref()
|
|
.map(|token| -> Result<_, ()> {
|
|
let mut repo_auth_url = repo_url.clone();
|
|
repo_auth_url.set_username("forgejo-actions")?;
|
|
repo_auth_url.set_password(Some(token))?;
|
|
Ok(repo_auth_url)
|
|
})
|
|
.transpose()
|
|
.map_err(|_e| anyhow::anyhow!("Repo URL does not have correct format"))
|
|
.context("Failed adding auth info to repo 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")?;
|
|
|
|
let ctx = Context {
|
|
forgejo,
|
|
owner: meta.issue.repository.owner.clone(),
|
|
repo: meta.issue.repository.name.clone(),
|
|
repo_url,
|
|
repo_auth_url,
|
|
timestamp,
|
|
};
|
|
|
|
if meta.action == event_meta::IssueAction::Opened {
|
|
log::debug!("Running for a newly opened issue. Taking care of the comment first...");
|
|
match issue::find_or_make_bot_comment(&ctx, meta.issue.number).await {
|
|
Ok((_comment, is_new)) => {
|
|
if is_new {
|
|
log::info!(
|
|
"Waiting for a minute, as the issue is brand new. We will likely catch some more updates this way!"
|
|
);
|
|
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
|
|
}
|
|
}
|
|
Err(err) => {
|
|
log::warn!(
|
|
"Error while commenting on new issue #{}, continuing anyway... Error: {err:?}",
|
|
meta.issue.number
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
log::info!("Collecting tree from issue metadata...");
|
|
let tree = collect::collect_tree(&ctx)
|
|
.await
|
|
.context("Failed to collect the techtree from issue metadata")?;
|
|
|
|
log::info!("Rendering and publishing techtree to git repository...");
|
|
let rendered_tree = render::render(&ctx, &tree)
|
|
.await
|
|
.context("Failed to render the techtree")?;
|
|
|
|
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 {
|
|
log::info!("Updating the wiki overview...");
|
|
wiki::update_wiki_from_tree(&ctx, &tree)
|
|
.await
|
|
.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)
|
|
.expect("issue from tree not found in tree");
|
|
let hash = subtree.stable_hash();
|
|
|
|
let (bot_comment, _is_new) = issue::find_or_make_bot_comment(&ctx, issue)
|
|
.await
|
|
.with_context(|| format!("Failed to find or make bot comment in issue #{issue}"))?;
|
|
|
|
if bot_comment.body.contains(&hash) {
|
|
log::debug!("Issue #{issue} is up to date, not editing comment.");
|
|
issue::remove_stale_label(&ctx, issue)
|
|
.await
|
|
.with_context(|| format!("Failed to remove `Stale` label from issue #{issue}"))?;
|
|
|
|
continue 'issues;
|
|
}
|
|
|
|
log::info!("Updating bot comment in issue #{issue} ...");
|
|
issue::update_bot_comment_from_subtree(&ctx, bot_comment.id, &subtree, &hash)
|
|
.await
|
|
.with_context(|| format!("Failed to update bot comment in issue #{issue}"))?;
|
|
|
|
issue::remove_stale_label(&ctx, issue)
|
|
.await
|
|
.with_context(|| format!("Failed to remove `Stale` label from issue #{issue}"))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
|
run().await
|
|
}
|