anvilsign in

collin/anvil

1//! Post-push CI trigger: detect which branches changed on a push and enqueue a
2//! CI run for any whose new commit defines a pipeline (`.anvil/ci.yml`).
3//!
4//! Used by the receive-pack paths in `anvil-web` and `anvil-ssh`: snapshot the
5//! branch tips *before* the push, then call [`enqueue_ci_for_push`] after it
6//! succeeds.
7
8use std::collections::HashMap;
9use std::path::Path;
10
11/// Local branch short-name → tip commit id (hex).
12pub type BranchTips = HashMap<String, String>;
13
14/// Snapshot current local branch tips. Best-effort — returns empty on error.
15pub fn snapshot_branches(repo_path: &Path) -> BranchTips {
16 let mut tips = HashMap::new();
17 let Ok(repo) = gix::open(repo_path) else {
18 return tips;
19 };
20 let Ok(platform) = repo.references() else {
21 return tips;
22 };
23 let Ok(iter) = platform.local_branches() else {
24 return tips;
25 };
26 for mut branch in iter.flatten() {
27 let name = branch.name().shorten().to_string();
28 if let Ok(id) = branch.peel_to_id() {
29 tips.insert(name, id.detach().to_string());
30 }
31 }
32 tips
33}
34
35/// After a push, enqueue a CI run for each branch whose tip changed (or is new)
36/// and whose new commit contains a pipeline definition. Returns the enqueued
37/// run ids (so the caller can notify the runner).
38pub async fn enqueue_ci_for_push(
39 db: &toasty::Db,
40 repo_id: i64,
41 repo_path: &Path,
42 before: &BranchTips,
43) -> Vec<i64> {
44 let after = snapshot_branches(repo_path);
45 let mut enqueued = Vec::new();
46 for (branch, new_tip) in &after {
47 if before.get(branch).map(String::as_str) == Some(new_tip.as_str()) {
48 continue; // unchanged branch
49 }
50 let has_pipeline = matches!(
51 crate::browse::read_blob(repo_path, new_tip, anvil_core::ci::PIPELINE_PATH),
52 Ok(Some(_))
53 );
54 if !has_pipeline {
55 continue;
56 }
57 match anvil_core::ci::enqueue(db, repo_id, new_tip, branch).await {
58 Ok(run) => {
59 tracing::info!("enqueued CI run {} for {branch}@{new_tip}", run.id);
60 enqueued.push(run.id);
61 }
62 Err(e) => tracing::error!("failed to enqueue CI run: {e}"),
63 }
64 }
65 enqueued
66}