git_operation_service.rb 4.48 KB
Newer Older
1
2
3
4
5
6
7
8
class GitOperationService
  attr_reader :user, :repository

  def initialize(new_user, new_repository)
    @user = new_user
    @repository = new_repository
  end

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

13
    update_ref_in_hooks(ref, newrev, oldrev)
14
15
16
17
  end

  def rm_branch(branch)
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
Lin Jen-Shin's avatar
Lin Jen-Shin committed
18
    oldrev = branch.target
19
20
    newrev = Gitlab::Git::BLANK_SHA

21
    update_ref_in_hooks(ref, newrev, oldrev)
22
23
24
25
26
27
  end

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

28
    with_hooks(ref, newrev, oldrev) do |service|
Lin Jen-Shin's avatar
Lin Jen-Shin committed
29
30
31
32
33
      # 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.
34
35
36
37
38
      raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
      service.newrev = raw_tag.target_id
    end
  end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
39
40
41
42
43
44
45
46
47
48
  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

49
50
51
52
  # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
  # it would be created from `start_branch_name`.
  # If `start_project` is passed, and the branch doesn't exist,
  # it would try to find the commits from it instead of current repository.
53
54
  def with_branch(
    branch_name,
55
56
    start_branch_name: nil,
    start_project: repository.project,
57
    &block)
58

59
60
    start_repository = start_project.repository
    start_branch_name = nil if start_repository.empty_repo?
61

62
    if start_branch_name && !start_repository.branch_exists?(start_branch_name)
63
      raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}"
64
    end
65

66
    update_branch_with_hooks(branch_name) do
67
      repository.with_repo_branch_commit(
68
69
        start_repository,
        start_branch_name || branch_name,
70
        &block)
71
72
73
74
75
    end
  end

  private

76
  def update_branch_with_hooks(branch_name)
77
    update_autocrlf_option
78

79
    was_empty = repository.empty?
80

81
    # Make commit
82
    newrev = yield
83

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

88
    branch = repository.find_branch(branch_name)
89
    oldrev = find_oldrev_from_branch(newrev, branch)
90

91
    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
92
93
94
95
    update_ref_in_hooks(ref, newrev, oldrev)

    # If repo was empty expire cache
    repository.after_create if was_empty
Lin Jen-Shin's avatar
Lin Jen-Shin committed
96
97
    repository.after_create_branch if
      was_empty || Gitlab::Git.blank_ref?(oldrev)
98
99

    newrev
100
101
  end

102
103
104
105
106
107
108
109
110
111
112
113
  def find_oldrev_from_branch(newrev, branch)
    return Gitlab::Git::BLANK_SHA unless branch

    oldrev = branch.target

    if oldrev == repository.rugged.merge_base(newrev, branch.target)
      oldrev
    else
      raise Repository::CommitError.new('Branch diverged')
    end
  end

114
115
116
  def update_ref_in_hooks(ref, newrev, oldrev)
    with_hooks(ref, newrev, oldrev) do
      update_ref(ref, newrev, oldrev)
117
118
119
    end
  end

120
  def with_hooks(ref, newrev, oldrev)
121
122
    GitHooksService.new.execute(
      user,
123
      repository.project,
124
125
126
127
      oldrev,
      newrev,
      ref) do |service|

128
      yield(service)
129
130
131
    end
  end

Jacob Vosmaer's avatar
Jacob Vosmaer committed
132
  # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites.
133
  def update_ref(ref, newrev, oldrev)
134
135
136
137
138
139
140
141
    # 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|
142
      stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
143
144
145
146
    end

    unless status.zero?
      raise Repository::CommitError.new(
147
        "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
148
149
150
151
152
153
154
155
156
157
        " Please refresh and try again.")
    end
  end

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