|
1 | 1 | mod changelog;
|
2 | 2 |
|
| 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; |
3 | 10 | use xshell::{cmd, Shell};
|
4 | 11 |
|
5 | 12 | use crate::{codegen, date_iso, flags, is_release_tag, project_root};
|
@@ -71,26 +78,157 @@ impl flags::Release {
|
71 | 78 | }
|
72 | 79 | }
|
73 | 80 |
|
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::Pull { |
75 | 83 | 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()?; |
80 | 100 |
|
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 rustc"; |
| 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")?; |
85 | 110 |
|
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::Push { |
| 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"); |
| 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 |
92 | 174 | .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!"); |
93 | 198 | }
|
| 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); |
94 | 205 | Ok(())
|
95 | 206 | }
|
96 | 207 | }
|
| 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