8
File.join(REPOS_BASE, username, "#{reponame}.git")
9
end
10
11
+ # Git ref/SHA values reach `git` as a single argv entry (Open3.capture3 never
12
+ # invokes a shell), so they can't carry shell metacharacters. But a value
13
+ # starting with "-" is still parsed by git itself as an option rather than a
14
+ # revision (e.g. a branch of "--output=/some/path" could make `git log` write
15
+ # to an arbitrary file) — real ref/SHA names can never start with "-" (see
16
+ # `git check-ref-format`), so reject anything that does before it reaches git.
17
+ def self.safe_rev?(rev)
18
+ rev.present? && !rev.start_with?("-")
19
+ end
20
+ private_class_method :safe_rev?
21
+
22
def self.create_bare_repo(username, reponame)
23
path = repo_path(username, reponame)
24
FileUtils.mkdir_p(path)
49
end
50
51
def self.list_tree(path, branch, tree_path = "")
52
+ return [] unless safe_rev?(branch)
53
prefix = tree_path.blank? ? "" : "#{tree_path.chomp("/")}/"
54
args = [ "git", "--git-dir", path, "ls-tree", "--long", "#{branch}:#{prefix}" ]
55
out, _err, status = Open3.capture3(*args)
94
end
95
96
def self.file_content(path, branch, file_path)
97
+ return nil unless safe_rev?(branch)
98
out, _err, status = Open3.capture3("git", "--git-dir", path, "show", "#{branch}:#{file_path}")
99
return nil unless status.success?
100
out
103
# Object id of the blob at <ref>:<file_path>. Stable for identical content, so
104
# it's the natural cache key for rendered/highlighted output. nil if missing.
105
def self.blob_sha(path, branch, file_path)
106
+ return nil unless safe_rev?(branch)
107
out, _err, status = Open3.capture3("git", "--git-dir", path, "rev-parse", "--verify", "--quiet", "#{branch}:#{file_path}")
108
status.success? ? out.strip.presence : nil
109
end
111
# Byte size of the blob without loading it into memory — lets the blob view
112
# decide up front whether a file is too large to highlight or render.
113
def self.blob_size(path, branch, file_path)
114
+ return nil unless safe_rev?(branch)
115
out, _err, status = Open3.capture3("git", "--git-dir", path, "cat-file", "-s", "#{branch}:#{file_path}")
116
status.success? ? out.strip.to_i : nil
117
end
119
# Full commit SHA a ref currently points at. Permalinks pin to this so they
120
# survive the branch moving on.
121
def self.commit_sha(path, ref)
122
+ return nil unless safe_rev?(ref)
123
out, _err, status = Open3.capture3("git", "--git-dir", path, "rev-parse", "--verify", "--quiet", "#{ref}^{commit}")
124
status.success? ? out.strip.presence : nil
125
end
126
127
def self.commits(path, branch, limit: 20, offset: 0)
128
+ return [] unless safe_rev?(branch)
129
format = "%H%x00%h%x00%s%x00%an%x00%ae%x00%ad%x00%cn"
130
out, _err, status = Open3.capture3(
131
"git", "--git-dir", path, "log",
154
# %B contains embedded newlines, so we cannot rely on splitting by "\n" to
155
# find the end of the format output. Instead we append a sentinel that git
156
# will never emit naturally and split on that.
157
+ return nil unless safe_rev?(sha)
158
+
159
sentinel = "SIGIT-EOH"
160
format = "%H%x00%h%x00%s%x00%B%x00%an%x00%ae%x00%ad%x00%T%x00#{sentinel}"
161
out, _err, status = Open3.capture3(
196
# files are skipped. Returns [] when nothing matches or the branch is unknown.
197
def self.search_code(path, branch, query, limit: 20)
198
return [] if query.to_s.empty?
199
+ return [] unless safe_rev?(branch)
200
201
out, _err, status = Open3.capture3(
202
"git", "--git-dir", path, "grep",
219
end
220
221
def self.branch_exists?(path, branch)
222
+ return false unless safe_rev?(branch)
223
_out, _err, status = Open3.capture3("git", "--git-dir", path, "rev-parse", "--verify", branch)
224
status.success?
225
end
231
end
232
233
def self.commit_count(path, branch)
234
+ return 0 unless safe_rev?(branch)
235
out, _err, status = Open3.capture3("git", "--git-dir", path, "rev-list", "--count", branch)
236
return 0 unless status.success?
237
out.strip.to_i
241
# content version when caching derived artifacts (e.g. Open Graph cards) so
242
# they regenerate exactly when the repository content changes.
243
def self.head_sha(path, branch)
244
+ return nil unless safe_rev?(branch)
245
out, _err, status = Open3.capture3("git", "--git-dir", path, "rev-parse", "--verify", "#{branch}^{commit}")
246
status.success? ? out.strip.presence : nil
247
end
249
# Flat list of every tracked file path on +branch+. One `ls-tree -r` call;
250
# used for cheap primary-language detection.
251
def self.tree_filenames(path, branch)
252
+ return [] unless safe_rev?(branch)
253
out, _err, status = Open3.capture3(
254
"git", "--git-dir", path, "ls-tree", "-r", "--name-only", branch
255
)