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

4
  attr_accessor :raw_repository, :path_with_namespace, :project
5

6
  def initialize(path_with_namespace, default_branch = nil, project = nil)
7
    @path_with_namespace = path_with_namespace
8
    @project = project
9 10

    if path_with_namespace
11
      @raw_repository = Gitlab::Git::Repository.new(path_to_repo)
12 13 14
      @raw_repository.autocrlf = :input
    end

15 16 17 18
  rescue Gitlab::Git::Repository::NoRepository
    nil
  end

19
  # Return absolute path to repository
20
  def path_to_repo
21 22 23
    @path_to_repo ||= File.expand_path(
      File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
    )
24 25
  end

26 27 28 29 30 31
  def exists?
    raw_repository
  end

  def empty?
    raw_repository.empty?
32 33
  end

34
  def commit(id = 'HEAD')
35
    return nil unless raw_repository
36
    commit = Gitlab::Git::Commit.find(raw_repository, id)
37
    commit = Commit.new(commit, @project) if commit
38
    commit
39
  rescue Rugged::OdbError
40
    nil
41 42
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
43
  def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
44 45 46 47 48 49 50
    commits = Gitlab::Git::Commit.where(
      repo: raw_repository,
      ref: ref,
      path: path,
      limit: limit,
      offset: offset,
    )
51
    commits = Commit.decorate(commits, @project) if commits.present?
52 53 54
    commits
  end

55 56
  def commits_between(from, to)
    commits = Gitlab::Git::Commit.between(raw_repository, from, to)
57
    commits = Commit.decorate(commits, @project) if commits.present?
58 59 60
    commits
  end

61 62 63 64 65 66 67 68
  def find_branch(name)
    branches.find { |branch| branch.name == name }
  end

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

69
  def add_branch(branch_name, ref)
70
    cache.expire(:branch_names)
71
    @branches = nil
72 73 74 75

    gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
  end

76
  def add_tag(tag_name, ref, message = nil)
77
    cache.expire(:tag_names)
78
    @tags = nil
79

80
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
81 82
  end

83
  def rm_branch(branch_name)
84
    cache.expire(:branch_names)
85
    @branches = nil
86

87 88 89
    gitlab_shell.rm_branch(path_with_namespace, branch_name)
  end

90
  def rm_tag(tag_name)
91
    cache.expire(:tag_names)
92
    @tags = nil
93

94 95 96
    gitlab_shell.rm_tag(path_with_namespace, tag_name)
  end

97 98 99 100 101 102 103 104 105 106 107 108
  def round_commit_count
    if commit_count > 10000
      '10000+'
    elsif commit_count > 5000
      '5000+'
    elsif commit_count > 1000
      '1000+'
    else
      commit_count
    end
  end

109
  def branch_names
110
    cache.fetch(:branch_names) { raw_repository.branch_names }
111 112 113
  end

  def tag_names
114
    cache.fetch(:tag_names) { raw_repository.tag_names }
115 116
  end

117
  def commit_count
118
    cache.fetch(:commit_count) do
119
      begin
120
        raw_repository.commit_count(self.root_ref)
121 122 123
      rescue
        0
      end
124
    end
125 126
  end

127 128 129
  # Return repo size in megabytes
  # Cached in redis
  def size
130
    cache.fetch(:size) { raw_repository.size }
131 132 133
  end

  def expire_cache
134
    %i(size branch_names tag_names commit_count graph_log
135
       readme version contribution_guide changelog license).each do |key|
136 137
      cache.expire(key)
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
138 139 140
  end

  def graph_log
141 142
    cache.fetch(:graph_log) do
      commits = raw_repository.log(limit: 6000, skip_merges: true,
143
                                   ref: root_ref)
144

Vinnie Okada's avatar
Vinnie Okada committed
145
      commits.map do |rugged_commit|
146
        commit = Gitlab::Git::Commit.new(rugged_commit)
147

Vinnie Okada's avatar
Vinnie Okada committed
148
        {
149 150
          author_name: commit.author_name,
          author_email: commit.author_email,
Vinnie Okada's avatar
Vinnie Okada committed
151
          additions: commit.stats.additions,
152
          deletions: commit.stats.deletions,
Vinnie Okada's avatar
Vinnie Okada committed
153 154
        }
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
155
    end
156 157
  end

158 159 160 161
  def lookup_cache
    @lookup_cache ||= {}
  end

162
  def method_missing(m, *args, &block)
163 164 165 166 167 168
    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
169 170
  end

171 172
  def respond_to_missing?(method, include_private = false)
    raw_repository.respond_to?(method, include_private) || super
173
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
174 175

  def blob_at(sha, path)
176 177 178
    unless Gitlab::Git.blank_ref?(sha)
      Gitlab::Git::Blob.find(self, sha, path)
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
179
  end
180

