class SemVerComponents::Outputs::SemanticReleaseGenerateNotes

Public Instance Methods

process(commits_info) click to toggle source

Process commits info

Parameters
  • commits_info (Array< Hash<Symbol, Object> >): List of commits info:

    • components_bump_levels (Hash<String or nil, Integer>): Set of bump levels (0: patch, 1: minor, 2: major) per component name (nil for global)

    • commit (Git::Object::Commit): Corresponding git commit

# File lib/sem_ver_components/outputs/semantic_release_generate_notes.rb, line 16
def process(commits_info)
  # Compute new version
  new_version = Semver.next_version_from(
    Semver.version_from_git_ref(@local_git.git_from),
    commits_info.map { |commit_info| commit_info[:components_bump_levels].values }.flatten(1).max
  )
  git_url = @local_git.git.remote('origin').url
  git_url = git_url[0..-5] if git_url.end_with?('.git')
  # Reference merge commits: merged commits will not be part of the changelog, but their bump level will be taken into account when reporting the merge commit.
  # List of merged commits' shas, per merge commit sha.
  # Hash< String, Array< String > >
  merge_commits = {}
  commits_info.each do |commit_info|
    git_commit = commit_info[:commit]
    git_commit_parents = git_commit.parents
    # In the case of a merge commit, reference all commits that are part of this merge commit, directly from the graph
    if git_commit_parents.size > 1
      git_commit_sha = git_commit.sha
      merge_commits[git_commit_sha] = @local_git.git_log.between(@local_git.git.merge_base(*git_commit_parents.map(&:sha)).first.sha, git_commit_sha)[1..-1].map(&:sha)
    end
  end
  commits_to_ignore = merge_commits.values.flatten(1).sort.uniq
  # Group commits per bump level, per component
  # Hash< String or nil, Hash< Integer, Array<Git::Object::Commit> >
  commits_per_component = {}
  commits_info.each do |commit_info|
    git_commit = commit_info[:commit]
    git_commit_sha = git_commit.sha
    # Don't put merged commits as we consider the changelog should contain the merge commit comment.
    next if commits_to_ignore.include?(git_commit_sha)
    components_bump_levels = commit_info[:components_bump_levels]
    # If we are dealing with a merge commit, consider the components' bump levels of the merged commits
    if merge_commits.key?(git_commit_sha)
      merge_commits[git_commit_sha].each do |merged_commit_sha|
        merged_commit_info = commits_info.find { |search_commit_info| search_commit_info[:commit].sha == merged_commit_sha }
        # If the merged commit is not part of the list of commits, it means that the merge commit was not rebased on the previous release tag.
        # In this case we can have some merged commits that are already part of the previous release.
        # So we can ignore them.
        unless merged_commit_info.nil?
          components_bump_levels = components_bump_levels.merge(merged_commit_info[:components_bump_levels]) do |component, bump_level_1, bump_level_2|
            [bump_level_1, bump_level_2].max
          end
        end
      end
    end
    components_bump_levels.each do |component, bump_level|
      commits_per_component[component] = {} unless commits_per_component.key?(component)
      commits_per_component[component][bump_level] = [] unless commits_per_component[component].key?(bump_level)
      commits_per_component[component][bump_level] << git_commit
    end
  end
  puts "# [v#{new_version}](#{@git_hosting.compare_url(git_url, @local_git.git_from, "v#{new_version}")}) (#{Time.now.utc.strftime('%F %T')})"
  puts
  commits_per_component.sort_by { |component, _component_info| component || '' }.each do |(component, component_info)|
    puts "## #{component.nil? ? 'Global changes' : "Changes for #{component}"}\n" if commits_per_component.size > 1 || !component.nil?
    component_info.each do |bump_level, commits|
      puts "### #{
        case bump_level
        when 0
          'Patches'
        when 1
          'Features'
        when 2
          'Breaking changes'
        else
          raise "Invalid bump level: #{bump_level}"
        end
      }"
      puts
      # Gather an ordered set of commit lines (with the corresponding commit sha) in order to not duplicate the info when there are merge commits
      # Hash< String, String >
      commit_lines = {}
      commits.each do |commit|
        message_lines = commit.message.split("\n")
        commit_line = message_lines.first
        if commit_line =~ /^Merge pull request .+$/
          # Consider the next line as commit line
          next_line = message_lines[1..-1].join("\n").strip.split("\n").first
          commit_line = next_line unless next_line.nil?
        end
        commit_lines[commit_line] = commit.sha
      end
      commit_lines.each do |commit_line, commit_sha|
        puts "* [#{commit_line}](#{@git_hosting.commit_url(git_url, commit_sha)})"
      end
      puts
    end
  end
end