Commit 676bce2ab941fcc50ef5796ff77229e238eb9b35

Authored by Dmitriy Zaporozhets
1 parent 71b0f8ea

Move Commit and Repository logic to lib/gitlab/git

lib/gitlab/git/commit.rb 0 → 100644
... ... @@ -0,0 +1,179 @@
  1 +# Gitlab::Git::Gitlab::Git::Commit is a wrapper around native Grit::Commit object
  2 +# We dont want to use grit objects inside app/
  3 +# It helps us easily migrate to rugged in future
  4 +module Gitlab
  5 + module Git
  6 + class Gitlab::Git::Commit
  7 + attr_accessor :raw_commit, :head, :refs
  8 +
  9 + delegate :message, :authored_date, :committed_date, :parents, :sha,
  10 + :date, :committer, :author, :diffs, :tree, :id, :stats,
  11 + :to_patch, to: :raw_commit
  12 +
  13 + class << self
  14 + def find_or_first(repo, commit_id = nil, root_ref)
  15 + commit = if commit_id
  16 + repo.commit(commit_id)
  17 + else
  18 + repo.commits(root_ref).first
  19 + end
  20 +
  21 + Gitlab::Git::Commit.new(commit) if commit
  22 + end
  23 +
  24 + def fresh_commits(repo, n = 10)
  25 + commits = repo.heads.map do |h|
  26 + repo.commits(h.name, n).map { |c| Gitlab::Git::Commit.new(c, h) }
  27 + end.flatten.uniq { |c| c.id }
  28 +
  29 + commits.sort! do |x, y|
  30 + y.committed_date <=> x.committed_date
  31 + end
  32 +
  33 + commits[0...n]
  34 + end
  35 +
  36 + def commits_with_refs(repo, n = 20)
  37 + commits = repo.branches.map { |ref| Gitlab::Git::Commit.new(ref.commit, ref) }
  38 +
  39 + commits.sort! do |x, y|
  40 + y.committed_date <=> x.committed_date
  41 + end
  42 +
  43 + commits[0..n]
  44 + end
  45 +
  46 + def commits_since(repo, date)
  47 + commits = repo.heads.map do |h|
  48 + repo.log(h.name, nil, since: date).each { |c| Gitlab::Git::Commit.new(c, h) }
  49 + end.flatten.uniq { |c| c.id }
  50 +
  51 + commits.sort! do |x, y|
  52 + y.committed_date <=> x.committed_date
  53 + end
  54 +
  55 + commits
  56 + end
  57 +
  58 + def commits(repo, ref, path = nil, limit = nil, offset = nil)
  59 + if path
  60 + repo.log(ref, path, max_count: limit, skip: offset)
  61 + elsif limit && offset
  62 + repo.commits(ref, limit, offset)
  63 + else
  64 + repo.commits(ref)
  65 + end.map{ |c| Gitlab::Git::Commit.new(c) }
  66 + end
  67 +
  68 + def commits_between(repo, from, to)
  69 + repo.commits_between(from, to).map { |c| Gitlab::Git::Commit.new(c) }
  70 + end
  71 +
  72 + def compare(project, from, to)
  73 + result = {
  74 + commits: [],
  75 + diffs: [],
  76 + commit: nil,
  77 + same: false
  78 + }
  79 +
  80 + return result unless from && to
  81 +
  82 + first = project.repository.commit(to.try(:strip))
  83 + last = project.repository.commit(from.try(:strip))
  84 +
  85 + if first && last
  86 + result[:same] = (first.id == last.id)
  87 + result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Gitlab::Git::Commit.new(c)}
  88 +
  89 + # Dont load diff for 100+ commits
  90 + result[:diffs] = if result[:commits].size > 100
  91 + []
  92 + else
  93 + project.repo.diff(last.id, first.id) rescue []
  94 + end
  95 +
  96 + result[:commit] = Gitlab::Git::Commit.new(first)
  97 + end
  98 +
  99 + result
  100 + end
  101 + end
  102 +
  103 + def initialize(raw_commit, head = nil)
  104 + raise "Nil as raw commit passed" unless raw_commit
  105 +
  106 + @raw_commit = raw_commit
  107 + @head = head
  108 + end
  109 +
  110 + def short_id(length = 10)
  111 + id.to_s[0..length]
  112 + end
  113 +
  114 + def safe_message
  115 + @safe_message ||= message
  116 + end
  117 +
  118 + def created_at
  119 + committed_date
  120 + end
  121 +
  122 + def author_email
  123 + author.email
  124 + end
  125 +
  126 + def author_name
  127 + author.name
  128 + end
  129 +
  130 + # Was this commit committed by a different person than the original author?
  131 + def different_committer?
  132 + author_name != committer_name || author_email != committer_email
  133 + end
  134 +
  135 + def committer_name
  136 + committer.name
  137 + end
  138 +
  139 + def committer_email
  140 + committer.email
  141 + end
  142 +
  143 + def prev_commit
  144 + @prev_commit ||= if parents.present?
  145 + Gitlab::Git::Commit.new(parents.first)
  146 + else
  147 + nil
  148 + end
  149 + end
  150 +
  151 + def prev_commit_id
  152 + prev_commit.try :id
  153 + end
  154 +
  155 + # Shows the diff between the commit's parent and the commit.
  156 + #
  157 + # Cuts out the header and stats from #to_patch and returns only the diff.
  158 + def to_diff
  159 + # see Grit::Gitlab::Git::Commit#show
  160 + patch = to_patch
  161 +
  162 + # discard lines before the diff
  163 + lines = patch.split("\n")
  164 + while !lines.first.start_with?("diff --git") do
  165 + lines.shift
  166 + end
  167 + lines.pop if lines.last =~ /^[\d.]+$/ # Git version
  168 + lines.pop if lines.last == "-- " # end of diff
  169 + lines.join("\n")
  170 + end
  171 +
  172 + def has_zero_stats?
  173 + stats.total.zero?
  174 + rescue
  175 + true
  176 + end
  177 + end
  178 + end
  179 +end
