11use crate :: utils:: { FileUpdater , update_text_region_fn} ;
22use chrono:: offset:: Utc ;
33use std:: fmt:: Write ;
4+ use std:: path:: Path ;
5+ use std:: process;
6+ use std:: process:: exit;
47
5- pub fn update_nightly ( ) {
8+ use xshell:: { Shell , cmd} ;
9+
10+ const JOSH_FILTER : & str = ":rev(2efebd2f0c03dabbe5c3ad7b4ebfbd99238d1fb2:prefix=src/tools/clippy):/src/tools/clippy" ;
11+ const JOSH_PORT : & str = "42042" ;
12+
13+ fn start_josh ( ) -> impl Drop {
14+ // Create a wrapper that stops it on drop.
15+ struct Josh ( process:: Child ) ;
16+ impl Drop for Josh {
17+ fn drop ( & mut self ) {
18+ #[ cfg( unix) ]
19+ {
20+ // Try to gracefully shut it down.
21+ process:: Command :: new ( "kill" )
22+ . args ( [ "-s" , "INT" , & self . 0 . id ( ) . to_string ( ) ] )
23+ . output ( )
24+ . expect ( "failed to SIGINT josh-proxy" ) ;
25+ // Sadly there is no "wait with timeout"... so we just give it some time to finish.
26+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) ;
27+ // Now hopefully it is gone.
28+ if self . 0 . try_wait ( ) . expect ( "failed to wait for josh-proxy" ) . is_some ( ) {
29+ return ;
30+ }
31+ }
32+ // If that didn't work (or we're not on Unix), kill it hard.
33+ eprintln ! ( "I have to kill josh-proxy the hard way, let's hope this does not break anything." ) ;
34+ self . 0 . kill ( ) . expect ( "failed to SIGKILL josh-proxy" ) ;
35+ }
36+ }
37+
38+ // Determine cache directory.
39+ let local_dir = {
40+ let user_dirs = directories:: ProjectDirs :: from ( "org" , "rust-lang" , "clippy-josh" ) . unwrap ( ) ;
41+ user_dirs. cache_dir ( ) . to_owned ( )
42+ } ;
43+ println ! ( "Using local cache directory: {}" , local_dir. display( ) ) ;
44+
45+ // Start josh, silencing its output.
46+ let mut cmd = process:: Command :: new ( "josh-proxy" ) ;
47+ cmd. arg ( "--local" ) . arg ( local_dir) ;
48+ cmd. arg ( "--remote" ) . arg ( "https://github.com" ) ;
49+ cmd. arg ( "--port" ) . arg ( JOSH_PORT ) ;
50+ cmd. arg ( "--no-background" ) ;
51+ cmd. stdout ( process:: Stdio :: null ( ) ) ;
52+ cmd. stderr ( process:: Stdio :: null ( ) ) ;
53+ let josh = cmd
54+ . spawn ( )
55+ . expect ( "failed to start josh-proxy, make sure it is installed" ) ;
56+ // Give it some time so hopefully the port is open.
57+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) ;
58+
59+ Josh ( josh)
60+ }
61+
62+ fn rustc_hash ( ) -> String {
63+ let sh = Shell :: new ( ) . expect ( "failed to create shell" ) ;
64+ // Make sure we pick up the updated toolchain (usually rustup pins the toolchain
65+ // inside a single cargo/rustc invocation via this env var).
66+ sh. set_var ( "RUSTUP_TOOLCHAIN" , "" ) ;
67+ cmd ! ( sh, "rustc --version --verbose" )
68+ . read ( )
69+ . expect ( "failed to run `rustc -vV`" )
70+ . lines ( )
71+ . find ( |line| line. starts_with ( "commit-hash:" ) )
72+ . expect ( "failed to parse `rustc -vV`" )
73+ . split_whitespace ( )
74+ . last ( )
75+ . expect ( "failed to get commit from `rustc -vV`" )
76+ . to_string ( )
77+ }
78+
79+ fn assert_clean_repo ( sh : & Shell ) {
80+ if !cmd ! ( sh, "git status --untracked-files=no --porcelain" )
81+ . read ( )
82+ . expect ( "failed to run git status" )
83+ . is_empty ( )
84+ {
85+ eprintln ! ( "working directory must be clean before running `cargo dev sync pull`" ) ;
86+ exit ( 1 ) ;
87+ }
88+ }
89+
90+ pub fn rustc_pull ( ) {
91+ const MERGE_COMMIT_MESSAGE : & str = "Merge from rustc" ;
92+
93+ let sh = Shell :: new ( ) . expect ( "failed to create shell" ) ;
94+ sh. change_dir ( clippy_project_root ( ) ) ;
95+
96+ assert_clean_repo ( & sh) ;
97+
98+ // Update rust-toolchain file
699 let date = Utc :: now ( ) . format ( "%Y-%m-%d" ) . to_string ( ) ;
7100 let toolchain_update = & mut update_text_region_fn (
8101 "# begin autogenerated nightly\n " ,
@@ -22,4 +115,133 @@ pub fn update_nightly() {
22115 let mut updater = FileUpdater :: default ( ) ;
23116 updater. update_file ( "rust-toolchain.toml" , toolchain_update) ;
24117 updater. update_file ( "clippy_utils/README.md" , readme_update) ;
118+
119+ let message = format ! ( "Bump nightly version -> {date}" ) ;
120+ cmd ! ( sh, "git commit rust-toolchain --no-verify -m {message}" )
121+ . run ( )
122+ . expect ( "FAILED to commit rust-toolchain file, something went wrong" ) ;
123+
124+ let commit = rustc_hash ( ) ;
125+
126+ // Make sure josh is running in this scope
127+ {
128+ let _josh = start_josh ( ) ;
129+
130+ // Fetch given rustc commit.
131+ cmd ! (
132+ sh,
133+ "git fetch http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git"
134+ )
135+ . run ( )
136+ . inspect_err ( |_| {
137+ // Try to un-do the previous `git commit`, to leave the repo in the state we found it.
138+ cmd ! ( sh, "git reset --hard HEAD^" )
139+ . run ( )
140+ . expect ( "FAILED to clean up again after failed `git fetch`, sorry for that" ) ;
141+ } )
142+ . expect ( "FAILED to fetch new commits, something went wrong" ) ;
143+ }
144+
145+ // This should not add any new root commits. So count those before and after merging.
146+ let num_roots = || -> u32 {
147+ cmd ! ( sh, "git rev-list HEAD --max-parents=0 --count" )
148+ . read ( )
149+ . expect ( "failed to determine the number of root commits" )
150+ . parse :: < u32 > ( )
151+ . unwrap ( )
152+ } ;
153+ let num_roots_before = num_roots ( ) ;
154+
155+ // Merge the fetched commit.
156+ cmd ! ( sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}" )
157+ . run ( )
158+ . expect ( "FAILED to merge new commits, something went wrong" ) ;
159+
160+ // Check that the number of roots did not increase.
161+ if num_roots ( ) != num_roots_before {
162+ eprintln ! ( "Josh created a new root commit. This is probably not the history you want." ) ;
163+ exit ( 1 ) ;
164+ }
165+ }
166+
167+ pub ( crate ) const PUSH_PR_DESCRIPTION : & str = "Sync from Clippy commit:" ;
168+
169+ pub fn rustc_push ( rustc_path : String , github_user : & str , branch : & str , force : bool ) {
170+ let sh = Shell :: new ( ) . expect ( "failed to create shell" ) ;
171+ sh. change_dir ( clippy_project_root ( ) ) ;
172+
173+ assert_clean_repo ( & sh) ;
174+
175+ // Prepare the branch. Pushing works much better if we use as base exactly
176+ // the commit that we pulled from last time, so we use the `rustc --version`
177+ // to find out which commit that would be.
178+ let base = rustc_hash ( ) ;
179+
180+ println ! ( "Preparing {github_user}/rust (base: {base})..." ) ;
181+ sh. change_dir ( rustc_path) ;
182+ if !force
183+ && cmd ! ( sh, "git fetch https://github.com/{github_user}/rust {branch}" )
184+ . ignore_stderr ( )
185+ . read ( )
186+ . is_ok ( )
187+ {
188+ eprintln ! (
189+ "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
190+ ) ;
191+ exit ( 1 ) ;
192+ }
193+ cmd ! ( sh, "git fetch https://github.com/rust-lang/rust {base}" )
194+ . run ( )
195+ . expect ( "failed to fetch base commit" ) ;
196+ let force_flag = if force { "--force" } else { "" } ;
197+ cmd ! (
198+ sh,
199+ "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch} {force_flag}"
200+ )
201+ . ignore_stdout ( )
202+ . ignore_stderr ( ) // silence the "create GitHub PR" message
203+ . run ( )
204+ . expect ( "failed to push base commit to the new branch" ) ;
205+
206+ // Make sure josh is running in this scope
207+ {
208+ let _josh = start_josh ( ) ;
209+
210+ // Do the actual push.
211+ sh. change_dir ( clippy_project_root ( ) ) ;
212+ println ! ( "Pushing Clippy changes..." ) ;
213+ cmd ! (
214+ sh,
215+ "git push http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}"
216+ )
217+ . run ( )
218+ . expect ( "failed to push changes to Josh" ) ;
219+
220+ // Do a round-trip check to make sure the push worked as expected.
221+ cmd ! (
222+ sh,
223+ "git fetch http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git {branch}"
224+ )
225+ . ignore_stderr ( )
226+ . read ( )
227+ . expect ( "failed to fetch the branch from Josh" ) ;
228+ }
229+
230+ let head = cmd ! ( sh, "git rev-parse HEAD" )
231+ . read ( )
232+ . expect ( "failed to get HEAD commit" ) ;
233+ let fetch_head = cmd ! ( sh, "git rev-parse FETCH_HEAD" )
234+ . read ( )
235+ . expect ( "failed to get FETCH_HEAD" ) ;
236+ if head != fetch_head {
237+ eprintln ! ( "Josh created a non-roundtrip push! Do NOT merge this into rustc!" ) ;
238+ exit ( 1 ) ;
239+ }
240+ println ! ( "Confirmed that the push round-trips back to Clippy properly. Please create a rustc PR:" ) ;
241+ let description = format ! ( "{}+rust-lang/rust-clippy@{head}" , PUSH_PR_DESCRIPTION . replace( ' ' , "+" ) ) ;
242+ println ! (
243+ // Open PR with `subtree update` title to silence the `no-merges` triagebot check
244+ // See https://github.com/rust-lang/rust/pull/114157
245+ " https://github.com/rust-lang/rust/compare/{github_user}:{branch}?quick_pull=1&title=Clippy+subtree+update&body=r?+@ghost%0A%0A{description}"
246+ ) ;
25247}
0 commit comments