1
- use std:: io:: { Write , stdout } ;
1
+ use std:: io:: Write ;
2
2
use std:: ops:: Not ;
3
3
use std:: path:: PathBuf ;
4
- use std:: process:: { Command , Stdio } ;
5
4
use std:: time:: Duration ;
6
- use std:: { env, fs , net, process} ;
5
+ use std:: { env, net, process} ;
7
6
8
7
use anyhow:: { Context , anyhow, bail} ;
9
8
use xshell:: { Shell , cmd} ;
10
9
11
- const PREPARING_COMMIT_MESSAGE : & str = "Preparing for merge from rustc" ;
12
- const MERGE_COMMIT_MESSAGE : & str = "Merge from rustc" ;
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
+ }
13
30
14
31
pub struct GitSync {
15
32
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 ,
22
33
}
23
34
24
35
/// This code was adapted from the miri repository
25
36
/// (https://github.com/rust-lang/miri/blob/6a68a79f38064c3bc30617cca4bdbfb2c336b140/miri-script/src/commands.rs#L236).
26
37
impl GitSync {
27
38
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 ;
31
39
Ok ( Self {
32
40
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}" ) ,
39
41
} )
40
42
}
41
43
42
44
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
-
52
45
let sh = Shell :: new ( ) ?;
53
46
sh. change_dir ( & self . dir ) ;
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 ( )
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 ( )
60
52
. next ( )
61
- . unwrap_or_else ( || panic ! ( "could not split output: '{out}'" ) )
62
- . to_owned ( )
63
- } ) ;
64
-
65
- ensure_clean ( & sh) ?;
66
-
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
+ }
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 ! ( "{josh_url_base }/{upstream_repo }.git@{upstream_head}{josh_filter }.git" ) ;
70
+ format ! ( "http://localhost:{JOSH_PORT }/{UPSTREAM_REPO }.git@{commit}{JOSH_FILTER }.git" ) ;
71
71
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 ( ) ;
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
+ }
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 ! ( "{upstream_head}\n " ) ) ?;
82
+ sh. write_file ( "rust-version" , format ! ( "{commit}\n " ) ) ?;
83
+ const PREPARING_COMMIT_MESSAGE : & str = "Preparing for merge from rustc" ;
83
84
cmd ! (
84
85
sh,
85
86
"git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}"
86
87
)
87
- . run ( ) ?;
88
+ . run ( )
89
+ . context ( "FAILED to commit rust-version file, something went wrong" ) ?;
88
90
89
91
// Fetch given rustc commit.
90
92
cmd ! ( sh, "git fetch {josh_url}" )
91
93
. run ( )
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
- // }
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)" ) ?;
106
101
107
102
// This should not add any new root commits. So count those before and after merging.
108
- let num_roots = || -> u32 {
109
- let out = cmd ! ( sh, "git rev-list HEAD --max-parents=0 --count" )
103
+ let num_roots = || -> anyhow :: Result < u32 > {
104
+ Ok ( cmd ! ( sh, "git rev-list HEAD --max-parents=0 --count" )
110
105
. read ( )
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}" ) )
106
+ . context ( "failed to determine the number of root commits" ) ?
107
+ . parse :: < u32 > ( ) ?)
114
108
} ;
115
- let num_roots_before = num_roots ( ) ;
109
+ let num_roots_before = num_roots ( ) ? ;
116
110
117
111
let sha = cmd ! ( sh, "git rev-parse HEAD" )
118
112
. output ( )
119
- . expect ( "FAILED to get current commit" )
113
+ . context ( "FAILED to get current commit" ) ?
120
114
. stdout ;
121
115
122
116
// Merge the fetched commit.
117
+ const MERGE_COMMIT_MESSAGE : & str = "Merge from rustc" ;
123
118
cmd ! (
124
119
sh,
125
120
"git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}"
126
121
)
127
122
. run ( )
128
- . expect ( "FAILED to merge new commits, something went wrong" ) ;
123
+ . context ( "FAILED to merge new commits, something went wrong" ) ? ;
129
124
130
125
let current_sha = cmd ! ( sh, "git rev-parse HEAD" )
131
126
. output ( )
132
127
. context ( "FAILED to get current commit" ) ?
133
128
. stdout ;
134
129
if current_sha == sha {
135
- cmd ! ( sh, "git reset --hard {orig_head} " )
130
+ cmd ! ( sh, "git reset --hard HEAD^ " )
136
131
. run ( )
137
132
. expect ( "FAILED to clean up after creating the preparation commit" ) ;
138
133
eprintln ! (
139
- "No merge was performed, no changes to pull were found. \
140
- Rolled back the preparation commit."
134
+ "No merge was performed, no changes to pull were found. Rolled back the preparation commit."
141
135
) ;
142
136
return Err ( RustcPullError :: NothingToPull ) ;
143
137
}
144
138
145
139
// Check that the number of roots did not increase.
146
- if num_roots ( ) != num_roots_before {
140
+ if num_roots ( ) ? != num_roots_before {
147
141
return Err ( anyhow:: anyhow!(
148
142
"Josh created a new root commit. This is probably not the history you want."
149
143
)
150
144
. into ( ) ) ;
151
145
}
152
146
147
+ drop ( josh) ;
153
148
Ok ( ( ) )
154
149
}
155
150
156
151
pub fn rustc_push ( & self , github_user : & str , branch : & str ) -> anyhow:: Result < ( ) > {
157
152
let sh = Shell :: new ( ) ?;
158
- let Self {
159
- upstream_repo,
160
- josh_filter,
161
- josh_url_base,
162
- ..
163
- } = self ;
164
153
sh. change_dir ( & self . dir ) ;
165
154
let base = sh. read_file ( "rust-version" ) ?. trim ( ) . to_owned ( ) ;
166
- ensure_clean ( & sh) ?;
167
-
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
+ }
168
163
// Make sure josh is running.
169
- let josh = self . start_josh ( ) ?;
170
- let josh_url = format ! ( "{josh_url_base}/{github_user}/rust.git{josh_filter}.git" ) ;
164
+ let josh = Self :: start_josh ( ) ?;
165
+ let josh_url =
166
+ format ! ( "http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git" ) ;
171
167
172
168
// Find a repo we can do our preparation in.
173
169
if let Ok ( rustc_git) = env:: var ( "RUSTC_GIT" ) {
@@ -205,7 +201,7 @@ impl GitSync {
205
201
) ;
206
202
std:: process:: exit ( 1 ) ;
207
203
}
208
- cmd ! ( sh, "git fetch https://github.com/{upstream_repo } {base}" ) . run ( ) ?;
204
+ cmd ! ( sh, "git fetch https://github.com/{UPSTREAM_REPO } {base}" ) . run ( ) ?;
209
205
cmd ! (
210
206
sh,
211
207
"git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}"
@@ -238,14 +234,14 @@ impl GitSync {
238
234
) ;
239
235
println ! (
240
236
// 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"
237
+ " https://github.com/{UPSTREAM_REPO }/compare/{github_user}:{branch}?quick_pull=1&title=rustc-dev-guide+subtree+update&body=r?+@ghost"
242
238
) ;
243
239
244
240
drop ( josh) ;
245
241
Ok ( ( ) )
246
242
}
247
243
248
- fn start_josh ( & self ) -> anyhow:: Result < impl Drop > {
244
+ fn start_josh ( ) -> anyhow:: Result < impl Drop > {
249
245
// Determine cache directory.
250
246
let local_dir = {
251
247
let user_dirs =
@@ -257,7 +253,7 @@ impl GitSync {
257
253
let mut cmd = process:: Command :: new ( "josh-proxy" ) ;
258
254
cmd. arg ( "--local" ) . arg ( local_dir) ;
259
255
cmd. arg ( "--remote" ) . arg ( "https://github.com" ) ;
260
- cmd. arg ( "--port" ) . arg ( self . josh_port . to_string ( ) ) ;
256
+ cmd. arg ( "--port" ) . arg ( JOSH_PORT . to_string ( ) ) ;
261
257
cmd. arg ( "--no-background" ) ;
262
258
cmd. stdout ( process:: Stdio :: null ( ) ) ;
263
259
cmd. stderr ( process:: Stdio :: null ( ) ) ;
@@ -300,7 +296,7 @@ impl GitSync {
300
296
for _ in 0 ..100 {
301
297
// This will generally fail immediately when the port is still closed.
302
298
let josh_ready = net:: TcpStream :: connect_timeout (
303
- & net:: SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , self . josh_port ) ) ,
299
+ & net:: SocketAddr :: from ( ( [ 127 , 0 , 0 , 1 ] , JOSH_PORT ) ) ,
304
300
Duration :: from_millis ( 1 ) ,
305
301
) ;
306
302
if josh_ready. is_ok ( ) {
@@ -311,43 +307,4 @@ impl GitSync {
311
307
}
312
308
bail ! ( "Even after waiting for 1s, josh-proxy is still not available." )
313
309
}
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
- }
353
310
}
0 commit comments