repository.rb 7.41 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

Dmitriy Zaporozhets's avatar
Dmitriy 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 66 67 68

    gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
  end

69
  def add_tag(tag_name, ref, message = nil)
70
    cache.expire(:tag_names)
71

72
    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
73 74
  end

75
  def rm_branch(branch_name)
76
    cache.expire(:branch_names)
77

78 79 80
    gitlab_shell.rm_branch(path_with_namespace, branch_name)
  end

81
  def rm_tag(tag_name)
82
    cache.expire(:tag_names)
83

84 85 86
    gitlab_shell.rm_tag(path_with_namespace, tag_name)
  end

87 88 89 90 91 92 93 94 95 96 97 98
  def round_commit_count
    if commit_count > 10000
      '10000+'
    elsif commit_count > 5000
      '5000+'
    elsif commit_count > 1000
      '1000+'
    else
      commit_count
    end
  end

99
  def branch_names
100
    cache.fetch(:branch_names) { raw_repository.branch_names }
101 102 103
  end

  def tag_names
104
    cache.fetch(:tag_names) { raw_repository.tag_names }
105 106
  end

107
  def commit_count
108
    cache.fetch(:commit_count) do
109
      begin
110
        raw_repository.commit_count(self.root_ref)
111 112 113
      rescue
        0
      end
114
    end
115 116
  end

117 118 119
  # Return repo size in megabytes
  # Cached in redis
  def size
120
    cache.fetch(:size) { raw_repository.size }
121 122 123
  end

  def expire_cache
124 125 126 127
    %i(size branch_names tag_names commit_count graph_log
       readme version contribution_guide).each do |key|
      cache.expire(key)
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
128 129 130
  end

  def graph_log
131 132
    cache.fetch(:graph_log) do
      commits = raw_repository.log(limit: 6000, skip_merges: true,
133
                                   ref: root_ref)
134

Vinnie Okada's avatar
Vinnie Okada committed
135
      commits.map do |rugged_commit|
136
        commit = Gitlab::Git::Commit.new(rugged_commit)
137

Vinnie Okada's avatar
Vinnie Okada committed
138 139 140 141
        {
          author_name: commit.author_name.force_encoding('UTF-8'),
          author_email: commit.author_email.force_encoding('UTF-8'),
          additions: commit.stats.additions,
142
          deletions: commit.stats.deletions,
Vinnie Okada's avatar
Vinnie Okada committed
143 144
        }
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
145
    end
146 147
  end

148
  def timestamps_by_user_log(user)
149 150
    author_emails = '(' + user.all_emails.map{ |e| Regexp.escape(e) }.join('|') + ')'
    args = %W(git log -E --author=#{author_emails} --since=#{(Date.today - 1.year).to_s} --branches --pretty=format:%cd --date=short)
151
    dates = Gitlab::Popen.popen(args, path_to_repo).first.split("\n")
152

153 154 155 156 157
    if dates.present?
      dates
    else
      []
    end
158 159
  end

160 161
  def commits_per_day_for_user(user)
    timestamps_by_user_log(user).
162 163 164 165 166 167 168
      group_by { |commit_date| commit_date }.
      inject({}) do |hash, (timestamp_date, commits)|
        hash[timestamp_date] = commits.count
        hash
      end
  end

169 170 171 172
  def method_missing(m, *args, &block)
    raw_repository.send(m, *args, &block)
  end

173
  def respond_to?(method)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
174
    return true if raw_repository.respond_to?(method)
175 176 177

    super
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
178 179 180 181

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

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

187
  def readme
188
    cache.fetch(:readme) { tree(:head).readme }
189
  end
190

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

199
  def contribution_guide
200
    cache.fetch(:contribution_guide) { tree(:head).contribution_guide }
201 202
  end

203 204 205 206 207 208 209 210 211 212 213
  def head_commit
    commit(self.root_ref)
  end

  def tree(sha = :head, path = nil)
    if sha == :head
      sha = head_commit.sha
    end

    Tree.new(self, sha, path)
  end
214 215

  def blob_at_branch(branch_name, path)
216
    last_commit = commit(branch_name)
217

218 219 220 221 222
    if last_commit
      blob_at(last_commit.sha, path)
    else
      nil
    end
223
  end
224 225 226 227 228 229 230 231

  # Returns url for submodule
  #
  # Ex.
  #   @repository.submodule_url_for('master', 'rack')
  #   # => git@localhost:rack.git
  #
  def submodule_url_for(ref, path)
232
    if submodules(ref).any?
233 234 235 236 237 238 239
      submodule = submodules(ref)[path]

      if submodule
        submodule['url']
      end
    end
  end
240 241

  def last_commit_for_path(sha, path)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
242
    args = %W(git rev-list --max-count=1 #{sha} -- #{path})
243 244
    sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
    commit(sha)
245
  end
246 247 248

  # Remove archives older than 2 hours
  def clean_old_archives
249 250
    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))
251
  end
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266

  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
267 268

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

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
271
    commits.group_by(&:author_email).map do |email, commits|
272 273
      contributor = Gitlab::Contributor.new
      contributor.email = email
274

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
275
      commits.each do |commit|
276
        if contributor.name.blank?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
277
          contributor.name = commit.author_name
278 279
        end

280
        contributor.commits += 1
281 282
      end

283 284
      contributor
    end
285
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301

  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
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318

  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
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335

  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
336 337 338 339 340 341

  private

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