repository.rb 7.71 KB
Newer Older
1
class Repository
2 3
  include Gitlab::ShellAdapter

4
  attr_accessor :raw_repository, :path_with_namespace
5

6
  def initialize(path_with_namespace, default_branch = nil)
7
    @path_with_namespace = path_with_namespace
8
    @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace
9 10 11 12
  rescue Gitlab::Git::Repository::NoRepository
    nil
  end

13
  # Return absolute path to repository
14
  def path_to_repo
15 16 17
    @path_to_repo ||= File.expand_path(
      File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
    )
18 19
  end

20 21 22 23 24 25
  def exists?
    raw_repository
  end

  def empty?
    raw_repository.empty?
26 27
  end

28
  def commit(id = 'HEAD')
29
    return nil unless raw_repository
30
    commit = Gitlab::Git::Commit.find(raw_repository, id)
31 32
    commit = Commit.new(commit) if commit
    commit
33
  rescue Rugged::OdbError
34
    nil
35 36
  end

Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
37
  def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
38 39 40 41 42 43 44
    commits = Gitlab::Git::Commit.where(
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
    )
45
    commits = Commit.decorate(commits) if commits.present?
46 47 48
    commits
  end

49 50
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
51
    commits = Commit.decorate(commits) if commits.present?
52 53 54
    commits
  end

55 56 57 58 59 60 61 62
  def find_branch(name)
    branches.find { |branch| branch.name == name }
  end

  def find_tag(name)
    tags.find { |tag| tag.name == name }
  end

63
  def add_branch(branch_name, ref)
64
    cache.expire(:branch_names)
65
    @branches = nil
66 67 68 69

    gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
  end

70
  def add_tag(tag_name, ref, message = nil)
71
    cache.expire(:tag_names)
72
    @tags = nil
73

74
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
75 76
  end

77
  def rm_branch(branch_name)
78
    cache.expire(:branch_names)
79
    @branches = nil
80

81 82 83
    gitlab_shell.rm_branch(path_with_namespace, branch_name)
  end

84
  def rm_tag(tag_name)
85
    cache.expire(:tag_names)
86
    @tags = nil
87

88 89 90
    gitlab_shell.rm_tag(path_with_namespace, tag_name)
  end

91 92 93 94 95 96 97 98 99 100 101 102
  def round_commit_count
    if commit_count > 10000
      '10000+'
    elsif commit_count > 5000
      '5000+'
    elsif commit_count > 1000
      '1000+'
    else
      commit_count
    end
  end

103
  def branch_names
104
    cache.fetch(:branch_names) { raw_repository.branch_names }
105 106 107
  end

  def tag_names
108
    cache.fetch(:tag_names) { raw_repository.tag_names }
109 110
  end

111
  def commit_count
112
    cache.fetch(:commit_count) do
113
      begin
114
        raw_repository.commit_count(self.root_ref)
115 116 117
      rescue
        0
      end
118
    end
119 120
  end

121 122 123
  # Return repo size in megabytes
  # Cached in redis
  def size
124
    cache.fetch(:size) { raw_repository.size }
125 126 127
  end

  def expire_cache
128
    %i(size branch_names tag_names commit_count graph_log
129
       readme version contribution_guide changelog license).each do |key|
130 131
      cache.expire(key)
    end
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
132 133 134
  end

  def graph_log
135 136
    cache.fetch(:graph_log) do
      commits = raw_repository.log(limit: 6000, skip_merges: true,
137
                                   ref: root_ref)
138

Vinnie Okada's avatar
Vinnie Okada committed
139
      commits.map do |rugged_commit|
140
        commit = Gitlab::Git::Commit.new(rugged_commit)
141

Vinnie Okada's avatar
Vinnie Okada committed
142
        {
143 144
          author_name: commit.author_name,
          author_email: commit.author_email,
Vinnie Okada's avatar
Vinnie Okada committed
145
          additions: commit.stats.additions,
146
          deletions: commit.stats.deletions,
Vinnie Okada's avatar
Vinnie Okada committed
147 148
        }
      end
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
149
    end
150 151
  end

152 153 154 155
  def lookup_cache
    @lookup_cache ||= {}
  end

