1- use std:: path:: Path ;
1+ use std:: path:: { Path , PathBuf } ;
22use std:: process:: { Command , Stdio } ;
33
44pub struct GitConfig < ' a > {
55 pub git_repository : & ' a str ,
66 pub nightly_branch : & ' a str ,
7+ pub git_merge_commit_email : & ' a str ,
78}
89
910/// Runs a command and returns the output
@@ -95,7 +96,11 @@ pub fn updated_master_branch(
9596 Err ( "Cannot find any suitable upstream master branch" . to_owned ( ) )
9697}
9798
98- pub fn get_git_merge_base (
99+ /// Finds the nearest merge commit by comparing the local `HEAD` with the upstream branch's state.
100+ /// To work correctly, the upstream remote must be properly configured using `git remote add <name> <url>`.
101+ /// In most cases `get_closest_merge_commit` is the function you are looking for as it doesn't require remote
102+ /// to be configured.
103+ fn git_upstream_merge_base (
99104 config : & GitConfig < ' _ > ,
100105 git_dir : Option < & Path > ,
101106) -> Result < String , String > {
@@ -107,6 +112,38 @@ pub fn get_git_merge_base(
107112 Ok ( output_result ( git. arg ( "merge-base" ) . arg ( & updated_master) . arg ( "HEAD" ) ) ?. trim ( ) . to_owned ( ) )
108113}
109114
115+ /// Searches for the nearest merge commit in the repository that also exists upstream.
116+ ///
117+ /// If it fails to find the upstream remote, it then looks for the most recent commit made
118+ /// by the merge bot by matching the author's email address with the merge bot's email.
119+ pub fn get_closest_merge_commit (
120+ git_dir : Option < & Path > ,
121+ config : & GitConfig < ' _ > ,
122+ target_paths : & [ PathBuf ] ,
123+ ) -> Result < String , String > {
124+ let mut git = Command :: new ( "git" ) ;
125+
126+ if let Some ( git_dir) = git_dir {
127+ git. current_dir ( git_dir) ;
128+ }
129+
130+ let merge_base = git_upstream_merge_base ( config, git_dir) . unwrap_or_else ( |_| "HEAD" . into ( ) ) ;
131+
132+ git. args ( [
133+ "rev-list" ,
134+ & format ! ( "--author={}" , config. git_merge_commit_email) ,
135+ "-n1" ,
136+ "--first-parent" ,
137+ & merge_base,
138+ ] ) ;
139+
140+ if !target_paths. is_empty ( ) {
141+ git. arg ( "--" ) . args ( target_paths) ;
142+ }
143+
144+ Ok ( output_result ( & mut git) ?. trim ( ) . to_owned ( ) )
145+ }
146+
110147/// Returns the files that have been modified in the current branch compared to the master branch.
111148/// The `extensions` parameter can be used to filter the files by their extension.
112149/// Does not include removed files.
@@ -116,7 +153,7 @@ pub fn get_git_modified_files(
116153 git_dir : Option < & Path > ,
117154 extensions : & [ & str ] ,
118155) -> Result < Option < Vec < String > > , String > {
119- let merge_base = get_git_merge_base ( config, git_dir ) ?;
156+ let merge_base = get_closest_merge_commit ( git_dir , config, & [ ] ) ?;
120157
121158 let mut git = Command :: new ( "git" ) ;
122159 if let Some ( git_dir) = git_dir {
0 commit comments