1
- use std:: io:: Write ;
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
5
use std:: time:: Duration ;
5
- use std:: { env, net, process} ;
6
+ use std:: { env, fs , net, process} ;
6
7
7
8
use anyhow:: { Context , anyhow, bail} ;
8
9
use xshell:: { Shell , cmd} ;
9
10
10
- /// Used for rustc syncs.
11
- const JOSH_FILTER : & str = ":/src/doc/rustc-dev-guide" ;
12
- const JOSH_PORT : u16 = 42042 ;
13
- const UPSTREAM_REPO : & str = "rust-lang/rust" ;
14
-
15
- pub enum RustcPullError {
16
- /// No changes are available to be pulled.
17
- NothingToPull ,
18
- /// A rustc-pull has failed, probably a git operation error has occurred.
19
- PullFailed ( anyhow:: Error ) ,
20
- }
21
-
22
- impl < E > From < E > for RustcPullError
23
- where
24
- E : Into < anyhow:: Error > ,
25
- {
26
- fn from ( error : E ) -> Self {
27
- Self :: PullFailed ( error. into ( ) )
28
- }
29
- }
11
+ const PREPARING_COMMIT_MESSAGE : & str = "Preparing for merge from rustc" ;
12
+ const MERGE_COMMIT_MESSAGE : & str = "Merge from rustc" ;
30
13
31
14
pub struct GitSync {
32
15
dir : PathBuf ,
16
+ upstream_repo : String ,
17
+ upstream_ref : String ,
18
+ upstream_url : String ,
19
+ josh_filter : String ,
20
+ josh_port : u16 ,
21
+ josh_url_base : String ,
33
22
}
34
23
35
24
/// This code was adapted from the miri repository
36
25
/// (https://github.com/rust-lang/miri/blob/6a68a79f38064c3bc30617cca4bdbfb2c336b140/miri-script/src/commands.rs#L236).
37
26
impl GitSync {
38
27
pub fn from_current_dir ( ) -> anyhow:: Result < Self > {
28
+ let upstream_repo =
29
+ env:: var ( "UPSTREAM_ORG" ) . unwrap_or_else ( |_| "rust-lang" . to_owned ( ) ) + "/rust" ;
30
+ let josh_port = 42042 ;
39
31
Ok ( Self {
40
32
dir : std:: env:: current_dir ( ) ?,
33
+ upstream_url : format ! ( "https://github.com/{upstream_repo}/" ) ,
34
+ upstream_repo,
35
+ upstream_ref : env:: var ( "UPSTREAM_REF" ) . unwrap_or_else ( |_| "HEAD" . to_owned ( ) ) ,
36
+ josh_filter : ":/library/compiler-builtins" . to_owned ( ) ,
37
+ josh_port,
38
+ josh_url_base : format ! ( "http://localhost:{josh_port}" ) ,
41
39
} )
42
40
}
43
41
44
42
pub fn rustc_pull ( & self , commit : Option < String > ) -> Result < ( ) , RustcPullError > {
43
+ let Self {
44
+ upstream_repo,
45
+ upstream_ref,
46
+ upstream_url,
47
+ josh_filter,
48
+ josh_url_base,
49
+ ..
50
+ } = self ;
51
+
45
52
let sh = Shell :: new ( ) ?;
46
53
sh. change_dir ( & self . dir ) ;
47
- let commit = commit. map ( Ok ) . unwrap_or_else ( || {
48
- let rust_repo_head =
49
- cmd ! ( sh, "git ls-remote https://github.com/{UPSTREAM_REPO}/ HEAD" ) . read ( ) ?;
50
- rust_repo_head
51
- . split_whitespace ( )
54
+
55
+ let upstream_head = commit. unwrap_or_else ( || {
56
+ let out = cmd ! ( sh, "git ls-remote {upstream_url} {upstream_ref}" )
57
+ . read ( )
58
+ . unwrap ( ) ;
59
+ out. split_whitespace ( )
52
60
. next ( )
53
- . map ( |front| front. trim ( ) . to_owned ( ) )
54
- . ok_or_else ( || anyhow ! ( "Could not obtain Rust repo HEAD from remote." ) )
55
- } ) ?;
56
- // Make sure the repo is clean.
57
- if cmd ! ( sh, "git status --untracked-files=no --porcelain" )
58
- . read ( ) ?
59
- . is_empty ( )
60
- . not ( )
61
- {
62
- return Err ( anyhow:: anyhow!(
63
- "working directory must be clean before performing rustc pull"
64
- )
65
- . into ( ) ) ;
66
- }
61
+ . unwrap_or_else ( || panic ! ( "could not split output: '{out}'" ) )
62
+ . to_owned ( )
63
+ } ) ;
64
+
65
+ ensure_clean ( & sh) ?;
66
+
67
67
// Make sure josh is running.
68
- let josh = Self :: start_josh ( ) ?;
68
+ let _josh = self . start_josh ( ) ?;
69
69
let josh_url =
70
- format ! ( "http://localhost:{JOSH_PORT }/{UPSTREAM_REPO }.git@{commit}{JOSH_FILTER }.git" ) ;
70
+ format ! ( "{josh_url_base }/{upstream_repo }.git@{upstream_head}{josh_filter }.git" ) ;
71
71
72
- let previous_base_commit = sh. read_file ( "rust-version" ) ? . trim ( ) . to_string ( ) ;
73
- if previous_base_commit == commit {
74
- return Err ( RustcPullError :: NothingToPull ) ;
75
- }
72
+ let previous_base_commit = sh. read_file ( "rust-version" ) . unwrap ( ) . trim ( ) . to_string ( ) ;
73
+ assert_ne ! ( previous_base_commit, upstream_head , "nothing to pull" ) ;
74
+
75
+ let orig_head = cmd ! ( sh , "git rev-parse HEAD" ) . read ( ) . unwrap ( ) ;
76
76
77
77
// Update rust-version file. As a separate commit, since making it part of
78
78
// the merge has confused the heck out of josh in the past.
79
79
// We pass `--no-verify` to avoid running git hooks.
80
80
// We do this before the merge so that if there are merge conflicts, we have
81
81
// the right rust-version file while resolving them.
82
- sh. write_file ( "rust-version" , format ! ( "{commit}\n " ) ) ?;
83
- const PREPARING_COMMIT_MESSAGE : & str = "Preparing for merge from rustc" ;
82
+ sh. write_file ( "rust-version" , format ! ( "{upstream_head}\n " ) ) ?;
84
83
cmd ! (
85
84
sh,
86
85
"git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}"
87
86
)
88
- . run ( )
89
- . context ( "FAILED to commit rust-version file, something went wrong" ) ?;
87
+ . run ( ) ?;
90
88
91
89
// Fetch given rustc commit.
92
90
cmd ! ( sh, "git fetch {josh_url}" )
93
91
. run ( )
94
- . inspect_err ( |_| {
95
- // Try to un-do the previous `git commit`, to leave the repo in the state we found it.
96
- cmd ! ( sh, "git reset --hard HEAD^" )
97
- . run ( )
98
- . expect ( "FAILED to clean up again after failed `git fetch`, sorry for that" ) ;
99
- } )
100
- . context ( "FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)" ) ?;
92
+ . expect ( "FAILED to fetch new commits, something went wrong" ) ;
93
+
94
+ // // Fetch given rustc commit.
95
+ // if let Err(e) = cmd!(sh, "git fetch {josh_url}").run() {
96
+ // println!("FAILED to fetch new commits, something went wrong");
97
+
98
+ // // Try to un-do the previous `git commit`, to leave the repo in the state we found it.
99
+ // cmd!(sh, "git reset --hard {orig_head}")
100
+ // .run()
101
+ // .expect("FAILED to clean up again after failed `git fetch`, sorry for that");
102
+
103
+ // println!("committing the rust-version file has been undone");
104
+ // panic!("{e}");
105
+ // }
101
106
102
107
// This should not add any new root commits. So count those before and after merging.
103
- let num_roots = || -> anyhow :: Result < u32 > {
104
- Ok ( cmd ! ( sh, "git rev-list HEAD --max-parents=0 --count" )
108
+ let num_roots = || -> u32 {
109
+ let out = cmd ! ( sh, "git rev-list HEAD --max-parents=0 --count" )
105
110
. read ( )
106
- . context ( "failed to determine the number of root commits" ) ?
107
- . parse :: < u32 > ( ) ?)
111
+ . expect ( "failed to determine the number of root commits" ) ;
112
+ out. parse :: < u32 > ( )
113
+ . unwrap_or_else ( |e| panic ! ( "failed to parse `out`: {e}" ) )
108
114
} ;
109
- let num_roots_before = num_roots ( ) ? ;
115
+ let num_roots_before = num_roots ( ) ;
110
116
111
117
let sha = cmd ! ( sh, "git rev-parse HEAD" )
112
118
. output ( )
113
- . context ( "FAILED to get current commit" ) ?
119
+ . expect ( "FAILED to get current commit" )
114
120
. stdout ;
115
121
116
122
// Merge the fetched commit.
117
- const MERGE_COMMIT_MESSAGE : & str = "Merge from rustc" ;
118
123
cmd ! (
119
124
sh,
120
125
"git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}"
121
126
)
122
127
. run ( )
123
- . context ( "FAILED to merge new commits, something went wrong" ) ? ;
128
+ . expect ( "FAILED to merge new commits, something went wrong" ) ;
124
129
125
130
let current_sha = cmd ! ( sh, "git rev-parse HEAD" )
126
131
. output ( )
127
132
. context ( "FAILED to get current commit" ) ?
128
133
. stdout ;
129
134
if current_sha == sha {
130
- cmd ! ( sh, "git reset --hard HEAD^ " )
135
+ cmd ! ( sh, "git reset --hard {orig_head} " )
131
136
. run ( )
132
137
. expect ( "FAILED to clean up after creating the preparation commit" ) ;
133
138
eprintln ! (
134
- "No merge was performed, no changes to pull were found. Rolled back the preparation commit."
139
+ "No merge was performed, no changes to pull were found. \
140
+ Rolled back the preparation commit."
135
141
) ;
136
142
return Err ( RustcPullError :: NothingToPull ) ;
137
143
}
138
144
139
145
// Check that the number of roots did not increase.
140
- if num_roots ( ) ? != num_roots_before {
146
+ if num_roots ( ) != num_roots_before {
141
147
return Err ( anyhow:: anyhow!(
142
148
"Josh created a new root commit. This is probably not the history you want."
143
149
)
144
150
. into ( ) ) ;
145
151
}
146
152
147
- drop ( josh) ;
148
153
Ok ( ( ) )
149
154
}
150
155
151
156
pub fn rustc_push ( & self , github_user : & str , branch : & str ) -> anyhow:: Result < ( ) > {
152
157
let sh = Shell :: new ( ) ?;
158
+ let Self {
159
+ upstream_repo,
160
+ josh_filter,
161
+ josh_url_base,
162
+ ..
163
+ } = self ;
153
164
sh. change_dir ( & self . dir ) ;
154
165
let base = sh. read_file ( "rust-version" ) ?. trim ( ) . to_owned ( ) ;
155
- // Make sure the repo is clean.
156
- if cmd ! ( sh, "git status --untracked-files=no --porcelain" )
157
- . read ( ) ?
158
- . is_empty ( )
159
- . not ( )
160
- {
161
- bail ! ( "working directory must be clean before running `rustc-push`" ) ;
162
- }
166
+ ensure_clean ( & sh) ?;
167
+
163
168
// Make sure josh is running.
164
- let josh = Self :: start_josh ( ) ?;
165
- let josh_url =
166
- format ! ( "http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git" ) ;
169
+ let josh = self . start_josh ( ) ?;
170
+ let josh_url = format ! ( "{josh_url_base}/{github_user}/rust.git{josh_filter}.git" ) ;
167
171
168
172
// Find a repo we can do our preparation in.
169
173
if let Ok ( rustc_git) = env:: var ( "RUSTC_GIT" ) {
@@ -201,7 +205,7 @@ impl GitSync {
201
205
) ;
202
206
std:: process:: exit ( 1 ) ;
203
207
}
204
- cmd ! ( sh, "git fetch https://github.com/{UPSTREAM_REPO } {base}" ) . run ( ) ?;
208
+ cmd ! ( sh, "git fetch https://github.com/{upstream_repo } {base}" ) . run ( ) ?;
205
209
cmd ! (
206
210
sh,
207
211
"git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}"
@@ -234,14 +238,14 @@ impl GitSync {
234
238
) ;
235
239
println ! (
236
240
// Open PR with `subtree update` title to silence the `no-merges` triagebot check
237
- " https://github.com/{UPSTREAM_REPO }/compare/{github_user}:{branch}?quick_pull=1&title=rustc-dev-guide+subtree+update&body=r?+@ghost"
241
+ " https://github.com/{upstream_repo }/compare/{github_user}:{branch}?quick_pull=1&title=rustc-dev-guide+subtree+update&body=r?+@ghost"
238
242
) ;
239
243
240
244
drop ( josh) ;
241
245
Ok ( ( ) )
242
246
}
243
247
244
- fn start_josh ( ) -> anyhow:: Result < impl Drop > {
248
+ fn start_josh ( & self ) -> anyhow:: Result < impl Drop > {
245
249
// Determine cache directory.
246
250
let local_dir = {
247
251
let user_dirs =
@@ -253,7 +257,7 @@ impl GitSync {
253
257
let mut cmd = process:: Command :: new ( "josh-proxy" ) ;
254
258
cmd. arg ( "--local" ) . arg ( local_dir) ;
255
259
cmd. arg ( "--remote" ) . arg ( "https://github.com" ) ;
256
- cmd. arg ( "--port" ) . arg ( JOSH_PORT . to_string ( ) ) ;
260
+ cmd. arg ( "--port" ) . arg ( self . josh_port . to_string ( ) ) ;
257
261
cmd. arg ( "--no-background" ) ;
258
262
cmd. stdout ( process:: Stdio :: null ( ) ) ;
259
263
cmd. stderr ( process:: Stdio :: null ( ) ) ;
@@ -296,7 +300,7 @@ impl GitSync {
296
300
for _ in 0 ..100 {
297
301
// This will generally fail immediately when the port is still closed.
298
302
let josh_ready = net:: TcpStream :: connect_timeout (
299
- & net:: SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , JOSH_PORT ) ) ,
303
+ & net:: SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , self . josh_port ) ) ,
300
304
Duration :: from_millis ( 1 ) ,
301
305
) ;
302
306
if josh_ready. is_ok ( ) {
@@ -307,4 +311,43 @@ impl GitSync {
307
311
}
308
312
bail ! ( "Even after waiting for 1s, josh-proxy is still not available." )
309
313
}
314
+
315
+ fn run ( & self , prog : & str , f : impl FnOnce ( & mut Command ) -> & mut Command ) -> String {
316
+ let mut cmd = Command :: new ( prog) ;
317
+ cmd. current_dir ( & self . dir ) . stderr ( Stdio :: inherit ( ) ) ;
318
+ f ( & mut cmd) ;
319
+ eprintln ! ( "+ {cmd:?}" ) ;
320
+ let out = cmd. output ( ) . expect ( "command failed" ) ;
321
+ assert ! ( out. status. success( ) ) ;
322
+ String :: from_utf8 ( out. stdout ) . expect ( "non-UTF8 output" )
323
+ }
324
+ }
325
+
326
+ fn ensure_clean ( sh : & Shell ) -> anyhow:: Result < ( ) > {
327
+ // Make sure the repo is clean.
328
+ if cmd ! ( sh, "git status --untracked-files=no --porcelain" )
329
+ . read ( ) ?
330
+ . is_empty ( )
331
+ . not ( )
332
+ {
333
+ bail ! ( "working directory must be clean before performing rustc pull" ) ;
334
+ }
335
+
336
+ Ok ( ( ) )
337
+ }
338
+
339
+ pub enum RustcPullError {
340
+ /// No changes are available to be pulled.
341
+ NothingToPull ,
342
+ /// A rustc-pull has failed, probably a git operation error has occurred.
343
+ PullFailed ( anyhow:: Error ) ,
344
+ }
345
+
346
+ impl < E > From < E > for RustcPullError
347
+ where
348
+ E : Into < anyhow:: Error > ,
349
+ {
350
+ fn from ( error : E ) -> Self {
351
+ Self :: PullFailed ( error. into ( ) )
352
+ }
310
353
}
0 commit comments