156
  def method_missing(m, *args, &block)
157 158 159 160 161 162
    if m == :lookup && !block_given?
      lookup_cache[m] ||= {}
      lookup_cache[m][args.join(":")] ||= raw_repository.send(m, *args, &block)
    else
      raw_repository.send(m, *args, &block)
    end
163 164
  end

165
  def respond_to?(method)
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
166
    return true if raw_repository.respond_to?(method)
167 168 169

    super
  end
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
170 171 172 173

  def blob_at(sha, path)
    Gitlab::Git::Blob.find(self, sha, path)
  end
174

175 176 177 178
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

179
  def readme
180
    cache.fetch(:readme) { tree(:head).readme }
181
  end
182

183
  def version
184
    cache.fetch(:version) do
185 186 187 188 189 190
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

191
  def contribution_guide
192 193 194 195 196 197
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
198 199 200 201 202 203 204

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
        file.name =~ /^(changelog|history)/i
      end
    end
205 206
  end

207 208 209 210 211 212
  def license
    cache.fetch(:license) do
      tree(:head).blobs.find do |file|
        file.name =~ /^license/i
      end
    end
213 214
  end

215
  def head_commit
216 217 218 219 220
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
221 222 223 224
  end

  def tree(sha = :head, path = nil)
    if sha == :head
225 226 227 228 229
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
230 231 232 233
    end

    Tree.new(self, sha, path)
  end
234 235

  def blob_at_branch(branch_name, path)
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
236
    last_commit = commit(branch_name)
237

Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
238 239 240 241 242
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
243
  end
244 245 246 247 248 249 250 251

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => [email protected]:rack.git
  #
  def submodule_url_for(ref, path)
252
    if submodules(ref).any?
253 254 255 256 257 258 259
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
260 261

  def last_commit_for_path(sha, path)
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
262
    args = %W(git rev-list --max-count=1 #{sha} -- #{path})
263 264
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
265
  end
266 267 268

  # Remove archives older than 2 hours
  def clean_old_archives
269 270
    repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
    Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
271
  end
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

  def branches_sorted_by(value)
    case value
    when 'recently_updated'
      branches.sort do |a, b|
        commit(b.target).committed_date <=> commit(a.target).committed_date
      end
    when 'last_updated'
      branches.sort do |a, b|
        commit(a.target).committed_date <=> commit(b.target).committed_date
      end
    else
      branches
    end
  end
287 288

  def contributors
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
289
    commits = self.commits(nil, nil, 2000, 0, true)
290

Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
291
    commits.group_by(&:author_email).map do |email, commits|
292 293
      contributor = Gitlab::Contributor.new
      contributor.email = email
294

Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
295
      commits.each do |commit|
296
        if contributor.name.blank?
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
297
          contributor.name = commit.author_name
298 299
        end

300
        contributor.commits += 1
301 302
      end

303 304
      contributor
    end
305
  end
Dmytro Zaporozhets's avatar
Dmytro Zaporozhets committed
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321

  def blob_for_diff(commit, diff)
    file = blob_at(commit.id, diff.new_path)

    unless file
      file = prev_blob_for_diff(commit, diff)
    end

    file
  end

  def prev_blob_for_diff(commit, diff)
    if commit.parent_id
      blob_at(commit.parent_id, diff.old_path)
    end
  end
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338

  def branch_names_contains(sha)
    args = %W(git branch --contains #{sha})
    names = Gitlab::Popen.popen(args, path_to_repo).first

    if names.respond_to?(:split)
      names = names.split("\n").map(&:strip)

      names.each do |name|
        name.slice! '* '
      end

      names
    else
      []
    end
  end
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355

  def tag_names_contains(sha)
    args = %W(git tag --contains #{sha})
    names = Gitlab::Popen.popen(args, path_to_repo).first

    if names.respond_to?(:split)
      names = names.split("\n").map(&:strip)

      names.each do |name|
        name.slice! '* '
      end

      names
    else
      []
    end
  end
356

357 358 359 360 361 362 363 364 365 366 367 368
  def branches
    @branches ||= raw_repository.branches
  end

  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
    @root_ref ||= raw_repository.root_ref
  end

369 370 371 372 373
  private

  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
374
end