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 update = & mut update_text_region_fn (
8101 "# begin autogenerated nightly\n " ,
@@ -15,4 +108,133 @@ pub fn update_nightly() {
15108 let mut updater = FileUpdater :: default ( ) ;
16109 updater. update_file ( "rust-toolchain.toml" , update) ;
17110 updater. update_file ( "clippy_utils/README.md" , update) ;
111+
112+ let message = format ! ( "Bump nightly version -> {date}" ) ;
113+ cmd ! ( sh, "git commit rust-toolchain --no-verify -m {message}" )
114+ . run ( )
115+ . expect ( "FAILED to commit rust-toolchain file, something went wrong" ) ;
116+
117+ let commit = rustc_hash ( ) ;
118+
119+ // Make sure josh is running in this scope
120+ {
121+ let _josh = start_josh ( ) ;
122+
123+ // Fetch given rustc commit.
124+ cmd ! (
125+ sh,
126+ "git fetch http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git"
127+ )
128+ . run ( )
129+ . inspect_err ( |_| {
130+ // Try to un-do the previous `git commit`, to leave the repo in the state we found it.
131+ cmd ! ( sh, "git reset --hard HEAD^" )
132+ . run ( )
133+ . expect ( "FAILED to clean up again after failed `git fetch`, sorry for that" ) ;
134+ } )
135+ . expect ( "FAILED to fetch new commits, something went wrong" ) ;
136+ }
137+
138+ // This should not add any new root commits. So count those before and after merging.
139+ let num_roots = || -> u32 {
140+ cmd ! ( sh, "git rev-list HEAD --max-parents=0 --count" )
141+ . read ( )
142+ . expect ( "failed to determine the number of root commits" )
143+ . parse :: < u32 > ( )
144+ . unwrap ( )
145+ } ;
146+ let num_roots_before = num_roots ( ) ;
147+
148+ // Merge the fetched commit.
149+ cmd ! ( sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}" )
150+ . run ( )
151+ . expect ( "FAILED to merge new commits, something went wrong" ) ;
152+
153+ // Check that the number of roots did not increase.
154+ if num_roots ( ) != num_roots_before {
155+ eprintln ! ( "Josh created a new root commit. This is probably not the history you want." ) ;
156+ exit ( 1 ) ;
157+ }
158+ }
159+
160+ pub ( crate ) const PUSH_PR_DESCRIPTION : & str = "Sync from Clippy commit:" ;
161+
162+ pub fn rustc_push ( rustc_path : String , github_user : & str , branch : & str , force : bool ) {
163+ let sh = Shell :: new ( ) . expect ( "failed to create shell" ) ;
164+ sh. change_dir ( clippy_project_root ( ) ) ;
165+
166+ assert_clean_repo ( & sh) ;
167+
168+ // Prepare the branch. Pushing works much better if we use as base exactly
169+ // the commit that we pulled from last time, so we use the `rustc --version`
170+ // to find out which commit that would be.
171+ let base = rustc_hash ( ) ;
172+
173+ println ! ( "Preparing {github_user}/rust (base: {base})..." ) ;
174+ sh. change_dir ( rustc_path) ;
175+ if !force
176+ && cmd ! ( sh, "git fetch https://github.com/{github_user}/rust {branch}" )
177+ . ignore_stderr ( )
178+ . read ( )
179+ . is_ok ( )
180+ {
181+ eprintln ! (
182+ "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
183+ ) ;
184+ exit ( 1 ) ;
185+ }
186+ cmd ! ( sh, "git fetch https://github.com/rust-lang/rust {base}" )
187+ . run ( )
188+ . expect ( "failed to fetch base commit" ) ;
189+ let force_flag = if force { "--force" } else { "" } ;
190+ cmd ! (
191+ sh,
192+ "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch} {force_flag}"
193+ )
194+ . ignore_stdout ( )
195+ . ignore_stderr ( ) // silence the "create GitHub PR" message
196+ . run ( )
197+ . expect ( "failed to push base commit to the new branch" ) ;
198+
199+ // Make sure josh is running in this scope
200+ {
201+ let _josh = start_josh ( ) ;
202+
203+ // Do the actual push.
204+ sh. change_dir ( clippy_project_root ( ) ) ;
205+ println ! ( "Pushing Clippy changes..." ) ;
206+ cmd ! (
207+ sh,
208+ "git push http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}"
209+ )
210+ . run ( )
211+ . expect ( "failed to push changes to Josh" ) ;
212+
213+ // Do a round-trip check to make sure the push worked as expected.
214+ cmd ! (
215+ sh,
216+ "git fetch http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git {branch}"
217+ )
218+ . ignore_stderr ( )
219+ . read ( )
220+ . expect ( "failed to fetch the branch from Josh" ) ;
221+ }
222+
223+ let head = cmd ! ( sh, "git rev-parse HEAD" )
224+ . read ( )
225+ . expect ( "failed to get HEAD commit" ) ;
226+ let fetch_head = cmd ! ( sh, "git rev-parse FETCH_HEAD" )
227+ . read ( )
228+ . expect ( "failed to get FETCH_HEAD" ) ;
229+ if head != fetch_head {
230+ eprintln ! ( "Josh created a non-roundtrip push! Do NOT merge this into rustc!" ) ;
231+ exit ( 1 ) ;
232+ }
233+ println ! ( "Confirmed that the push round-trips back to Clippy properly. Please create a rustc PR:" ) ;
234+ let description = format ! ( "{}+rust-lang/rust-clippy@{head}" , PUSH_PR_DESCRIPTION . replace( ' ' , "+" ) ) ;
235+ println ! (
236+ // Open PR with `subtree update` title to silence the `no-merges` triagebot check
237+ // See https://github.com/rust-lang/rust/pull/114157
238+ " https://github.com/rust-lang/rust/compare/{github_user}:{branch}?quick_pull=1&title=Clippy+subtree+update&body=r?+@ghost%0A%0A{description}"
239+ ) ;
18240}
0 commit comments