Commit 676bce2ab941fcc50ef5796ff77229e238eb9b35
1 parent
71b0f8ea
Exists in
master
and in
4 other branches
Move Commit and Repository logic to lib/gitlab/git
Showing
2 changed files
with
360 additions
and
0 deletions
Show diff stats
@@ -0,0 +1,179 @@ | @@ -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 |
@@ -0,0 +1,181 @@ | @@ -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 |