@@ -118,11 +118,8 @@ pub fn check_path_modifications(
118118 // modified the set of paths, to have an upstream reference that does not change
119119 // unnecessarily often.
120120 // However, if such commit is not found, we can fall back to the latest upstream commit
121- let upstream_with_modifications = get_latest_commit_that_modified_files (
122- git_dir,
123- target_paths,
124- config. git_merge_commit_email ,
125- ) ?;
121+ let upstream_with_modifications =
122+ get_latest_upstream_commit_that_modified_files ( Some ( git_dir) , config, target_paths) ?;
126123 match upstream_with_modifications {
127124 Some ( sha) => Some ( sha) ,
128125 None => get_closest_upstream_commit ( Some ( git_dir) , config, ci_env) ?,
@@ -157,17 +154,40 @@ pub fn has_changed_since(git_dir: &Path, base: &str, paths: &[&str]) -> bool {
157154 !git. status ( ) . expect ( "cannot run git diff-index" ) . success ( )
158155}
159156
160- /// Returns the latest commit that modified `target_paths`, or `None` if no such commit was found.
161- /// If `author` is `Some`, only considers commits made by that author.
162- fn get_latest_commit_that_modified_files (
163- git_dir : & Path ,
157+ /// Returns the latest upstream commit that modified `target_paths`, or `None` if no such commit
158+ /// was found.
159+ fn get_latest_upstream_commit_that_modified_files (
160+ git_dir : Option < & Path > ,
161+ git_config : & GitConfig < ' _ > ,
164162 target_paths : & [ & str ] ,
165- author : & str ,
166163) -> Result < Option < String > , String > {
167164 let mut git = Command :: new ( "git" ) ;
168- git. current_dir ( git_dir) ;
169165
170- git. args ( [ "rev-list" , "-n1" , "--first-parent" , "HEAD" , "--author" , author] ) ;
166+ if let Some ( git_dir) = git_dir {
167+ git. current_dir ( git_dir) ;
168+ }
169+
170+ // In theory, we could just use
171+ // `git rev-list --first-parent HEAD --author=<merge-bot> -- <paths>`
172+ // to find the latest upstream commit that modified `<paths>`.
173+ // However, this does not seem to work if you are in a subtree sync branch that contains
174+ // rustc merge commits that are not the first parents of a merge commit.
175+ // We thus have to take a two-pronged approach. First lookup the most recent upstream commit
176+ // by *date* (this should work even in a subtree sync branch), and then start the lookup for
177+ // modified paths starting from that commit.
178+ //
179+ // See https://github.com/rust-lang/rust/pull/138591#discussion_r2037081858 for more details.
180+ let upstream = get_closest_upstream_commit ( git_dir, git_config, CiEnv :: None ) ?
181+ . unwrap_or_else ( || "HEAD" . to_string ( ) ) ;
182+
183+ git. args ( [
184+ "rev-list" ,
185+ "--first-parent" ,
186+ "-n1" ,
187+ & upstream,
188+ "--author" ,
189+ git_config. git_merge_commit_email ,
190+ ] ) ;
171191
172192 if !target_paths. is_empty ( ) {
173193 git. arg ( "--" ) . args ( target_paths) ;
@@ -176,44 +196,65 @@ fn get_latest_commit_that_modified_files(
176196 if output. is_empty ( ) { Ok ( None ) } else { Ok ( Some ( output) ) }
177197}
178198
179- /// Returns the most recent commit found in the local history that should definitely
180- /// exist upstream. We identify upstream commits by the e-mail of the commit author.
199+ /// Returns the most recent (ordered chronologically) commit found in the local history that
200+ /// should exist upstream. We identify upstream commits by the e-mail of the commit
201+ /// author.
181202///
182- /// If `include_head` is false, the HEAD (current) commit will be ignored and only
183- /// its parents will be searched. This is useful for try/auto CI, where HEAD is
184- /// actually a commit made by bors, although it is not upstream yet.
203+ /// If we are in CI, we simply return out first parent.
185204fn get_closest_upstream_commit (
186205 git_dir : Option < & Path > ,
187206 config : & GitConfig < ' _ > ,
188207 env : CiEnv ,
189208) -> Result < Option < String > , String > {
209+ let base = match env {
210+ CiEnv :: None => "HEAD" ,
211+ CiEnv :: GitHubActions => {
212+ // On CI, we should always have a non-upstream merge commit at the tip,
213+ // and our first parent should be the most recently merged upstream commit.
214+ // We thus simply return our first parent.
215+ return resolve_commit_sha ( git_dir, "HEAD^1" ) . map ( Some ) ;
216+ }
217+ } ;
218+
190219 let mut git = Command :: new ( "git" ) ;
191220
192221 if let Some ( git_dir) = git_dir {
193222 git. current_dir ( git_dir) ;
194223 }
195224
196- let base = match env {
197- CiEnv :: None => "HEAD" ,
198- CiEnv :: GitHubActions => {
199- // On CI, we always have a merge commit at the tip.
200- // We thus skip it, because although it can be created by
201- // `config.git_merge_commit_email`, it should not be upstream.
202- "HEAD^1"
203- }
204- } ;
225+ // We do not use `--first-parent`, because we can be in a situation (outside CI) where we have
226+ // a subtree merge that actually has the main rustc history as its second parent.
227+ // Using `--first-parent` would recurse into the history of the subtree, which could have some
228+ // old bors commits that are not relevant to us.
229+ // With `--author-date-order`, git recurses into all parent subtrees, and returns the most
230+ // chronologically recent bors commit.
231+ // Here we assume that none of our subtrees use bors anymore, and that all their old bors
232+ // commits are way older than recent rustc bors commits!
205233 git. args ( [
206234 "rev-list" ,
235+ "--author-date-order" ,
207236 & format ! ( "--author={}" , config. git_merge_commit_email) ,
208237 "-n1" ,
209- "--first-parent" ,
210238 & base,
211239 ] ) ;
212240
213241 let output = output_result ( & mut git) ?. trim ( ) . to_owned ( ) ;
214242 if output. is_empty ( ) { Ok ( None ) } else { Ok ( Some ( output) ) }
215243}
216244
245+ /// Resolve the commit SHA of `commit_ref`.
246+ fn resolve_commit_sha ( git_dir : Option < & Path > , commit_ref : & str ) -> Result < String , String > {
247+ let mut git = Command :: new ( "git" ) ;
248+
249+ if let Some ( git_dir) = git_dir {
250+ git. current_dir ( git_dir) ;
251+ }
252+
253+ git. args ( [ "rev-parse" , commit_ref] ) ;
254+
255+ Ok ( output_result ( & mut git) ?. trim ( ) . to_owned ( ) )
256+ }
257+
217258/// Returns the files that have been modified in the current branch compared to the master branch.
218259/// This includes committed changes, uncommitted changes, and changes that are not even staged.
219260///
0 commit comments