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

git_operation_service.rb « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3b7f702e3abb7e88ef509b82615f2d9ecdd79434 (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
class GitOperationService
  attr_reader :user, :repository

  def initialize(new_user, new_repository)
    @user = new_user
    @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 `source_branch_name` is passed, if `branch_name` doesn't exist,
  # it would be created from `source_branch_name`.
  # If `source_project` is passed, and the branch doesn't exist,
  # it would try to find the source from it instead of current repository.
  def with_branch(
    branch_name,
    source_branch_name: nil,
    source_project: repository.project,
    &block)

    check_with_branch_arguments!(
      branch_name, source_branch_name, source_project)

    update_branch_with_hooks(branch_name) do
      repository.with_repo_branch_commit(
        source_project.repository,
        source_branch_name || branch_name,
        &block)
    end
  end

  private

  def update_branch_with_hooks(branch_name)
    update_autocrlf_option

    was_empty = repository.empty?

    # Make commit
    newrev = yield

    unless newrev
      raise Repository::CommitError.new('Failed to create commit')
    end

    branch = repository.find_branch(branch_name)
    oldrev = if branch
               # This could verify we're not losing commits
               repository.rugged.merge_base(newrev, branch.target)
             else
               Gitlab::Git::BLANK_SHA
             end

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

    # If repo was empty expire cache
    repository.after_create if was_empty
    repository.after_create_branch if
      was_empty || Gitlab::Git.blank_ref?(oldrev)

    newrev
  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)
    result = nil

    GitHooksService.new.execute(
      user,
      repository.path_to_repo,
      oldrev,
      newrev,
      ref) do |service|

      result = yield(service) if block_given?
    end

    result
  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]
    _, status = Gitlab::Popen.popen(
      command,
      repository.path_to_repo) do |stdin|
      stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
    end

    unless status.zero?
      raise Repository::CommitError.new(
        "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
        " Please refresh and try again.")
    end
  end

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

  def check_with_branch_arguments!(
    branch_name, source_branch_name, source_project)
    return if repository.branch_exists?(branch_name)

    if repository.project != source_project
      unless source_branch_name
        raise ArgumentError,
          'Should also pass :source_branch_name if' +
          ' :source_project is different from current project'
      end

      unless source_project.repository.branch_exists?(source_branch_name)
        raise ArgumentError,
          "Cannot find branch #{branch_name} nor" \
          " #{source_branch_name} from" \
          " #{source_project.path_with_namespace}"
      end
    elsif source_branch_name
      unless repository.branch_exists?(source_branch_name)
        raise ArgumentError,
          "Cannot find branch #{branch_name} nor" \
          " #{source_branch_name} from" \
          " #{repository.project.path_with_namespace}"
      end
    end
  end
end