samjeston / openhub Public
Sam Jeston Working vertical 9aa3c45 · History
112 lines· 3.8 KB ·Rust
Raw
1//! Keeps database state in step with repository refs after they change,
2//! whether through a push or a merge in the web interface.
3
4use anyhow::Result;
5use git2::Oid;
6
7use crate::db::{self, Repo};
8use crate::git;
9use crate::state::AppState;
10
11enum Change {
12 /// The head branch moved or the merge base changed.
13 Updated {
14 id: i64,
15 head: Oid,
16 merge_base: Option<Oid>,
17 },
18 /// The head is now contained in the base, e.g. merged on the command line.
19 Merged {
20 id: i64,
21 head: Oid,
22 merge_base: String,
23 },
24}
25
26/// Called after a successful push.
27pub async fn after_push(state: &AppState, repo: &Repo, pusher: i64) -> Result<()> {
28 db::touch_pushed(&state.db, repo.id).await?;
29 let path = state.config.repo_path(repo.id);
30 let preferred = repo.default_branch.clone();
31 let head =
32 tokio::task::spawn_blocking(move || git::ensure_head(&git::open(&path)?, &preferred))
33 .await??;
34 if let Some(head) = head.filter(|h| *h != repo.default_branch) {
35 db::set_default_branch(&state.db, repo.id, &head).await?;
36 }
37 refresh_pulls(state, repo.id, Some(pusher)).await
38}
39
40/// Re-evaluates every open pull request of a repository.
41pub async fn refresh_pulls(state: &AppState, repo_id: i64, actor: Option<i64>) -> Result<()> {
42 let pulls = db::list_pulls(&state.db, repo_id, true).await?;
43 if pulls.is_empty() {
44 return Ok(());
45 }
46 let path = state.config.repo_path(repo_id);
47 let changes = tokio::task::spawn_blocking(move || -> Result<Vec<Change>> {
48 let repo = git::open(&path)?;
49 let mut changes = Vec::new();
50 for p in pulls {
51 // A deleted head branch leaves the pull request as it was.
52 let Some(head) = git::branch_tip(&repo, &p.head_branch) else {
53 continue;
54 };
55 let head_changed = p.head_sha.as_deref() != Some(head.to_string().as_str());
56 if head_changed {
57 git::set_pull_ref(&repo, p.number, head)?;
58 }
59 let base = git::branch_tip(&repo, &p.base_branch);
60 match base {
61 Some(base) if git::is_ancestor(&repo, head, base) => {
62 let merge_base = p
63 .base_sha
64 .clone()
65 .or_else(|| git::merge_base(&repo, head, base).map(|o| o.to_string()))
66 .unwrap_or_else(|| head.to_string());
67 changes.push(Change::Merged {
68 id: p.id,
69 head,
70 merge_base,
71 });
72 }
73 _ => {
74 let merge_base = base.and_then(|b| git::merge_base(&repo, b, head));
75 let base_changed =
76 merge_base.map(|m| m.to_string()) != p.base_sha && merge_base.is_some();
77 if head_changed || base_changed {
78 changes.push(Change::Updated {
79 id: p.id,
80 head,
81 merge_base,
82 });
83 }
84 }
85 }
86 }
87 Ok(changes)
88 })
89 .await??;
90
91 for change in changes {
92 match change {
93 Change::Updated {
94 id,
95 head,
96 merge_base,
97 } => {
98 let mb = merge_base.map(|m| m.to_string());
99 db::update_pull_head(&state.db, id, &head.to_string(), mb.as_deref()).await?;
100 }
101 Change::Merged {
102 id,
103 head,
104 merge_base,
105 } => {
106 db::mark_merged(&state.db, id, actor, None, &head.to_string(), &merge_base).await?;
107 db::add_event(&state.db, id, actor, "merged", "").await?;
108 }
109 }
110 }
111 Ok(())
112}