@@ -424,7 +424,7 @@ def check_prerelease_version(gem)
424424 puts "#{ gem } -#{ spec . version } is not latest version of rubygems.org" if spec . version . to_s != latest_version
425425 end
426426
427- def message_filter ( repo , sha , log )
427+ def message_filter ( repo , sha , log , context : nil )
428428 unless repo . count ( "/" ) == 1 and /\A \S +\z / =~ repo
429429 raise ArgumentError , "invalid repository: #{ repo } "
430430 end
@@ -458,14 +458,15 @@ def message_filter(repo, sha, log)
458458 end
459459 end
460460 commit_url = "#{ repo_url } /commit/#{ sha [ 0 , 10 ] } \n "
461+ sync_note = context ? "#{ commit_url } \n #{ context } " : commit_url
461462 if log and !log . empty?
462463 log . sub! ( /(?<=\n )\n +\z / , '' ) # drop empty lines at the last
463464 conv [ log ]
464465 log . sub! ( /(?:(\A \s *)|\s *\n )(?=((?i:^Co-authored-by:.*\n ?)+)?\Z )/ ) {
465- ( $~. begin ( 1 ) ? "" : "\n \n " ) + commit_url + ( $~. begin ( 2 ) ? "\n " : "" )
466+ ( $~. begin ( 1 ) ? "" : "\n \n " ) + sync_note + ( $~. begin ( 2 ) ? "\n " : "" )
466467 }
467468 else
468- log = commit_url
469+ log = sync_note
469470 end
470471 "#{ subject } \n \n #{ log } "
471472 end
@@ -475,27 +476,51 @@ def log_format(format, args, &block)
475476 log --no-show-signature --format=#{ format } ] + args , "rb" , &block )
476477 end
477478
478- # Returns commit list as array of [commit_hash, subject].
479- def commits_in_ranges ( gem , repo , default_branch , ranges )
480- # If -a is given, discover all commits since the last picked commit
481- if ranges == true
482- pattern = "https://github\. com/#{ Regexp . quote ( repo ) } /commit/([0-9a-f]+)$"
483- log = log_format ( '%B' , %W" -E --grep=#{ pattern } -n1 -- " , &:read )
484- ranges = [ "#{ log [ %r[#{ pattern } \n \s *(?i:co-authored-by:.*)*\s *\Z ] , 1 ] } ..#{ gem } /#{ default_branch } " ]
485- end
479+ def commits_in_range ( upto , exclude , toplevel :)
480+ args = [ upto , *exclude . map { |s |"^#{ s } " } ]
481+ log_format ( '%H,%P,%s' , %W" --first-parent " + args ) do |f |
482+ f . read . split ( "\n " ) . reverse . flat_map { |commit |
483+ hash , parents , subject = commit . split ( ',' , 3 )
484+ parents = parents . split
485+
486+ # Non-merge commit
487+ if parents . size <= 1
488+ puts "#{ hash } #{ subject } "
489+ next [ [ hash , subject ] ]
490+ end
486491
487- # Parse a given range with git log
488- ranges . flat_map do |range |
489- unless range . include? ( ".." )
490- range = "#{ range } ~1..#{ range } "
491- end
492+ # Clean 2-parent merge commit: follow the other parent as long as it
493+ # contains no potentially-non-clean merges
494+ if parents . size == 2 &&
495+ IO . popen ( %W" git diff-tree --remerge-diff #{ hash } " , "rb" , &:read ) . empty?
496+ puts "\e [2mChecking the other parent of #{ hash } #{ subject } \e [0m"
497+ ret = catch ( :quit ) {
498+ commits_in_range ( parents [ 1 ] , exclude + [ parents [ 0 ] ] , toplevel : false )
499+ }
500+ next ret if ret
501+ end
492502
493- log_format ( '%H,%s' , %W" #{ range } -- " ) do |f |
494- f . read . split ( "\n " ) . reverse . map { |commit | commit . split ( ',' , 2 ) }
495- end
503+ unless toplevel
504+ puts "\e [1mMerge commit with possible conflict resolution #{ hash } #{ subject } \e [0m"
505+ throw :quit
506+ end
507+
508+ puts "#{ hash } #{ subject } " \
509+ "\e [1m[merge commit with possible conflicts, will do a squash merge]\e [0m"
510+ [ [ hash , subject ] ]
511+ }
496512 end
497513 end
498514
515+ # Returns commit list as array of [commit_hash, subject, sync_note].
516+ def commits_in_ranges ( ranges )
517+ ranges . flat_map do |range |
518+ exclude , upto = range . include? ( ".." ) ? range . split ( ".." , 2 ) : [ "#{ range } ~1" , range ]
519+ puts "Looking for commits in range #{ exclude } ..#{ upto } "
520+ commits_in_range ( upto , exclude . empty? ? [ ] : [ exclude ] , toplevel : true )
521+ end . uniq
522+ end
523+
499524 #--
500525 # Following methods used by sync_default_gems_with_commits return
501526 # true: success
@@ -558,7 +583,12 @@ def make_commit_info(gem, sha)
558583 "GIT_AUTHOR_EMAIL" => author_email ,
559584 "GIT_AUTHOR_DATE" => author_date ,
560585 }
561- message = message_filter ( config . upstream , sha , orig )
586+ context = nil
587+ if /^parent (?<first_parent>.{40})\n parent .{40}$/ =~ headers
588+ # Squashing a merge commit: keep authorship information
589+ context = IO . popen ( %W" git shortlog #{ first_parent } ..#{ sha } -- " , "rb" , &:read )
590+ end
591+ message = message_filter ( config . upstream , sha , orig , context : context )
562592 [ author , message ]
563593 end
564594
@@ -683,9 +713,8 @@ def pickup_commit(gem, sha, edit)
683713 return true
684714 end
685715
686- # NOTE: This method is also used by GitHub ruby/git.ruby-lang.org's bin/update-default-gem.sh
687716 # @param gem [String] A gem name, also used as a git remote name. REPOSITORIES converts it to the appropriate GitHub repository.
688- # @param ranges [Array<String>] "before..after". Note that it will NOT sync "before" (but commits after that).
717+ # @param ranges [Array<String>, true ] "commit", " before..after", or true . Note that it will NOT sync "before" (but commits after that).
689718 # @param edit [TrueClass] Set true if you want to resolve conflicts. Obviously, update-default-gem.sh doesn't use this.
690719 def sync_default_gems_with_commits ( gem , ranges , edit : nil )
691720 config = REPOSITORIES [ gem ]
@@ -700,21 +729,18 @@ def sync_default_gems_with_commits(gem, ranges, edit: nil)
700729 end
701730 system ( *%W" git fetch --no-tags --depth=#{ FETCH_DEPTH } #{ gem } #{ default_branch } " )
702731
703- commits = commits_in_ranges ( gem , repo , default_branch , ranges )
704-
705- # Ignore Merge commits and already-merged commits.
706- commits . delete_if do | sha , subject |
707- subject . start_with? ( "Merge" , "Auto Merge" )
732+ # If -a is given, discover all commits since the last picked commit
733+ if ranges == true
734+ pattern = "https://github \. com/ #{ Regexp . quote ( repo ) } /commit/([0-9a-f]+)$"
735+ log = log_format ( '%B' , %W" -E --grep= #{ pattern } -n1 -- " , & :read )
736+ ranges = [ " #{ log [ %r[ #{ pattern } \n \s *(?i:co-authored-by:.*)* \s * \Z ] , 1 ] } .. #{ gem } / #{ default_branch } " ]
708737 end
709-
738+ commits = commits_in_ranges ( ranges )
710739 if commits . empty?
711740 puts "No commits to pick"
712741 return true
713742 end
714743
715- puts "Try to pick these commits:"
716- puts commits . map { |commit | commit . join ( ": " ) }
717-
718744 failed_commits = [ ]
719745 commits . each do |sha , subject |
720746 puts "----"
0 commit comments