summaryrefslogtreecommitdiff
path: root/lib/gitlab/changelog/committer.rb
blob: 617017faa58d45b3e563f88de13218aed2d2338f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# frozen_string_literal: true

module Gitlab
  module Changelog
    # A class used for committing a release's changelog to a Git repository.
    class Committer
      CommitError = Class.new(StandardError)

      def initialize(project, user)
        @project = project
        @user = user
      end

      # Commits a release's changelog to a file on a branch.
      #
      # The `release` argument is a `Gitlab::Changelog::Release` for which to
      # update the changelog.
      #
      # The `file` argument specifies the path to commit the changes to.
      #
      # The `branch` argument specifies the branch to commit the changes on.
      #
      # The `message` argument specifies the commit message to use.
      def commit(release:, file:, branch:, message:)
        # When retrying, we need to reprocess the existing changelog from
        # scratch, otherwise we may end up throwing away changes. As such, all
        # the logic is contained within the retry block.
        Retriable.retriable(on: CommitError) do
          commit = Gitlab::Git::Commit.last_for_path(
            @project.repository,
            branch,
            file,
            literal_pathspec: true
          )

          content = blob_content(file, commit)

          # If the release has already been added (e.g. concurrently by another
          # API call), we don't want to add it again.
          break if content&.match?(release.header_start_pattern)

          service = Files::MultiService.new(
            @project,
            @user,
            commit_message: message,
            branch_name: branch,
            start_branch: branch,
            actions: [
              {
                action: content ? 'update' : 'create',
                content: Generator.new(content.to_s).add(release),
                file_path: file,
                last_commit_id: commit&.sha
              }
            ]
          )

          result = service.execute

          raise CommitError.new(result[:message]) if result[:status] != :success
        end
      end

      def blob_content(file, commit = nil)
        return unless commit

        @project.repository.blob_at(commit.sha, file)&.data
      end
    end
  end
end