Skip to content

Commit 2228677

Browse files
committed
Use josh for subtree syncs
1 parent c83d8cf commit 2228677

File tree

7 files changed

+219
-21
lines changed

7 files changed

+219
-21
lines changed

Cargo.lock

+49
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/rust-analyzer/tests/slow-tests/tidy.rs

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ MIT OR Apache-2.0
144144
MIT OR Apache-2.0 OR Zlib
145145
MIT OR Zlib OR Apache-2.0
146146
MIT/Apache-2.0
147+
MPL-2.0
147148
Unlicense OR MIT
148149
Unlicense/MIT
149150
Zlib OR Apache-2.0 OR MIT

rust-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
688c30dc9f8434d63bddb65bd6a4d2258d19717c

xtask/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ rust-version.workspace = true
88

99
[dependencies]
1010
anyhow.workspace = true
11+
directories = "5.0"
1112
flate2 = "1.0.24"
1213
write-json = "0.1.2"
1314
xshell.workspace = true

xtask/src/flags.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ xflags::xflags! {
3232
cmd release {
3333
optional --dry-run
3434
}
35-
cmd promote {
36-
optional --dry-run
35+
36+
cmd rustc-pull {
37+
optional --commit refspec: String
3738
}
39+
cmd rustc-push {}
40+
3841
cmd dist {
3942
/// Use mimalloc allocator for server
4043
optional --mimalloc
@@ -77,7 +80,8 @@ pub enum XtaskCmd {
7780
Install(Install),
7881
FuzzTests(FuzzTests),
7982
Release(Release),
80-
Promote(Promote),
83+
RustcPull(RustcPull),
84+
RustcPush(RustcPush),
8185
Dist(Dist),
8286
PublishReleaseNotes(PublishReleaseNotes),
8387
Metrics(Metrics),
@@ -104,10 +108,13 @@ pub struct Release {
104108
}
105109

106110
#[derive(Debug)]
107-
pub struct Promote {
108-
pub dry_run: bool,
111+
pub struct RustcPull {
112+
pub commit: Option<String>,
109113
}
110114

115+
#[derive(Debug)]
116+
pub struct RustcPush;
117+
111118
#[derive(Debug)]
112119
pub struct Dist {
113120
pub mimalloc: bool,

xtask/src/main.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ fn main() -> anyhow::Result<()> {
3434
flags::XtaskCmd::Install(cmd) => cmd.run(sh),
3535
flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh),
3636
flags::XtaskCmd::Release(cmd) => cmd.run(sh),
37-
flags::XtaskCmd::Promote(cmd) => cmd.run(sh),
37+
flags::XtaskCmd::RustcPull(cmd) => cmd.run(sh),
38+
flags::XtaskCmd::RustcPush(cmd) => cmd.run(sh),
3839
flags::XtaskCmd::Dist(cmd) => cmd.run(sh),
3940
flags::XtaskCmd::PublishReleaseNotes(cmd) => cmd.run(sh),
4041
flags::XtaskCmd::Metrics(cmd) => cmd.run(sh),

xtask/src/release.rs

+153-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
mod changelog;
22

3+
use std::process::{Command, Stdio};
4+
use std::time::Duration;
5+
use std::{env, thread};
6+
7+
use anyhow::{bail, Context as _};
8+
use directories::ProjectDirs;
9+
use stdx::JodChild;
310
use xshell::{cmd, Shell};
411

512
use crate::{codegen, date_iso, flags, is_release_tag, project_root};
@@ -71,26 +78,157 @@ impl flags::Release {
7178
}
7279
}
7380

74-
impl flags::Promote {
81+
// git sync implementation adapted from https://github.com/rust-lang/miri/blob/62039ac/miri-script/src/commands.rs
82+
impl flags::RustcPull {
7583
pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
76-
let _dir = sh.push_dir("../rust-rust-analyzer");
77-
cmd!(sh, "git switch master").run()?;
78-
cmd!(sh, "git fetch upstream").run()?;
79-
cmd!(sh, "git reset --hard upstream/master").run()?;
84+
sh.change_dir(project_root());
85+
let commit = self.commit.map(Result::Ok).unwrap_or_else(|| {
86+
let rust_repo_head =
87+
cmd!(sh, "git ls-remote https://github.com/rust-lang/rust/ HEAD").read()?;
88+
rust_repo_head
89+
.split_whitespace()
90+
.next()
91+
.map(|front| front.trim().to_owned())
92+
.ok_or_else(|| anyhow::format_err!("Could not obtain Rust repo HEAD from remote."))
93+
})?;
94+
// Make sure the repo is clean.
95+
if !cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty() {
96+
bail!("working directory must be clean before running `cargo xtask pull`");
97+
}
98+
// Make sure josh is running.
99+
let josh = start_josh()?;
80100

81-
let date = date_iso(sh)?;
82-
let branch = format!("rust-analyzer-{date}");
83-
cmd!(sh, "git switch -c {branch}").run()?;
84-
cmd!(sh, "git subtree pull -m ':arrow_up: rust-analyzer' -P src/tools/rust-analyzer rust-analyzer release").run()?;
101+
// Update rust-version file. As a separate commit, since making it part of
102+
// the merge has confused the heck out of josh in the past.
103+
// We pass `--no-verify` to avoid running any git hooks that might exist,
104+
// in case they dirty the repository.
105+
sh.write_file("rust-version", format!("{commit}\n"))?;
106+
const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from downstream";
107+
cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}")
108+
.run()
109+
.context("FAILED to commit rust-version file, something went wrong")?;
85110

86-
if !self.dry_run {
87-
cmd!(sh, "git push -u origin {branch}").run()?;
88-
cmd!(
89-
sh,
90-
"xdg-open https://github.com/matklad/rust/pull/new/{branch}?body=r%3F%20%40ghost"
91-
)
111+
// Fetch given rustc commit.
112+
cmd!(sh, "git fetch http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git")
113+
.run()
114+
.map_err(|e| {
115+
// Try to un-do the previous `git commit`, to leave the repo in the state we found it it.
116+
cmd!(sh, "git reset --hard HEAD^")
117+
.run()
118+
.expect("FAILED to clean up again after failed `git fetch`, sorry for that");
119+
e
120+
})
121+
.context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?;
122+
123+
// Merge the fetched commit.
124+
const MERGE_COMMIT_MESSAGE: &str = "Merge from downstream";
125+
cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}")
126+
.run()
127+
.context("FAILED to merge new commits, something went wrong")?;
128+
129+
drop(josh);
130+
Ok(())
131+
}
132+
}
133+
134+
impl flags::RustcPush {
135+
pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
136+
let branch = "sync-from-ra";
137+
let Ok(github_user) = env::var("GITHUB_USER") else {
138+
bail!("please set `GITHUB_USER` to the GitHub username of your `rust-lang/rust` clone");
139+
};
140+
141+
sh.change_dir(project_root());
142+
let base = sh.read_file("rust-version")?.trim().to_owned();
143+
// Make sure the repo is clean.
144+
if !cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty() {
145+
bail!("working directory must be clean before running `cargo xtask push`");
146+
}
147+
// Make sure josh is running.
148+
let josh = start_josh()?;
149+
150+
// Find a repo we can do our preparation in.
151+
if let Ok(rustc_git) = env::var("RUSTC_GIT") {
152+
// If rustc_git is `Some`, we'll use an existing fork for the branch updates.
153+
sh.change_dir(rustc_git);
154+
} else {
155+
bail!("please set `RUSTC_GIT` to a `rust-lang/rust` clone");
156+
};
157+
// Prepare the branch. Pushing works much better if we use as base exactly
158+
// the commit that we pulled from last time, so we use the `rust-version`
159+
// file to find out which commit that would be.
160+
println!("Preparing {github_user}/rust (base: {base})...");
161+
if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}")
162+
.ignore_stderr()
163+
.read()
164+
.is_ok()
165+
{
166+
bail!(
167+
"The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
168+
);
169+
}
170+
cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?;
171+
cmd!(sh, "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}")
172+
.ignore_stdout()
173+
.ignore_stderr() // silence the "create GitHub PR" message
92174
.run()?;
175+
println!();
176+
177+
// Do the actual push.
178+
sh.change_dir(project_root());
179+
println!("Pushing rust-analyzer changes...");
180+
cmd!(
181+
sh,
182+
"git push http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}"
183+
)
184+
.run()?;
185+
println!();
186+
187+
// Do a round-trip check to make sure the push worked as expected.
188+
cmd!(
189+
sh,
190+
"git fetch http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git {branch}"
191+
)
192+
.ignore_stderr()
193+
.read()?;
194+
let head = cmd!(sh, "git rev-parse HEAD").read()?;
195+
let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?;
196+
if head != fetch_head {
197+
bail!("Josh created a non-roundtrip push! Do NOT merge this into rustc!");
93198
}
199+
println!("Confirmed that the push round-trips back to rust-analyzer properly. Please create a rustc PR:");
200+
println!(
201+
" https://github.com/rust-lang/rust/compare/{github_user}:{branch}?quick_pull=1&title=Subtree+update+of+rust-analyzer&body=r?+@ghost"
202+
);
203+
204+
drop(josh);
94205
Ok(())
95206
}
96207
}
208+
209+
/// Used for rustc syncs.
210+
const JOSH_FILTER: &str =
211+
":rev(f5a9250147f6569d8d89334dc9cca79c0322729f:prefix=src/tools/rust-analyzer):/src/tools/rust-analyzer";
212+
const JOSH_PORT: &str = "42042";
213+
214+
fn start_josh() -> anyhow::Result<impl Drop> {
215+
// Determine cache directory.
216+
let local_dir = {
217+
let user_dirs = ProjectDirs::from("org", "rust-lang", "rust-analyzer-josh").unwrap();
218+
user_dirs.cache_dir().to_owned()
219+
};
220+
221+
// Start josh, silencing its output.
222+
let mut cmd = Command::new("josh-proxy");
223+
cmd.arg("--local").arg(local_dir);
224+
cmd.arg("--remote").arg("https://github.com");
225+
cmd.arg("--port").arg(JOSH_PORT);
226+
cmd.arg("--no-background");
227+
cmd.stdout(Stdio::null());
228+
cmd.stderr(Stdio::null());
229+
let josh = cmd.spawn().context("failed to start josh-proxy, make sure it is installed")?;
230+
// Give it some time so hopefully the port is open. (100ms was not enough.)
231+
thread::sleep(Duration::from_millis(200));
232+
233+
Ok(JodChild(josh))
234+
}

0 commit comments

Comments
 (0)