Skip to content

Commit d9d0422

Browse files
arlosiTurbo87
authored andcommitted
Add normalize-index admin command
Re-generates the git index by reading existing files, normalizing them and writing them back out again. Does not use the database.
1 parent 0853bcb commit d9d0422

File tree

3 files changed

+107
-2
lines changed

3 files changed

+107
-2
lines changed

src/admin/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod delete_version;
33
pub mod dialoguer;
44
pub mod git_import;
55
pub mod migrate;
6+
pub mod normalize_index;
67
pub mod on_call;
78
pub mod populate;
89
pub mod render_readmes;

src/admin/normalize_index.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use std::{
2+
fs::File,
3+
io::{BufRead, BufReader},
4+
process::Command,
5+
};
6+
7+
use cargo_registry_index::{Repository, RepositoryConfig};
8+
use chrono::Utc;
9+
use indicatif::{ProgressBar, ProgressIterator, ProgressStyle};
10+
11+
use crate::admin::dialoguer;
12+
13+
#[derive(clap::Parser, Debug, Copy, Clone)]
14+
#[clap(name = "normalize-index", about = "Normalize and squash the git index")]
15+
pub struct Opts {}
16+
17+
pub fn run(_opts: Opts) -> anyhow::Result<()> {
18+
println!("fetching git repo");
19+
let config = RepositoryConfig::from_environment();
20+
let repo = Repository::open(&config)?;
21+
22+
repo.reset_head()?;
23+
println!("please place site in read-only mode now to prevent further commits");
24+
if !dialoguer::confirm("continue?") {
25+
return Ok(());
26+
}
27+
repo.reset_head()?;
28+
println!("HEAD is at {}", repo.head_oid()?);
29+
30+
let files = repo.get_files_modified_since(None)?;
31+
println!("found {} crates", files.len());
32+
let pb = ProgressBar::new(files.len() as u64);
33+
pb.set_style(ProgressStyle::with_template("{bar:60} ({pos}/{len}, ETA {eta})").unwrap());
34+
35+
for file in files.iter().progress_with(pb) {
36+
let crate_name = file.file_name().unwrap().to_str().unwrap();
37+
let path = repo.index_file(crate_name);
38+
if !path.exists() {
39+
continue;
40+
}
41+
42+
let mut body: Vec<u8> = Vec::new();
43+
let file = File::open(&path)?;
44+
let reader = BufReader::new(file);
45+
let mut versions = Vec::new();
46+
for line in reader.lines() {
47+
let mut krate: cargo_registry_index::Crate = serde_json::from_str(&line?)?;
48+
for dep in &mut krate.deps {
49+
// Remove deps with empty features
50+
dep.features.retain(|d| !d.is_empty());
51+
// Set null DependencyKind to Normal
52+
dep.kind = Some(
53+
dep.kind
54+
.unwrap_or(cargo_registry_index::DependencyKind::Normal),
55+
);
56+
}
57+
krate.deps.sort();
58+
versions.push(krate);
59+
}
60+
versions.sort_by_cached_key(|version| semver::Version::parse(&version.vers).ok());
61+
for version in versions {
62+
serde_json::to_writer(&mut body, &version).unwrap();
63+
body.push(b'\n');
64+
}
65+
std::fs::write(path, body)?;
66+
}
67+
68+
let original_head = repo.head_oid()?.to_string();
69+
70+
// Add an additional commit after the squash commit that normalizes the index.
71+
println!("committing normalization");
72+
let msg = "Normalize index format\n\n\
73+
More information can be found at https://github.com/rust-lang/crates.io/pull/5066";
74+
repo.run_command(Command::new("git").args(["commit", "-am", msg]))?;
75+
let snapshot_head = repo.head_oid()?.to_string();
76+
77+
println!("squashing");
78+
let now = Utc::now().format("%Y-%m-%d");
79+
let msg = format!("Collapse index into one commit\n\n\
80+
Previous HEAD was {}, now on the `snapshot-{}` branch\n\n\
81+
More information about this change can be found [online] and on [this issue].\n\n\
82+
[online]: https://internals.rust-lang.org/t/cargos-crate-index-upcoming-squash-into-one-commit/8440\n\
83+
[this issue]: https://github.com/rust-lang/crates-io-cargo-teams/issues/47", snapshot_head, now);
84+
repo.squash_to_single_commit(&msg)?;
85+
86+
if dialoguer::confirm("push to origin?") {
87+
repo.run_command(Command::new("git").args([
88+
"push",
89+
// Both updates should succeed or fail together
90+
"--atomic",
91+
"origin",
92+
// Overwrite master, but only if it server matches the expected value
93+
&format!("--force-with-lease=refs/heads/master:{original_head}"),
94+
// The new squashed commit is pushed to master
95+
"HEAD:refs/heads/master",
96+
// The previous value of HEAD is pushed to a snapshot branch
97+
&format!("{snapshot_head}:refs/heads/snapshot-{now}"),
98+
]))?;
99+
println!("The index has been successfully normalized and squashed.");
100+
}
101+
Ok(())
102+
}

src/bin/crates-admin.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#![warn(clippy::all, rust_2018_idioms)]
22

33
use cargo_registry::admin::{
4-
delete_crate, delete_version, git_import, migrate, populate, render_readmes, test_pagerduty,
5-
transfer_crates, upload_index, verify_token, yank_version,
4+
delete_crate, delete_version, git_import, migrate, normalize_index, populate, render_readmes,
5+
test_pagerduty, transfer_crates, upload_index, verify_token, yank_version,
66
};
77

88
#[derive(clap::Parser, Debug)]
@@ -25,6 +25,7 @@ enum SubCommand {
2525
UploadIndex(upload_index::Opts),
2626
YankVersion(yank_version::Opts),
2727
GitImport(git_import::Opts),
28+
NormalizeIndex(normalize_index::Opts),
2829
}
2930

3031
fn main() -> anyhow::Result<()> {
@@ -49,6 +50,7 @@ fn main() -> anyhow::Result<()> {
4950
SubCommand::UploadIndex(opts) => upload_index::run(opts)?,
5051
SubCommand::YankVersion(opts) => yank_version::run(opts),
5152
SubCommand::GitImport(opts) => git_import::run(opts)?,
53+
SubCommand::NormalizeIndex(opts) => normalize_index::run(opts)?,
5254
}
5355

5456
Ok(())

0 commit comments

Comments
 (0)