//! The nightly garbage collection (hub spec ยง17.2), run by hand. use oxy_core::repo::Repo; use oxygenhub::config::Config; use oxygenhub::db; use oxygenhub::repos::RepoName; use std::path::Path; fn hermetic() { static ONCE: std::sync::Once = std::sync::Once::new(); static DIR: std::sync::OnceLock = std::sync::OnceLock::new(); ONCE.call_once(|| { let d = tempfile::tempdir().unwrap(); std::env::set_var("OXY_HOME", d.path()); DIR.set(d).ok(); }); } fn write(root: &Path, rel: &str, content: &[u8]) { let p = root.join(rel); std::fs::create_dir_all(p.parent().unwrap()).unwrap(); std::fs::write(p, content).unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn every_repository_is_garbage_collected_in_turn() { hermetic(); let dir = tempfile::tempdir().unwrap(); let mut config = Config::load(dir.path()).unwrap(); config.auth.push_tokens = vec!["site".into()]; let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let port = listener.local_addr().unwrap().port(); config.server.base_url = format!("http://127.0.0.1:{port}"); assert_eq!(config.retention.gc_hour, 3, "nightly at 03:00 UTC by default"); let app = oxygenhub::build(config).await.unwrap(); let router = oxygenhub::router(app.clone()); tokio::spawn(async move { axum::serve(listener, router).await.unwrap() }); db::accounts::create_account(&app.db, "andy", "a@example.com", "x", "salt", true).await.unwrap(); for name in ["one", "two"] { app.repos.create(&RepoName::parse(&format!("andy/{name}")).unwrap(), None).unwrap(); db::repos::create(&app.db, "andy", name, "", hub_api::Visibility::Public).await.unwrap(); } // A gc folds what the pushes stored into one pack. let work = tempfile::tempdir().unwrap(); let mut c = Repo::init(work.path()).unwrap(); c.config.set("remote.origin.url", &format!("http://127.0.0.1:{port}/andy/one")).unwrap(); c.config.set("remote.origin.token", "site").unwrap(); c.save_config().unwrap(); for i in 0..3 { write(work.path(), &format!("f{i}.txt"), format!("{i}\n").as_bytes()); c.commit(&format!("c{i}"), &[]).unwrap(); } assert!(c.push("origin", false).unwrap().iter().all(|r| r.ok)); let hosted = app.repos.dir(&RepoName::parse("andy/one").unwrap()); let before = Repo::open(&hosted).unwrap(); assert!(before.store().loose_count().unwrap() > 0, "the push left loose objects"); drop(before); let done = oxygenhub::maintenance::gc_all(&app).await; assert_eq!(done.len(), 2, "{done:?}"); assert!(done.iter().all(|d| !d.failed), "{done:?}"); assert!(done[0].summary.contains("packed"), "{}", done[0]); let after = Repo::open(&hosted).unwrap(); assert_eq!(after.store().loose_count().unwrap(), 0, "everything is in a pack"); assert_eq!(after.store().pack_count(), 1); assert_eq!(after.bookmark_heads().unwrap(), c.bookmark_heads().unwrap(), "and the history is intact"); // The clone after the gc sees the same repository. let again = tempfile::tempdir().unwrap(); let d = Repo::clone(&format!("http://127.0.0.1:{port}/andy/one"), again.path()).unwrap(); assert_eq!(std::fs::read(again.path().join("f2.txt")).unwrap(), b"2\n"); let _ = d; }