@@ -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 ( 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,38 @@ 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 (
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 (
163160 git_dir : & Path ,
161+ git_config : & GitConfig < ' _ > ,
164162 target_paths : & [ & str ] ,
165- author : & str ,
166163) -> Result < Option < String > , String > {
167164 let mut git = Command :: new ( "git" ) ;
168165 git. current_dir ( git_dir) ;
169166
170- git. args ( [ "rev-list" , "-n1" , "--first-parent" , "HEAD" , "--author" , author] ) ;
167+ // In theory, we could just use
168+ // `git rev-list --first-parent HEAD --author=<merge-bot> -- <paths>`
169+ // to find the latest upstream commit that modified `<paths>`.
170+ // However, this does not work if you are in a subtree sync branch that contains merge commits
171+ // which have the subtree history as their first parent, and the rustc history as second parent:
172+ // `--first-parent` will just walk up the subtree history and never see a single rustc commit.
173+ // We thus have to take a two-pronged approach. First lookup the most recent upstream commit
174+ // by *date* (this should work even in a subtree sync branch), and then start the lookup for
175+ // modified paths starting from that commit.
176+ //
177+ // See https://github.com/rust-lang/rust/pull/138591#discussion_r2037081858 for more details.
178+ let upstream = get_closest_upstream_commit ( Some ( git_dir) , git_config, CiEnv :: None ) ?
179+ . unwrap_or_else ( || "HEAD" . to_string ( ) ) ;
180+
181+ git. args ( [
182+ "rev-list" ,
183+ "--first-parent" ,
184+ "-n1" ,
185+ & upstream,
186+ "--author" ,
187+ git_config. git_merge_commit_email ,
188+ ] ) ;
171189
172190 if !target_paths. is_empty ( ) {
173191 git. arg ( "--" ) . args ( target_paths) ;
@@ -176,44 +194,65 @@ fn get_latest_commit_that_modified_files(
176194 if output. is_empty ( ) { Ok ( None ) } else { Ok ( Some ( output) ) }
177195}
178196
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.
197+ /// Returns the most recent (ordered chronologically) commit found in the local history that
198+ /// should exist upstream. We identify upstream commits by the e-mail of the commit
199+ /// author.
181200///
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.
201+ /// If we are in CI, we simply return our first parent.
185202fn get_closest_upstream_commit (
186203 git_dir : Option < & Path > ,
187204 config : & GitConfig < ' _ > ,
188205 env : CiEnv ,
189206) -> Result < Option < String > , String > {
207+ let base = match env {
208+ CiEnv :: None => "HEAD" ,
209+ CiEnv :: GitHubActions => {
210+ // On CI, we should always have a non-upstream merge commit at the tip,
211+ // and our first parent should be the most recently merged upstream commit.
212+ // We thus simply return our first parent.
213+ return resolve_commit_sha ( git_dir, "HEAD^1" ) . map ( Some ) ;
214+ }
215+ } ;
216+
190217 let mut git = Command :: new ( "git" ) ;
191218
192219 if let Some ( git_dir) = git_dir {
193220 git. current_dir ( git_dir) ;
194221 }
195222
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- } ;
223+ // We do not use `--first-parent`, because we can be in a situation (outside CI) where we have
224+ // a subtree merge that actually has the main rustc history as its second parent.
225+ // Using `--first-parent` would recurse into the history of the subtree, which could have some
226+ // old bors commits that are not relevant to us.
227+ // With `--author-date-order`, git recurses into all parent subtrees, and returns the most
228+ // chronologically recent bors commit.
229+ // Here we assume that none of our subtrees use bors anymore, and that all their old bors
230+ // commits are way older than recent rustc bors commits!
205231 git. args ( [
206232 "rev-list" ,
233+ "--author-date-order" ,
207234 & format ! ( "--author={}" , config. git_merge_commit_email) ,
208235 "-n1" ,
209- "--first-parent" ,
210236 & base,
211237 ] ) ;
212238
213239 let output = output_result ( & mut git) ?. trim ( ) . to_owned ( ) ;
214240 if output. is_empty ( ) { Ok ( None ) } else { Ok ( Some ( output) ) }
215241}
216242
243+ /// Resolve the commit SHA of `commit_ref`.
244+ fn resolve_commit_sha ( git_dir : Option < & Path > , commit_ref : & str ) -> Result < String , String > {
245+ let mut git = Command :: new ( "git" ) ;
246+
247+ if let Some ( git_dir) = git_dir {
248+ git. current_dir ( git_dir) ;
249+ }
250+
251+ git. args ( [ "rev-parse" , commit_ref] ) ;
252+
253+ Ok ( output_result ( & mut git) ?. trim ( ) . to_owned ( ) )
254+ }
255+
217256/// Returns the files that have been modified in the current branch compared to the master branch.
218257/// This includes committed changes, uncommitted changes, and changes that are not even staged.
219258///
0 commit comments