Skip to content

Commit a7efdcd

Browse files
committed
Add a background job for squashing the index
This adds a background job that squashes the index into a single commit. The current plan is to manually enqueue this job on a 6 week schedule, roughly aligning with new `rustc` releases. Before deploying this, will need to make sure that the SSH key is allowed to do a force push to the protected master branch. This job is derived from a [script] that was periodically run by the cargo team. There are a few minor differences relative to the original script: * The push of the snapshot branch is no longer forced. The job will fail if run more than once on the same day. (If the first attempt fails before pushing a new root commit upstream, then retries should succeed as long as the snapshot can be fast-forwarded.) * The push of the new root commit to the origin no longer uses `--force-with-lease` to reject the force push if new commits have been pushed there in parallel. Other than the occasional manual changes to the index (such as deleting crates), background jobs have exclusive write access to the index while running. Given that such manual changes are rare, this job completes quickly, and such manual tasks should be automated too, this is low risk. The alternative is to shell out to git because `libgit2` (and thus the `git2` crate) do not yet support this portion of the protocol. [script]: rust-lang/crates-io-cargo-teams#47 (comment)
1 parent 23e2607 commit a7efdcd

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

src/bin/enqueue-job.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![deny(clippy::all)]
22

33
use anyhow::{anyhow, Result};
4-
use cargo_registry::{db, env, tasks};
4+
use cargo_registry::{db, env, git, tasks};
55
use diesel::prelude::*;
66
use swirl::schema::background_jobs::dsl::*;
77
use swirl::Job;
@@ -36,6 +36,7 @@ fn main() -> Result<()> {
3636
Ok(tasks::dump_db(database_url, target_name).enqueue(&conn)?)
3737
}
3838
"daily_db_maintenance" => Ok(tasks::daily_db_maintenance().enqueue(&conn)?),
39+
"squash_index" => Ok(git::squash_index().enqueue(&conn)?),
3940
other => Err(anyhow!("Unrecognized job type `{}`", other)),
4041
}
4142
}

src/git.rs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
use std::collections::HashMap;
44
use std::fs::{self, OpenOptions};
55
use std::path::{Path, PathBuf};
6+
7+
use chrono::Utc;
68
use swirl::PerformError;
79
use tempfile::{Builder, TempDir};
810
use url::Url;
@@ -205,12 +207,11 @@ impl Repository {
205207
self.repository
206208
.commit(Some("HEAD"), &sig, &sig, &msg, &tree, &[&parent])?;
207209

208-
self.push()
210+
self.push("refs/heads/master")
209211
}
210212

211-
/// Push the current branch to "refs/heads/master"
212-
fn push(&self) -> Result<(), PerformError> {
213-
let refname = "refs/heads/master";
213+
/// Push the current branch to the provided refname
214+
fn push(&self, refspec: &str) -> Result<(), PerformError> {
214215
let mut ref_status = Ok(());
215216
let mut callback_called = false;
216217
{
@@ -219,8 +220,7 @@ impl Repository {
219220
callbacks.credentials(|_, user_from_url, cred_type| {
220221
self.credentials.git2_callback(user_from_url, cred_type)
221222
});
222-
callbacks.push_update_reference(|cb_refname, status| {
223-
assert_eq!(refname, cb_refname);
223+
callbacks.push_update_reference(|_, status| {
224224
if let Some(s) = status {
225225
ref_status = Err(format!("failed to push a ref: {}", s).into())
226226
}
@@ -229,7 +229,7 @@ impl Repository {
229229
});
230230
let mut opts = git2::PushOptions::new();
231231
opts.remote_callbacks(callbacks);
232-
origin.push(&[refname], Some(&mut opts))?;
232+
origin.push(&[refspec], Some(&mut opts))?;
233233
}
234234

235235
if !callback_called {
@@ -278,6 +278,24 @@ impl Repository {
278278
opts.remote_callbacks(callbacks);
279279
opts
280280
}
281+
282+
/// Reset `HEAD` to a single commit with all the index contents, but no parent
283+
fn squash_to_single_commit(&self, msg: &str) -> Result<(), PerformError> {
284+
let tree = self.repository.find_commit(self.head_oid()?)?.tree()?;
285+
let sig = self.repository.signature()?;
286+
287+
// We cannot update an existing `update_ref`, because that requires the
288+
// first parent of this commit to match the ref's current value.
289+
// Instead, create the commit and then do a hard reset.
290+
let commit = self.repository.commit(None, &sig, &sig, &msg, &tree, &[])?;
291+
let commit = self
292+
.repository
293+
.find_object(commit, Some(git2::ObjectType::Commit))?;
294+
self.repository
295+
.reset(&commit, git2::ResetType::Hard, None)?;
296+
297+
Ok(())
298+
}
281299
}
282300

283301
#[swirl::background_job]
@@ -360,3 +378,34 @@ pub fn yank(
360378
Ok(())
361379
})
362380
}
381+
382+
/// Collapse the index into a single commit, archiving the current history in a snapshot branch.
383+
#[swirl::background_job]
384+
pub fn squash_index(env: &Environment) -> Result<(), PerformError> {
385+
let repo = env.lock_index()?;
386+
println!("Squashing the index into a single commit.");
387+
388+
let now = Utc::now().format("%Y-%m-%d");
389+
let head = repo.head_oid()?;
390+
let msg = format!("Collapse index into one commit\n\n\
391+
392+
Previous HEAD was {}, now on the `snapshot-{}` branch\n\n\
393+
394+
More information about this change can be found [online] and on [this issue].\n\n\
395+
396+
[online]: https://internals.rust-lang.org/t/cargos-crate-index-upcoming-squash-into-one-commit/8440\n\
397+
[this issue]: https://github.com/rust-lang/crates-io-cargo-teams/issues/47", head, now);
398+
399+
// Create a snapshot branch of current `HEAD`.
400+
repo.push(&format!("HEAD:refs/heads/snapshot-{}", now))?;
401+
402+
repo.squash_to_single_commit(&msg)?;
403+
404+
// Because this will not be a fast-forward push, `+` is added to the
405+
// beginning of the refspec to force the push.
406+
repo.push("+HEAD:refs/heads/master")?;
407+
408+
println!("The index has been successfully squashed.");
409+
410+
Ok(())
411+
}

0 commit comments

Comments
 (0)