@@ -118,11 +118,8 @@ pub fn check_path_modifications(
118
118
// modified the set of paths, to have an upstream reference that does not change
119
119
// unnecessarily often.
120
120
// 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) ?;
126
123
match upstream_with_modifications {
127
124
Some ( sha) => Some ( sha) ,
128
125
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 {
157
154
!git. status ( ) . expect ( "cannot run git diff-index" ) . success ( )
158
155
}
159
156
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 (
163
160
git_dir : & Path ,
161
+ git_config : & GitConfig < ' _ > ,
164
162
target_paths : & [ & str ] ,
165
- author : & str ,
166
163
) -> Result < Option < String > , String > {
167
164
let mut git = Command :: new ( "git" ) ;
168
165
git. current_dir ( git_dir) ;
169
166
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
+ ] ) ;
171
189
172
190
if !target_paths. is_empty ( ) {
173
191
git. arg ( "--" ) . args ( target_paths) ;
@@ -176,44 +194,65 @@ fn get_latest_commit_that_modified_files(
176
194
if output. is_empty ( ) { Ok ( None ) } else { Ok ( Some ( output) ) }
177
195
}
178
196
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.
181
200
///
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.
185
202
fn get_closest_upstream_commit (
186
203
git_dir : Option < & Path > ,
187
204
config : & GitConfig < ' _ > ,
188
205
env : CiEnv ,
189
206
) -> 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
+
190
217
let mut git = Command :: new ( "git" ) ;
191
218
192
219
if let Some ( git_dir) = git_dir {
193
220
git. current_dir ( git_dir) ;
194
221
}
195
222
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!
205
231
git. args ( [
206
232
"rev-list" ,
233
+ "--author-date-order" ,
207
234
& format ! ( "--author={}" , config. git_merge_commit_email) ,
208
235
"-n1" ,
209
- "--first-parent" ,
210
236
& base,
211
237
] ) ;
212
238
213
239
let output = output_result ( & mut git) ?. trim ( ) . to_owned ( ) ;
214
240
if output. is_empty ( ) { Ok ( None ) } else { Ok ( Some ( output) ) }
215
241
}
216
242
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
+
217
256
/// Returns the files that have been modified in the current branch compared to the master branch.
218
257
/// This includes committed changes, uncommitted changes, and changes that are not even staged.
219
258
///
0 commit comments