... ...
lib/gitlab/git/repository.rb 0 → 100644
... ... @@ -0,0 +1,181 @@
  1 +# Gitlab::Git::Gitlab::Git::Commit is a wrapper around native Grit::Repository object
  2 +# We dont want to use grit objects inside app/
  3 +# It helps us easily migrate to rugged in future
  4 +module Gitlab
  5 + module Git
  6 + class Repository
  7 + include Gitlab::Popen
  8 +
  9 + class NoRepository < StandardError; end
  10 +
  11 + # Repository directory name with namespace direcotry
  12 + # Examples:
  13 + # gitlab/gitolite
  14 + # diaspora
  15 + #
  16 + attr_accessor :path_with_namespace
  17 +
  18 + # Grit repo object
  19 + attr_accessor :repo
  20 +
  21 + # Default branch in the repository
  22 + attr_accessor :root_ref
  23 +
  24 + def initialize(path_with_namespace, root_ref = 'master')
  25 + @root_ref = root_ref || "master"
  26 + @path_with_namespace = path_with_namespace
  27 +
  28 + # Init grit repo object
  29 + repo
  30 + end
  31 +
  32 + def raw
  33 + repo
  34 + end
  35 +
  36 + def path_to_repo
  37 + @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
  38 + end
  39 +
  40 + def repo
  41 + @repo ||= Grit::Repo.new(path_to_repo)
  42 + rescue Grit::NoSuchPathError
  43 + raise NoRepository.new('no repository for such path')
  44 + end
  45 +
  46 + def commit(commit_id = nil)
  47 + Gitlab::Git::Commit.find_or_first(repo, commit_id, root_ref)
  48 + end
  49 +
  50 + def fresh_commits(n = 10)
  51 + Gitlab::Git::Commit.fresh_commits(repo, n)
  52 + end
  53 +
  54 + def commits_with_refs(n = 20)
  55 + Gitlab::Git::Commit.commits_with_refs(repo, n)
  56 + end
  57 +
  58 + def commits_since(date)
  59 + Gitlab::Git::Commit.commits_since(repo, date)
  60 + end
  61 +
  62 + def commits(ref, path = nil, limit = nil, offset = nil)
  63 + Gitlab::Git::Commit.commits(repo, ref, path, limit, offset)
  64 + end
  65 +
  66 + def last_commit_for(ref, path = nil)
  67 + commits(ref, path, 1).first
  68 + end
  69 +
  70 + def commits_between(from, to)
  71 + Gitlab::Git::Commit.commits_between(repo, from, to)
  72 + end
  73 +
  74 + # Returns an Array of branch names
  75 + # sorted by name ASC
  76 + def branch_names
  77 + branches.map(&:name)
  78 + end
  79 +
  80 + # Returns an Array of Branches
  81 + def branches
  82 + repo.branches.sort_by(&:name)
  83 + end
  84 +
  85 + # Returns an Array of tag names
  86 + def tag_names
  87 + repo.tags.collect(&:name).sort.reverse
  88 + end
  89 +
  90 + # Returns an Array of Tags
  91 + def tags
  92 + repo.tags.sort_by(&:name).reverse
  93 + end
  94 +
  95 + # Returns an Array of branch and tag names
  96 + def ref_names
  97 + [branch_names + tag_names].flatten
  98 + end
  99 +
  100 + def heads
  101 + @heads ||= repo.heads
  102 + end
  103 +
  104 + def tree(fcommit, path = nil)
  105 + fcommit = commit if fcommit == :head
  106 + tree = fcommit.tree
  107 + path ? (tree / path) : tree
  108 + end
  109 +
  110 + def has_commits?
  111 + !!commit
  112 + rescue Grit::NoSuchPathError
  113 + false
  114 + end
  115 +
  116 + def empty?
  117 + !has_commits?
  118 + end
  119 +
  120 + # Discovers the default branch based on the repository's available branches
  121 + #
  122 + # - If no branches are present, returns nil
  123 + # - If one branch is present, returns its name
  124 + # - If two or more branches are present, returns the one that has a name
  125 + # matching root_ref (default_branch or 'master' if default_branch is nil)
  126 + def discover_default_branch
  127 + if branch_names.length == 0
  128 + nil
  129 + elsif branch_names.length == 1
  130 + branch_names.first
  131 + else
  132 + branch_names.select { |v| v == root_ref }.first
  133 + end
  134 + end
  135 +
  136 + # Archive Project to .tar.gz
  137 + #
  138 + # Already packed repo archives stored at
  139 + # app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz
  140 + #
  141 + def archive_repo(ref)
  142 + ref = ref || self.root_ref
  143 + commit = self.commit(ref)
  144 + return nil unless commit
  145 +
  146 + # Build file path
  147 + file_name = self.path_with_namespace.gsub("/","_") + "-" + commit.id.to_s + ".tar.gz"
  148 + storage_path = Rails.root.join("tmp", "repositories")
  149 + file_path = File.join(storage_path, self.path_with_namespace, file_name)
  150 +
  151 + # Put files into a directory before archiving
  152 + prefix = File.basename(self.path_with_namespace) + "/"
  153 +
  154 + # Create file if not exists
  155 + unless File.exists?(file_path)
  156 + FileUtils.mkdir_p File.dirname(file_path)
  157 + file = self.repo.archive_to_file(ref, prefix, file_path)
  158 + end
  159 +
  160 + file_path
  161 + end
  162 +
  163 + # Return repo size in megabytes
  164 + # Cached in redis
  165 + def size
  166 + Rails.cache.fetch(cache_key(:size)) do
  167 + size = popen('du -s', path_to_repo).first.strip.to_i
  168 + (size.to_f / 1024).round(2)
  169 + end
  170 + end
  171 +
  172 + def expire_cache
  173 + Rails.cache.delete(cache_key(:size))
  174 + end
  175 +
  176 + def cache_key(type)
  177 + "#{type}:#{path_with_namespace}"
  178 + end
  179 + end
  180 + end
  181 +end
... ...