irongit

Git hosting and a container registry in one Rust binary (axum + Astro)

Imports: last-updated time is the newest commit, not the import time; fix existing imported repos at startup

huncholanehuncholaneauthored
parent 4dc24a2commit 3c098ea8b7ae0ee36ab99aa9c6a5540330a3e1eaBrowse files

3 files changed, +56 -1

+9-0backend/src/git.rs
@@ -367,6 +367,15 @@ impl Git {
367367 Ok(parse_commits(&self.run(&args).await?))
368368 }
369369
370+ /// When the newest commit on any branch was made: the honest "last
371+ /// pushed" time for history that arrived all at once, as in an import.
372+ /// Clamped to now, since commit clocks can run ahead.
373+ pub async fn newest_commit_time(&self) -> anyhow::Result<Option<DateTime<Utc>>> {
374+ let out = self.run(&["for-each-ref", "--sort=-committerdate", "--count=1", "--format=%(committerdate:unix)", "refs/heads"]).await?;
375+ let seconds: Option<i64> = String::from_utf8_lossy(&out).trim().parse().ok();
376+ Ok(seconds.and_then(|s| DateTime::from_timestamp(s, 0)).map(|t| t.min(Utc::now())))
377+ }
378+
370379 pub async fn count_commits(&self, rev: &str) -> anyhow::Result<u64> {
371380 let out = self.run_opt(&["rev-list", "--count", "--end-of-options", rev, "--"]).await?;
372381 Ok(out.and_then(|o| String::from_utf8_lossy(&o).trim().parse().ok()).unwrap_or(0))
+46-1backend/src/importer/mod.rs
@@ -544,9 +544,12 @@ async fn fill_repo(state: &AppState, gh: &GitHub, import: &ImportRow, repo: &Rep
544544 }
545545
546546 let size = git.disk_size().await.unwrap_or(0);
547+ // "Updated" should say when the work happened on GitHub, not when it
548+ // was copied here.
549+ let last_commit = git.newest_commit_time().await.unwrap_or(None);
547550 sqlx::query(
548551 "update repos set default_branch = coalesce($2, default_branch), is_empty = $3, size_bytes = $4, archived = $5,
549- pushed_at = case when $3 then pushed_at else now() end, updated_at = now()
552+ pushed_at = case when $3 then pushed_at else coalesce($6, now()) end, updated_at = now()
550553 where id = $1",
551554 )
552555 .bind(repo.id)
@@ -554,6 +557,7 @@ async fn fill_repo(state: &AppState, gh: &GitHub, import: &ImportRow, repo: &Rep
554557 .bind(branches.is_empty())
555558 .bind(size as i64)
556559 .bind(item.archived)
560+ .bind(last_commit)
557561 .execute(&state.db)
558562 .await?;
559563
@@ -965,3 +969,44 @@ mod tests {
965969 assert_eq!(stashed_token(&key, 7), None);
966970 }
967971 }
972+
973+/// Imports used to stamp `pushed_at` with the time of the import, so every
974+/// imported repo read "Updated" at the same moment. Repos that have had no
975+/// real push since get the time of their newest commit instead. A repo only
976+/// qualifies while `pushed_at` still sits at its import, so this settles
977+/// after one run.
978+pub fn spawn_import_date_fix(state: AppState) {
979+ tokio::spawn(async move {
980+ let repos: Vec<Repo> = match sqlx::query_as(concat!(
981+ crate::repo_select!(),
982+ " where not r.is_empty
983+ and exists (select 1 from push_events e where e.repo_id = r.id and e.via = 'import'
984+ and abs(extract(epoch from e.created_at - r.pushed_at)) < 600)
985+ and not exists (select 1 from push_events e where e.repo_id = r.id and e.via <> 'import')"
986+ ))
987+ .fetch_all(&state.db)
988+ .await
989+ {
990+ Ok(repos) => repos,
991+ Err(error) => {
992+ tracing::warn!(%error, "import date fix query failed");
993+ return;
994+ }
995+ };
996+ let mut fixed = 0;
997+ for repo in &repos {
998+ let git = Git::new(repo.disk_path(&state.config));
999+ let Ok(Some(last)) = git.newest_commit_time().await else { continue };
1000+ if Some(last) == repo.pushed_at {
1001+ continue;
1002+ }
1003+ match sqlx::query("update repos set pushed_at = $2 where id = $1").bind(repo.id).bind(last).execute(&state.db).await {
1004+ Ok(_) => fixed += 1,
1005+ Err(error) => tracing::warn!(repo = %repo.full_name(), %error, "import date fix failed"),
1006+ }
1007+ }
1008+ if fixed > 0 {
1009+ tracing::info!(fixed, "set imported repositories' last-updated time to their newest commit");
1010+ }
1011+ });
1012+}
+1-0backend/src/jobs.rs
@@ -10,6 +10,7 @@ pub fn spawn_all(state: AppState) {
1010 tokio::spawn(async move { crate::importer::recover(&recover_state).await });
1111 crate::registry::spawn_gc(state.clone());
1212 crate::languages::spawn_backfill(state.clone());
13+ crate::importer::spawn_import_date_fix(state.clone());
1314 crate::backup::spawn(state.clone());
1415 tokio::spawn(prune_sessions(state));
1516 }