1
1
use std:: io:: { Write , stdout} ;
2
2
use std:: ops:: Not ;
3
3
use std:: path:: PathBuf ;
4
- use std:: process:: { Command , Stdio } ;
4
+ use std:: process:: { Command , Stdio , exit } ;
5
5
use std:: time:: Duration ;
6
6
use std:: { env, fs, net, process} ;
7
7
@@ -10,6 +10,7 @@ use xshell::{Shell, cmd};
10
10
11
11
const PREPARING_COMMIT_MESSAGE : & str = "Preparing for merge from rustc" ;
12
12
const MERGE_COMMIT_MESSAGE : & str = "Merge from rustc" ;
13
+ const DEFAULT_PR_BRANCH : & str = "update-builtins" ;
13
14
14
15
pub struct GitSync {
15
16
dir : PathBuf ,
@@ -53,9 +54,7 @@ impl GitSync {
53
54
sh. change_dir ( & self . dir ) ;
54
55
55
56
let upstream_head = commit. unwrap_or_else ( || {
56
- let out = cmd ! ( sh, "git ls-remote {upstream_url} {upstream_ref}" )
57
- . read ( )
58
- . unwrap ( ) ;
57
+ let out = self . read ( & [ "git" , "ls-remote" , upstream_url, upstream_ref] ) ;
59
58
out. split_whitespace ( )
60
59
. next ( )
61
60
. unwrap_or_else ( || panic ! ( "could not split output: '{out}'" ) )
@@ -72,24 +71,25 @@ impl GitSync {
72
71
let previous_base_commit = sh. read_file ( "rust-version" ) . unwrap ( ) . trim ( ) . to_string ( ) ;
73
72
assert_ne ! ( previous_base_commit, upstream_head, "nothing to pull" ) ;
74
73
75
- let orig_head = cmd ! ( sh , "git rev-parse HEAD" ) . read ( ) . unwrap ( ) ;
74
+ let orig_head = self . read ( [ "git" , " rev-parse" , " HEAD"] ) ;
76
75
77
76
// Update rust-version file. As a separate commit, since making it part of
78
77
// the merge has confused the heck out of josh in the past.
79
78
// We pass `--no-verify` to avoid running git hooks.
80
79
// We do this before the merge so that if there are merge conflicts, we have
81
80
// the right rust-version file while resolving them.
82
81
sh. write_file ( "rust-version" , format ! ( "{upstream_head}\n " ) ) ?;
83
- cmd ! (
84
- sh,
85
- "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}"
86
- )
87
- . run ( ) ?;
82
+ self . run ( [
83
+ "git" ,
84
+ "commit" ,
85
+ "rust-version" ,
86
+ "--no-verify" ,
87
+ "-m" ,
88
+ PREPARING_COMMIT_MESSAGE ,
89
+ ] ) ;
88
90
89
91
// Fetch given rustc commit.
90
- cmd ! ( sh, "git fetch {josh_url}" )
91
- . run ( )
92
- . expect ( "FAILED to fetch new commits, something went wrong" ) ;
92
+ self . run ( [ "git" , "fetch" , & josh_url] ) ;
93
93
94
94
// // Fetch given rustc commit.
95
95
// if let Err(e) = cmd!(sh, "git fetch {josh_url}").run() {
@@ -106,139 +106,134 @@ impl GitSync {
106
106
107
107
// This should not add any new root commits. So count those before and after merging.
108
108
let num_roots = || -> u32 {
109
- let out = cmd ! ( sh, "git rev-list HEAD --max-parents=0 --count" )
110
- . read ( )
111
- . expect ( "failed to determine the number of root commits" ) ;
109
+ let out = self . read ( [ "git" , "rev-list" , "HEAD" , "--max-parents=0" , "--count" ] ) ;
112
110
out. parse :: < u32 > ( )
113
- . unwrap_or_else ( |e| panic ! ( "failed to parse `out`: {e}" ) )
111
+ . unwrap_or_else ( |e| panic ! ( "failed to parse `{ out} `: {e}" ) )
114
112
} ;
115
113
let num_roots_before = num_roots ( ) ;
116
114
117
- let sha = cmd ! ( sh, "git rev-parse HEAD" )
118
- . output ( )
119
- . expect ( "FAILED to get current commit" )
120
- . stdout ;
115
+ let pre_merge_sha = self . read ( [ "git" , "rev-parse" , "HEAD" ] ) ;
121
116
122
117
// Merge the fetched commit.
123
- cmd ! (
124
- sh,
125
- "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}"
126
- )
127
- . run ( )
128
- . expect ( "FAILED to merge new commits, something went wrong" ) ;
129
-
130
- let current_sha = cmd ! ( sh, "git rev-parse HEAD" )
131
- . output ( )
132
- . context ( "FAILED to get current commit" ) ?
133
- . stdout ;
134
- if current_sha == sha {
135
- cmd ! ( sh, "git reset --hard {orig_head}" )
136
- . run ( )
137
- . expect ( "FAILED to clean up after creating the preparation commit" ) ;
118
+ self . run ( [
119
+ "git" ,
120
+ "merge" ,
121
+ "FETCH_HEAD" ,
122
+ "--no-verify" ,
123
+ "--no-ff" ,
124
+ "-m" ,
125
+ MERGE_COMMIT_MESSAGE ,
126
+ ] ) ;
127
+
128
+ let current_sha = self . read ( [ "git" , "rev-parse" , "HEAD" ] ) ;
129
+ if current_sha == pre_merge_sha {
130
+ self . run ( [ "git" , "reset" , "--hard" , & orig_head] ) ;
138
131
eprintln ! (
139
132
"No merge was performed, no changes to pull were found. \
140
133
Rolled back the preparation commit."
141
134
) ;
142
- return Err ( RustcPullError :: NothingToPull ) ;
135
+ exit ( 1 ) ;
143
136
}
144
137
145
138
// Check that the number of roots did not increase.
146
- if num_roots ( ) != num_roots_before {
147
- return Err ( anyhow:: anyhow!(
148
- "Josh created a new root commit. This is probably not the history you want."
149
- )
150
- . into ( ) ) ;
151
- }
139
+ assert_eq ! (
140
+ num_roots( ) ,
141
+ num_roots_before,
142
+ "Josh created a new root commit. This is probably not the history you want."
143
+ ) ;
152
144
153
145
Ok ( ( ) )
154
146
}
155
147
156
- pub fn rustc_push ( & self , github_user : & str , branch : & str ) -> anyhow:: Result < ( ) > {
157
- let sh = Shell :: new ( ) ?;
148
+ pub fn rustc_push ( & self , github_user : & str , branch : Option < & str > ) -> anyhow:: Result < ( ) > {
158
149
let Self {
159
150
upstream_repo,
160
151
josh_filter,
161
152
josh_url_base,
153
+ upstream_url,
162
154
..
163
155
} = self ;
156
+
157
+ let sh = Shell :: new ( ) ?;
164
158
sh. change_dir ( & self . dir ) ;
165
- let base = sh. read_file ( "rust-version" ) ?. trim ( ) . to_owned ( ) ;
159
+
160
+ let branch = branch. unwrap_or ( DEFAULT_PR_BRANCH ) ;
161
+ let josh_url = format ! ( "{josh_url_base}/{github_user}/rust.git{josh_filter}.git" ) ;
162
+ let user_rust_url = format ! ( "git@github.com:{github_user}/rust.git" ) ;
163
+
164
+ let Ok ( rustc_git) = env:: var ( "RUSTC_GIT" ) else {
165
+ panic ! ( "the RUSTC_GIT environment variable must be set to a rust-lang/rust checkout" )
166
+ } ;
167
+
166
168
ensure_clean ( & sh) ?;
169
+ let base = sh. read_file ( "rust-version" ) ?. trim ( ) . to_owned ( ) ;
167
170
168
171
// Make sure josh is running.
169
172
let josh = self . start_josh ( ) ?;
170
- let josh_url = format ! ( "{josh_url_base}/{github_user}/rust.git{josh_filter}.git" ) ;
171
173
172
- // Find a repo we can do our preparation in.
173
- if let Ok ( rustc_git) = env:: var ( "RUSTC_GIT" ) {
174
- // If rustc_git is `Some`, we'll use an existing fork for the branch updates.
175
- sh. change_dir ( rustc_git) ;
176
- } else {
177
- // Otherwise, do this in the local repo.
178
- println ! (
179
- "This will pull a copy of the rust-lang/rust history into this checkout, growing it by about 1GB."
180
- ) ;
181
- print ! (
182
- "To avoid that, abort now and set the `RUSTC_GIT` environment variable to an existing rustc checkout. Proceed? [y/N] "
183
- ) ;
184
- std:: io:: stdout ( ) . flush ( ) ?;
185
- let mut answer = String :: new ( ) ;
186
- std:: io:: stdin ( ) . read_line ( & mut answer) ?;
187
- if answer. trim ( ) . to_lowercase ( ) != "y" {
188
- std:: process:: exit ( 1 ) ;
189
- }
190
- } ;
191
174
// Prepare the branch. Pushing works much better if we use as base exactly
192
175
// the commit that we pulled from last time, so we use the `rust-version`
193
176
// file to find out which commit that would be.
194
177
println ! ( "Preparing {github_user}/rust (base: {base})..." ) ;
195
- if cmd ! (
196
- sh ,
197
- "git fetch https://github.com/{github_user}/rust {branch}"
198
- )
199
- . ignore_stderr ( )
200
- . read ( )
201
- . is_ok ( )
178
+
179
+ if Command :: new ( "git" )
180
+ . args ( [ "-C" , & rustc_git , "fetch" , & user_rust_url ] )
181
+ . output ( )
182
+ . expect ( "could not run fetch" )
183
+ . status
184
+ . success ( )
202
185
{
203
- println ! (
204
- "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
186
+ panic ! (
187
+ "The branch '{branch}' seems to already exist in '{user_rust_url}'. \
188
+ Please delete it and try again."
205
189
) ;
206
- std:: process:: exit ( 1 ) ;
207
190
}
208
- cmd ! ( sh, "git fetch https://github.com/{upstream_repo} {base}" ) . run ( ) ?;
209
- cmd ! (
210
- sh,
211
- "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}"
212
- )
213
- . ignore_stdout ( )
214
- . ignore_stderr ( ) // silence the "create GitHub PR" message
215
- . run ( ) ?;
216
- println ! ( ) ;
191
+
192
+ self . run ( [
193
+ "git" ,
194
+ "-C" ,
195
+ & rustc_git,
196
+ "fetch" ,
197
+ & format ! ( "https://github.com/{upstream_repo}" ) ,
198
+ & base,
199
+ ] ) ;
200
+
201
+ self . run_args ( "git" , |c| {
202
+ c. args ( [
203
+ "-C" ,
204
+ & rustc_git,
205
+ "push" ,
206
+ & format ! ( "https://github.com/{github_user}/rust" ) ,
207
+ & format ! ( "{base}:refs/heads/{branch}" ) ,
208
+ ] )
209
+ . stdout ( Stdio :: null ( ) )
210
+ . stderr ( Stdio :: null ( ) ) // silence the "create GitHub PR" message
211
+ } ) ;
212
+ println ! ( "pushed PR branch" ) ;
217
213
218
214
// Do the actual push.
219
215
sh. change_dir ( & self . dir ) ;
220
216
println ! ( "Pushing changes..." ) ;
221
- cmd ! ( sh , "git push { josh_url} HEAD:{branch}" ) . run ( ) ? ;
217
+ self . run ( [ "git" , " push" , & josh_url, & format ! ( " HEAD:{branch}") ] ) ;
222
218
println ! ( ) ;
223
219
224
220
// Do a round-trip check to make sure the push worked as expected.
225
- cmd ! ( sh, "git fetch {josh_url} {branch}" )
226
- . ignore_stderr ( )
227
- . read ( ) ?;
228
- let head = cmd ! ( sh, "git rev-parse HEAD" ) . read ( ) ?;
229
- let fetch_head = cmd ! ( sh, "git rev-parse FETCH_HEAD" ) . read ( ) ?;
230
- if head != fetch_head {
231
- bail ! (
232
- "Josh created a non-roundtrip push! Do NOT merge this into rustc!\n \
233
- Expected {head}, got {fetch_head}."
234
- ) ;
235
- }
221
+ self . run ( [ "git" , "fetch" , & josh_url, & branch] ) ; // TODO depth=1?
222
+
223
+ let head = self . read ( [ "git" , "rev-parse" , "HEAD" ] ) ;
224
+ let fetch_head = self . read ( [ "git" , "rev-parse" , "FETCH_HEAD" ] ) ;
225
+ assert_eq ! (
226
+ head, fetch_head,
227
+ "Josh created a non-roundtrip push! Do NOT merge this into rustc!\n \
228
+ Expected {head}, got {fetch_head}."
229
+ ) ;
236
230
println ! (
237
- "Confirmed that the push round-trips back to rustc-dev-guide properly. Please create a rustc PR:"
231
+ "Confirmed that the push round-trips back to rustc-dev-guide properly. Please \
232
+ create a rustc PR:"
238
233
) ;
234
+ // Open PR with `subtree update` title to silence the `no-merges` triagebot check
239
235
println ! (
240
- // Open PR with `subtree update` title to silence the `no-merges` triagebot check
241
- " https://github.com/{upstream_repo}/compare/{github_user}:{branch}?quick_pull=1&title=rustc-dev-guide+subtree+update&body=r?+@ghost"
236
+ " {upstream_url}/compare/{github_user}:{branch}?quick_pull=1&title=rustc-dev-guide+subtree+update&body=r?+@ghost"
242
237
) ;
243
238
244
239
drop ( josh) ;
@@ -263,16 +258,15 @@ impl GitSync {
263
258
cmd. stderr ( process:: Stdio :: null ( ) ) ;
264
259
let josh = cmd
265
260
. spawn ( )
266
- . context ( "failed to start josh-proxy, make sure it is installed" ) ? ;
261
+ . expect ( "failed to start josh-proxy, make sure it is installed" ) ;
267
262
268
263
// Create a wrapper that stops it on drop.
269
264
struct Josh ( process:: Child ) ;
270
265
impl Drop for Josh {
271
266
fn drop ( & mut self ) {
272
- #[ cfg( unix) ]
273
- {
267
+ if cfg ! ( unix) {
274
268
// Try to gracefully shut it down.
275
- process :: Command :: new ( "kill" )
269
+ Command :: new ( "kill" )
276
270
. args ( [ "-s" , "INT" , & self . 0 . id ( ) . to_string ( ) ] )
277
271
. output ( )
278
272
. expect ( "failed to SIGINT josh-proxy" ) ;
@@ -290,7 +284,8 @@ impl GitSync {
290
284
}
291
285
// If that didn't work (or we're not on Unix), kill it hard.
292
286
eprintln ! (
293
- "I have to kill josh-proxy the hard way, let's hope this does not break anything."
287
+ "I have to kill josh-proxy the hard way, let's hope this does not \
288
+ break anything."
294
289
) ;
295
290
self . 0 . kill ( ) . expect ( "failed to SIGKILL josh-proxy" ) ;
296
291
}
@@ -312,7 +307,22 @@ impl GitSync {
312
307
bail ! ( "Even after waiting for 1s, josh-proxy is still not available." )
313
308
}
314
309
315
- fn run ( & self , prog : & str , f : impl FnOnce ( & mut Command ) -> & mut Command ) -> String {
310
+ fn run < ' a , Args : AsRef < [ & ' a str ] > > ( & self , l : Args ) {
311
+ let l = l. as_ref ( ) ;
312
+ self . run_args ( l[ 0 ] , |c| c. args ( & l[ 1 ..] ) )
313
+ }
314
+
315
+ fn run_args ( & self , prog : & str , f : impl FnOnce ( & mut Command ) -> & mut Command ) {
316
+ // self.read(l.as_ref());
317
+ self . read_args ( prog, |c| f ( c. stdout ( Stdio :: inherit ( ) ) ) ) ;
318
+ }
319
+
320
+ fn read < ' a , Args : AsRef < [ & ' a str ] > > ( & self , l : Args ) -> String {
321
+ let l = l. as_ref ( ) ;
322
+ self . read_args ( l[ 0 ] , |c| c. args ( & l[ 1 ..] ) )
323
+ }
324
+
325
+ fn read_args ( & self , prog : & str , f : impl FnOnce ( & mut Command ) -> & mut Command ) -> String {
316
326
let mut cmd = Command :: new ( prog) ;
317
327
cmd. current_dir ( & self . dir ) . stderr ( Stdio :: inherit ( ) ) ;
318
328
f ( & mut cmd) ;
0 commit comments