Module: Mbeditor::GitService
- Included in:
- GitBlameService, GitCommitGraphService, GitDiffService, GitFileHistoryService
- Defined in:
- app/services/mbeditor/git_service.rb
Overview
Shared helpers for running git CLI commands read-only inside a repo. All public methods accept repo_path as their first argument so services stay stateless and composable.
Constant Summary collapse
- SAFE_GIT_REF =
Safe pattern for git ref names (branch, remote/branch, tag). Rejects refs containing whitespace, NUL, shell metacharacters, or git reflog syntax (e.g. “@sequences beyond the trailing “@{u”).
%r{\A[\w./-]+\z}
Class Method Summary collapse
-
.ahead_behind(repo_path, upstream) ⇒ Object
Returns [ahead_count, behind_count] relative to upstream, or [0,0].
-
.current_branch(repo_path) ⇒ Object
Current branch name, or nil if not in a git repo.
-
.parse_git_log(raw_output) ⇒ Object
Parse compact ‘git log –pretty=format:%H%x1f%s%x1f%an%x1f%aI%x1e` output.
-
.parse_git_log_with_parents(raw_output) ⇒ Object
Parse compact ‘git log –pretty=format:%H%x1f%P%x1f%s%x1f%an%x1f%aI%x1e` output.
-
.resolve_path(repo_path, relative) ⇒ Object
Resolve a file path safely within repo_path.
-
.run_git(repo_path, *args) ⇒ Object
Run an arbitrary git command inside
repo_path. -
.upstream_branch(repo_path) ⇒ Object
Upstream tracking branch for the current branch, e.g.
Class Method Details
.ahead_behind(repo_path, upstream) ⇒ Object
Returns [ahead_count, behind_count] relative to upstream, or [0,0].
43 44 45 46 47 48 49 50 51 52 |
# File 'app/services/mbeditor/git_service.rb', line 43 def ahead_behind(repo_path, upstream) return [0, 0] if upstream.blank? return [0, 0] unless upstream.match?(SAFE_GIT_REF) out, status = run_git(repo_path, "rev-list", "--left-right", "--count", "HEAD...#{upstream}") return [0, 0] unless status.success? parts = out.strip.split("\t", 2) [parts[0].to_i, parts[1].to_i] end |
.current_branch(repo_path) ⇒ Object
Current branch name, or nil if not in a git repo. Uses rev-parse for compatibility with Git < 2.22 (which lacks –show-current).
27 28 29 30 |
# File 'app/services/mbeditor/git_service.rb', line 27 def current_branch(repo_path) out, status = run_git(repo_path, "rev-parse", "--abbrev-ref", "HEAD") status.success? ? out.strip : nil end |
.parse_git_log(raw_output) ⇒ Object
Parse compact ‘git log –pretty=format:%H%x1f%s%x1f%an%x1f%aI%x1e` output.
72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'app/services/mbeditor/git_service.rb', line 72 def self.parse_git_log(raw_output) raw_output.split("\x1e").map do |entry| fields = entry.strip.split("\x1f", 4) next unless fields.length == 4 { "hash" => fields[0], "title" => fields[1], "author" => fields[2], "date" => fields[3] } end.compact end |
.parse_git_log_with_parents(raw_output) ⇒ Object
Parse compact ‘git log –pretty=format:%H%x1f%P%x1f%s%x1f%an%x1f%aI%x1e` output. Returns Array of hashes.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'app/services/mbeditor/git_service.rb', line 56 def self.parse_git_log_with_parents(raw_output) raw_output.split("\x1e").map do |entry| fields = entry.strip.split("\x1f", 5) next unless fields.length == 5 { "hash" => fields[0], "parents" => fields[1].split.reject(&:blank?), "title" => fields[2], "author" => fields[3], "date" => fields[4] } end.compact end |
.resolve_path(repo_path, relative) ⇒ Object
Resolve a file path safely within repo_path. Returns full path string or nil if the path escapes the root.
88 89 90 91 92 93 |
# File 'app/services/mbeditor/git_service.rb', line 88 def resolve_path(repo_path, relative) return nil if relative.blank? full = File.(relative.to_s, repo_path.to_s) full.start_with?(repo_path.to_s + "/") || full == repo_path.to_s ? full : nil end |
.run_git(repo_path, *args) ⇒ Object
Run an arbitrary git command inside repo_path. Returns [stdout, Process::Status]. stderr is captured and discarded to prevent git diagnostic messages from leaking into the Rails server log.
20 21 22 23 |
# File 'app/services/mbeditor/git_service.rb', line 20 def run_git(repo_path, *args) out, _err, status = Open3.capture3("git", "-C", repo_path, *args) [out, status] end |
.upstream_branch(repo_path) ⇒ Object
Upstream tracking branch for the current branch, e.g. “origin/main”. Returns nil if the branch name contains characters outside SAFE_GIT_REF.
34 35 36 37 38 39 40 |
# File 'app/services/mbeditor/git_service.rb', line 34 def upstream_branch(repo_path) out, status = run_git(repo_path, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}") return nil unless status.success? ref = out.strip ref.match?(SAFE_GIT_REF) ? ref : nil end |