|  | 
| 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