Welcome to mirror list, hosted at ThFree Co, Russian Federation.

operation_service.rb « git « gitlab « lib « ruby - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 74951ade0d0d334112d1c840efaa7a333e06c4de (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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
module Gitlab
  module Git
    class OperationService
      include Gitlab::Git::Popen

      BranchUpdate = Struct.new(:newrev, :repo_created, :branch_created) do
        alias_method :repo_created?, :repo_created
        alias_method :branch_created?, :branch_created

        def self.from_gitaly(branch_update)
          return if branch_update.nil?

          new(
            branch_update.commit_id,
            branch_update.repo_created,
            branch_update.branch_created
          )
        end
      end

      attr_reader :user, :repository

      def initialize(user, new_repository)
        @user = user

        # Refactoring aid
        Gitlab::Git.check_namespace!(new_repository)

        @repository = new_repository
      end

      def add_branch(branch_name, newrev)
        ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
        oldrev = Gitlab::Git::BLANK_SHA

        update_ref_in_hooks(ref, newrev, oldrev)
      end

      def rm_branch(branch)
        ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
        oldrev = branch.target
        newrev = Gitlab::Git::BLANK_SHA

        update_ref_in_hooks(ref, newrev, oldrev)
      end

      def add_tag(tag_name, newrev, options = {})
        ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
        oldrev = Gitlab::Git::BLANK_SHA

        with_hooks(ref, newrev, oldrev) do |service|
          # We want to pass the OID of the tag object to the hooks. For an
          # annotated tag we don't know that OID until after the tag object
          # (raw_tag) is created in the repository. That is why we have to
          # update the value after creating the tag object. Only the
          # "post-receive" hook will receive the correct value in this case.
          raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
          service.newrev = raw_tag.target_id
        end
      end

      def rm_tag(tag)
        ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
        oldrev = tag.target
        newrev = Gitlab::Git::BLANK_SHA

        update_ref_in_hooks(ref, newrev, oldrev) do
          repository.rugged.tags.delete(tag_name)
        end
      end

      # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
      # it would be created from `start_branch_name`.
      # If `start_repository` is passed, and the branch doesn't exist,
      # it would try to find the commits from it instead of current repository.
      def with_branch(
        branch_name,
        start_branch_name: nil,
        start_repository: repository,
        force: false,
        &block
      )

        Gitlab::Git.check_namespace!(start_repository)
        start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)

        start_branch_name = nil if start_repository.empty?

        raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.relative_path}" if start_branch_name && !start_repository.branch_exists?(start_branch_name)

        update_branch_with_hooks(branch_name, force) do
          repository.with_repo_branch_commit(
            start_repository,
            start_branch_name || branch_name,
            &block
          )
        end
      end

      def update_branch(branch_name, newrev, oldrev)
        ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
        update_ref_in_hooks(ref, newrev, oldrev)
      end

      # Yields the given block (which should return a commit) and
      # writes it to the ref while also executing hooks for it.
      # The ref is _always_ overwritten (nothing is taken from its
      # previous state).
      #
      # Returns the generated commit.
      #
      # ref - The target ref path we're commiting to.
      # from_branch - The branch we're taking the HEAD commit from.
      def commit_ref(ref, from_branch:)
        update_autocrlf_option

        repository.write_ref(ref, from_branch.target)

        # Make commit
        newrev = yield

        raise Gitlab::Git::CommitError.new('Failed to create commit') unless newrev

        oldrev = from_branch.target

        update_ref_in_hooks(ref, newrev, oldrev)

        newrev
      end

      private

      # Returns [newrev, should_run_after_create, should_run_after_create_branch]
      def update_branch_with_hooks(branch_name, force)
        update_autocrlf_option

        was_empty = repository.empty?

        # Make commit
        newrev = yield

        raise Gitlab::Git::CommitError.new('Failed to create commit') unless newrev

        branch = repository.find_branch(branch_name)
        oldrev = find_oldrev_from_branch(newrev, branch, force)

        ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
        update_ref_in_hooks(ref, newrev, oldrev)

        BranchUpdate.new(newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev))
      end

      def find_oldrev_from_branch(newrev, branch, force)
        return Gitlab::Git::BLANK_SHA unless branch

        oldrev = branch.target

        return oldrev if force

        merge_base = repository.merge_base(newrev, branch.target)
        raise Gitlab::Git::Repository::InvalidRef unless merge_base

        if oldrev == merge_base
          oldrev
        else
          raise Gitlab::Git::CommitError.new('Branch diverged')
        end
      end

      def update_ref_in_hooks(ref, newrev, oldrev)
        with_hooks(ref, newrev, oldrev) do
          update_ref(ref, newrev, oldrev)
        end
      end

      def with_hooks(ref, newrev, oldrev)
        Gitlab::Git::HooksService.new.execute(
          user,
          repository,
          oldrev,
          newrev,
          ref
        ) do |service|

          yield(service)
        end
      end

      def update_ref(ref, newrev, oldrev)
        # We use 'git update-ref' because libgit2/rugged currently does not
        # offer 'compare and swap' ref updates. Without compare-and-swap we can
        # (and have!) accidentally reset the ref to an earlier state, clobbering
        # commits. See also https://github.com/libgit2/libgit2/issues/1534.
        command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]

        output, status = popen(
          command,
          repository.path
        ) do |stdin|
          stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
        end

        unless status.zero?
          Gitlab::GitLogger.error("'git update-ref' in #{repository.path}: #{output}")
          raise Gitlab::Git::CommitError.new(
            "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
            " Please refresh and try again."
          )
        end
      end

      def update_autocrlf_option
        repository.autocrlf = :input if repository.autocrlf != :input
      end
    end
  end
end