181 182 183 184
  def blob_by_oid(oid)
    Gitlab::Git::Blob.raw(self, oid)
  end

185
  def readme
186
    cache.fetch(:readme) { tree(:head).readme }
187
  end
188

189
  def version
190
    cache.fetch(:version) do
191 192 193 194 195 196
      tree(:head).blobs.find do |file|
        file.name.downcase == 'version'
      end
    end
  end

197
  def contribution_guide
198 199 200 201 202 203
    cache.fetch(:contribution_guide) do
      tree(:head).blobs.find do |file|
        file.contributing?
      end
    end
  end
204 205 206 207

  def changelog
    cache.fetch(:changelog) do
      tree(:head).blobs.find do |file|
208
        file.name =~ /\A(changelog|history)/i
209 210
      end
    end
211 212
  end

213 214 215
  def license
    cache.fetch(:license) do
      tree(:head).blobs.find do |file|
216
        file.name =~ /\Alicense/i
217 218
      end
    end
219 220
  end

221
  def head_commit
222 223 224 225 226
    @head_commit ||= commit(self.root_ref)
  end

  def head_tree
    @head_tree ||= Tree.new(self, head_commit.sha, nil)
227 228 229 230
  end

  def tree(sha = :head, path = nil)
    if sha == :head
231 232 233 234 235
      if path.nil?
        return head_tree
      else
        sha = head_commit.sha
      end
236 237 238 239
    end

    Tree.new(self, sha, path)
  end
240 241

  def blob_at_branch(branch_name, path)
242
    last_commit = commit(branch_name)
243

244 245 246 247 248
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
249
  end
250 251 252 253 254 255 256 257

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
258
    if submodules(ref).any?
259 260 261 262 263 264 265
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
266 267

  def last_commit_for_path(sha, path)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
268
    args = %W(git rev-list --max-count=1 #{sha} -- #{path})
269 270
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
271
  end
272 273 274

  # Remove archives older than 2 hours
  def clean_old_archives
275
    repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
276 277 278

    return unless File.directory?(repository_downloads_path)

279
    Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
280
  end
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

  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
296 297

  def contributors
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
298
    commits = self.commits(nil, nil, 2000, 0, true)
299

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
300
    commits.group_by(&:author_email).map do |email, commits|
301 302
      contributor = Gitlab::Contributor.new
      contributor.email = email
303

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
304
      commits.each do |commit|
305
        if contributor.name.blank?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
306
          contributor.name = commit.author_name
307 308
        end

309
        contributor.commits += 1
310 311
      end

312 313
      contributor
    end
314
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330

  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
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347

  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
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364

  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
365

366 367 368 369 370 371 372 373 374 375 376 377
  def branches
    @branches ||= raw_repository.branches
  end

  def tags
    @tags ||= raw_repository.tags
  end

  def root_ref
    @root_ref ||= raw_repository.root_ref
  end

378 379 380
  def commit_file(user, path, content, message, ref)
    path[0] = '' if path[0] == '/'

381
    committer = user_to_comitter(user)
382
    options = {}
383 384
    options[:committer] = committer
    options[:author] = committer
385 386 387 388 389 390 391 392 393 394 395 396 397
    options[:commit] = {
      message: message,
      branch: ref
    }

    options[:file] = {
      content: content,
      path: path
    }

    Gitlab::Git::Blob.commit(raw_repository, options)
  end

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
  def remove_file(user, path, message, ref)
    path[0] = '' if path[0] == '/'

    committer = user_to_comitter(user)
    options = {}
    options[:committer] = committer
    options[:author] = committer
    options[:commit] = {
      message: message,
      branch: ref
    }

    options[:file] = {
      path: path
    }

    Gitlab::Git::Blob.remove(raw_repository, options)
  end

  def user_to_comitter(user)
    {
      email: user.email,
      name: user.name,
      time: Time.now
    }
  end

425 426 427 428 429 430 431 432 433
  def can_be_merged?(source_branch, target_branch)
    our_commit = rugged.branches[target_branch].target
    their_commit = rugged.branches[source_branch].target

    if our_commit && their_commit
      !rugged.merge_commits(our_commit, their_commit).conflicts?
    end
  end

434 435 436 437 438 439
  def search_files(query, ref)
    offset = 2
    args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
    Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
  end

440
  def parse_search_result(result)
441 442 443 444
    ref = nil
    filename = nil
    startline = 0

445
    lines = result.lines
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
    lines.each_with_index do |line, index|
      if line =~ /^.*:.*:\d+:/
        ref, filename, startline = line.split(':')
        startline = startline.to_i - index
        break
      end
    end

    data = lines.map do |line|
      line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
    end

    data = data.join("")

    OpenStruct.new(
      filename: filename,
      ref: ref,
      startline: startline,
      data: data
    )
  end

468 469
  private

470 471 472
  def cache
    @cache ||= RepositoryCache.new(path_with_namespace)
  end
473
end