@@ -1725,9 +1725,9 @@ impl GitChain {
17251725 parent_branch : & str ,
17261726 branch_name : & str ,
17271727 ) -> Result < Vec < MergeCommitInfo > , Error > {
1728- // Get the latest commit on the branch
1728+ // 1. Fetch all commit hashes for the given `branch_name`.
17291729 let mut command = Command :: new ( "git" ) ;
1730- command. args ( [ "log" , "--oneline " , "-1" , branch_name] ) ;
1730+ command. args ( [ "log" , "--format=%H " , branch_name] ) ; // Get only commit hashes
17311731 let output = match command. output ( ) {
17321732 Ok ( output) => output,
17331733 Err ( _) => return Ok ( vec ! [ ] ) , // Return empty vec on error
@@ -1737,92 +1737,101 @@ impl GitChain {
17371737 return Ok ( vec ! [ ] ) ;
17381738 }
17391739
1740- let latest_commit = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
1741- if latest_commit. is_empty ( ) {
1742- return Ok ( vec ! [ ] ) ;
1743- }
1740+ let commit_hashes_str = String :: from_utf8_lossy ( & output. stdout ) ;
1741+ let commit_hashes: Vec < & str > = commit_hashes_str. trim ( ) . lines ( ) . collect ( ) ;
17441742
1745- // Check if it's a merge commit by looking for parent commits
1746- let commit_hash = latest_commit. split_whitespace ( ) . next ( ) . unwrap_or ( "" ) ;
1747- if commit_hash. is_empty ( ) {
1743+ if commit_hashes. is_empty ( ) {
17481744 return Ok ( vec ! [ ] ) ;
17491745 }
17501746
1751- // Get commit information
1752- let mut command = Command :: new ( "git" ) ;
1753- command. args ( [ "show" , "--stat" , commit_hash] ) ;
1754- let output = match command. output ( ) {
1755- Ok ( output) => output,
1756- Err ( _) => return Ok ( vec ! [ ] ) ,
1757- } ;
1747+ let mut merge_commits_info = Vec :: new ( ) ;
17581748
1759- if !output. status . success ( ) {
1760- return Ok ( vec ! [ ] ) ;
1761- }
1749+ // 2. Iterate through each commit hash.
1750+ for commit_hash in commit_hashes {
1751+ if commit_hash. is_empty ( ) {
1752+ continue ;
1753+ }
1754+
1755+ // 3. For each commit, use `git show --stat <commit_hash>` to get its information.
1756+ let mut show_command = Command :: new ( "git" ) ;
1757+ show_command. args ( [ "show" , "--stat" , commit_hash] ) ;
1758+ let show_output = match show_command. output ( ) {
1759+ Ok ( output) => output,
1760+ Err ( _) => continue , // Skip this commit on error
1761+ } ;
1762+
1763+ if !show_output. status . success ( ) {
1764+ continue ; // Skip this commit on error
1765+ }
17621766
1763- let commit_info = String :: from_utf8_lossy ( & output. stdout ) . to_string ( ) ;
1767+ let commit_info_str = String :: from_utf8_lossy ( & show_output. stdout ) . to_string ( ) ;
1768+ let commit_lines: Vec < & str > = commit_info_str. lines ( ) . collect ( ) ;
17641769
1765- // Check if it's a merge commit, which typically contains "Merge" in the commit message
1766- if commit_info. contains ( & format ! ( "Merge branch '{}'" , parent_branch) )
1767- || commit_info. contains ( "Merge branch" )
1768- {
1769- // Extract commit message (first line after commit hash)
1770- let commit_lines: Vec < & str > = commit_info. lines ( ) . collect ( ) ;
1771- let message = commit_lines
1770+ // 4. If the commit is a merge commit (contains "Merge branch" in its message AND refers to `parent_branch`),
1771+ // extract its message and stats.
1772+ let merge_line_prefix = format ! ( "Merge branch '{}'" , parent_branch) ;
1773+ let is_merge_from_parent = commit_lines
17721774 . iter ( )
1773- . position ( |line| line. trim ( ) . starts_with ( "Merge branch" ) )
1774- . map ( |idx| commit_lines[ idx] . trim ( ) . to_string ( ) ) ;
1775+ . any ( |line| line. trim ( ) . starts_with ( & merge_line_prefix) ) ;
17751776
1776- // Extract stats
1777- let stats_line = commit_lines
1777+ // Also check for generic "Merge branch" if parent_branch is empty (e.g. for initial merge to main)
1778+ // or if the specific format isn't exact.
1779+ let is_generic_merge = commit_lines
17781780 . iter ( )
1779- . find ( |line| line. contains ( "files changed" ) || line . contains ( "file changed ") ) ;
1781+ . any ( |line| line. trim ( ) . starts_with ( "Merge branch ") ) ;
17801782
1781- let stats = stats_line. map ( |line| {
1782- let mut files_changed = 0 ;
1783- let mut insertions = 0 ;
1784- let mut deletions = 0 ;
1783+ // A merge commit must have a line starting with "Merge: "
1784+ let is_actual_merge_commit = commit_lines. iter ( ) . any ( |line| line. starts_with ( "Merge: " ) ) ;
17851785
1786- if let Some ( files_idx) = line. find ( "file changed" ) {
1787- if let Some ( files_num) = line[ ..files_idx] . split_whitespace ( ) . last ( ) {
1788- files_changed = files_num. parse ( ) . unwrap_or ( 0 ) ;
1789- }
1790- } else if let Some ( files_idx) = line. find ( "files changed" ) {
1791- if let Some ( files_num) = line[ ..files_idx] . split_whitespace ( ) . last ( ) {
1792- files_changed = files_num. parse ( ) . unwrap_or ( 0 ) ;
1793- }
1794- }
1786+ if is_actual_merge_commit && ( is_merge_from_parent || ( parent_branch. is_empty ( ) && is_generic_merge) ) {
1787+ // Extract commit message (the line starting with "Merge branch ...")
1788+ let message = commit_lines
1789+ . iter ( )
1790+ . find ( |line| line. trim ( ) . starts_with ( "Merge branch" ) )
1791+ . map ( |line| line. trim ( ) . to_string ( ) ) ;
17951792
1796- if let Some ( ins_idx) = line. find ( "insertion" ) {
1797- if let Some ( ins_end) = line[ ..ins_idx] . rfind ( ' ' ) {
1798- if let Some ( ins_start) = line[ ..ins_end] . rfind ( ' ' ) {
1799- let ins_str = & line[ ins_start + 1 ..ins_end] ;
1800- insertions = ins_str. parse ( ) . unwrap_or ( 0 ) ;
1793+ // Extract stats (preserve existing logic, with slight refinement for parsing)
1794+ let stats_line = commit_lines
1795+ . iter ( )
1796+ . find ( |line| line. contains ( "files changed" ) || line. contains ( "file changed" ) ) ;
1797+
1798+ let stats = stats_line. map ( |line| {
1799+ let mut files_changed = 0 ;
1800+ let mut insertions = 0 ;
1801+ let mut deletions = 0 ;
1802+
1803+ // Example line: "1 file changed, 5 insertions(+), 2 deletions(-)"
1804+ // More robust parsing:
1805+ let parts: Vec < & str > = line. split ( ',' ) . map ( |s| s. trim ( ) ) . collect ( ) ;
1806+ for part in parts {
1807+ if part. contains ( "file changed" ) || part. contains ( "files changed" ) {
1808+ if let Some ( num_str) = part. split_whitespace ( ) . next ( ) {
1809+ files_changed = num_str. parse ( ) . unwrap_or ( 0 ) ;
1810+ }
1811+ } else if part. contains ( "insertion" ) {
1812+ if let Some ( num_str) = part. split_whitespace ( ) . next ( ) {
1813+ insertions = num_str. parse ( ) . unwrap_or ( 0 ) ;
1814+ }
1815+ } else if part. contains ( "deletion" ) {
1816+ if let Some ( num_str) = part. split_whitespace ( ) . next ( ) {
1817+ deletions = num_str. parse ( ) . unwrap_or ( 0 ) ;
1818+ }
18011819 }
18021820 }
1803- }
1804-
1805- if let Some ( del_idx) = line. find ( "deletion" ) {
1806- if let Some ( del_end) = line[ ..del_idx] . rfind ( ' ' ) {
1807- if let Some ( del_start) = line[ ..del_end] . rfind ( ' ' ) {
1808- let del_str = & line[ del_start + 1 ..del_end] ;
1809- deletions = del_str. parse ( ) . unwrap_or ( 0 ) ;
1810- }
1821+ MergeStats {
1822+ files_changed,
1823+ insertions,
1824+ deletions,
18111825 }
1812- }
1826+ } ) ;
18131827
1814- MergeStats {
1815- files_changed,
1816- insertions,
1817- deletions,
1818- }
1819- } ) ;
1820-
1821- return Ok ( vec ! [ MergeCommitInfo { message, stats } ] ) ;
1828+ merge_commits_info. push ( MergeCommitInfo { message, stats } ) ;
1829+ }
18221830 }
18231831
1824- // It's not a merge commit
1825- Ok ( vec ! [ ] )
1832+ // 5. Collect all `MergeCommitInfo` structs for the identified merge commits.
1833+ // 6. Return `Ok(Vec<MergeCommitInfo>)`
1834+ Ok ( merge_commits_info)
18261835 }
18271836
18281837 fn report_merge_results (
0 commit comments