Class: Repositories::ChangelogService

Inherits:
Object
  • Object
show all
Defined in:
app/services/repositories/changelog_service.rb

Overview

A service class for generating a changelog section.

Constant Summary collapse

DEFAULT_TRAILER =
'Changelog'
DEFAULT_FILE =
'CHANGELOG.md'
COMMITS_LIMIT =

The maximum number of commits allowed to fetch in from and to range.

This value is arbitrarily chosen. Increasing it means more Gitaly calls and more presure on Gitaly services.

This number is 3x of the average number of commits per GitLab releases. Some examples for GitLab’s own releases:

  • 13.6.0: 4636 commits

  • 13.5.0: 5912 commits

  • 13.4.0: 5541 commits

15_000

Instance Method Summary collapse

Constructor Details

#initialize(project, user, version:, branch: project.default_branch_or_main, from: nil, to: branch, date: DateTime.now, trailer: DEFAULT_TRAILER, config_file: Gitlab::Changelog::Config::DEFAULT_FILE_PATH, config_file_ref: Gitlab::Changelog::Config::DEFAULT_CONFIG_FILE_REFERENCE, file: DEFAULT_FILE, message: "Add changelog for version #{version}") ⇒ ChangelogService

The project specifies the Project to generate the changelog section for.

The user argument specifies a User to use for committing the changes to the Git repository.

The version arguments must be a version String using semantic versioning as the format.

The arguments from and to must specify a Git ref or SHA to use for fetching the commits to include in the changelog. The SHA/ref set in the from argument isn’t included in the list.

The date argument specifies the date of the release, and defaults to the current time/date.

The branch argument specifies the branch to commit the changes to. The branch must already exist.

The trailer argument is the Git trailer to use for determining what commits to include in the changelog.

The config_file arguments specifies the path to the configuration file as stored in the project’s Git repository.

The config_file_ref argument specifies the ref where the configuration file is stored. By default, it’s a default repository branch.

The file arguments specifies the name/path of the file to commit the changes to. If the file doesn’t exist, it’s created automatically.

The message argument specifies the commit message to use when committing the changelog changes.

rubocop: disable Metrics/ParameterLists



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/services/repositories/changelog_service.rb', line 57

def initialize(
  project,
  user,
  version:,
  branch: project.default_branch_or_main,
  from: nil,
  to: branch,
  date: DateTime.now,
  trailer: DEFAULT_TRAILER,
  config_file: Gitlab::Changelog::Config::DEFAULT_FILE_PATH,
  config_file_ref: Gitlab::Changelog::Config::DEFAULT_CONFIG_FILE_REFERENCE,
  file: DEFAULT_FILE,
  message: "Add changelog for version #{version}"
)
  @project = project
  @user = user
  @version = version
  @from = from
  @to = to
  @date = date
  @branch = branch
  @trailer = trailer
  @config_file = config_file
  @config_file_ref = config_file_ref
  @file = file
  @message = message
end

Instance Method Details

#execute(commit_to_changelog: true) ⇒ Object

rubocop: enable Metrics/ParameterLists



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'app/services/repositories/changelog_service.rb', line 86

def execute(commit_to_changelog: true)
  config = Gitlab::Changelog::Config.from_git(@project, @user, @config_file, @config_file_ref)
  from = start_of_commit_range(config)

  # For every entry we want to only include the merge request that
  # originally introduced the commit, which is the oldest merge request that
  # contains the commit. We fetch there merge requests in batches, reducing
  # the number of SQL queries needed to get this data.
  mrs_finder = MergeRequests::OldestPerCommitFinder.new(@project)
  release = Gitlab::Changelog::Release
    .new(version: @version, date: @date, config: config)

  commits =
    ChangelogCommitsFinder.new(project: @project, from: from, to: @to)

  verify_commit_range!(from, @to)

  commits.each_page(@trailer) do |page|
    mrs = mrs_finder.execute(page)

    # Preload the authors. This ensures we only need a single SQL query per
    # batch of commits, instead of needing a query for every commit.
    page.each(&:lazy_author)

    # Preload author permissions
    @project.team.max_member_access_for_user_ids(page.map(&:author).compact.map(&:id))

    page.each do |commit|
      release.add_entry(
        title: commit.title,
        commit: commit,
        category: commit.trailers.fetch(@trailer),
        author: commit.author,
        merge_request: mrs[commit.id]
      )
    end
  end

  if commit_to_changelog
    Gitlab::Changelog::Committer
      .new(@project, @user)
      .commit(release: release, file: @file, branch: @branch, message: @message)
  else
    Gitlab::Changelog::Generator.new.add(release)
  end
end

#start_of_commit_range(config) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'app/services/repositories/changelog_service.rb', line 133

def start_of_commit_range(config)
  return @from if @from

  finder = ChangelogTagFinder.new(@project, regex: config.tag_regex)

  if (prev_tag = finder.execute(@version))
    return prev_tag.target_commit.id
  end

  raise(
    Gitlab::Changelog::Error,
    'The commit start range is unspecified, and no previous tag ' \
      'could be found to use instead'
  )
end

#verify_commit_range!(from, to) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
# File 'app/services/repositories/changelog_service.rb', line 149

def verify_commit_range!(from, to)
  commits = @project.repository.commits_by(oids: [from, to])

  raise Gitlab::Changelog::Error, "Invalid or not found commit value in the given range" unless commits.count == 2

  _, commits_count = @project.repository.diverging_commit_count(from, to)

  if commits_count > COMMITS_LIMIT
    raise Gitlab::Changelog::Error, "The commits range exceeds #{COMMITS_LIMIT} elements."
  end
end