Class: Mbeditor::GitController

Inherits:
ApplicationController show all
Defined in:
app/controllers/mbeditor/git_controller.rb

Overview

Thin controller for all Git-related endpoints added in the Git & Code Review system. All heavy logic lives in service objects under app/services/mbeditor/.

Endpoints

GET /mbeditor/git/diff ?file=[&base=&head=] GET /mbeditor/git/blame ?file= GET /mbeditor/git/line_diff ?file= GET /mbeditor/git/file_history ?file= GET /mbeditor/git/commit_graph GET /mbeditor/redmine/issue/:id

Instance Method Summary collapse

Instance Method Details

#blameObject

GET /mbeditor/git/blame?file=



49
50
51
52
53
54
55
56
57
# File 'app/controllers/mbeditor/git_controller.rb', line 49

def blame
  file = require_file_param
  return unless file

  lines = GitBlameService.new(repo_path: workspace_root, file_path: file).call
  render json: { lines: lines }
rescue StandardError => e
  render json: { error: e.message }, status: :unprocessable_content
end

#combined_diffObject

GET /mbeditor/git/combined_diff?scope=local|branch Returns the raw unified diff text for all files in the given scope. scope=local → git diff HEAD (working tree vs HEAD) scope=branch → git diff ..HEAD (same baseline as git_info) The body stays raw diff text for the viewer; what it was compared against (and why it couldn't be) rides along in headers, so an empty diff is no longer indistinguishable from "we had no base and gave up".



106
107
108
109
110
111
112
113
114
115
116
# File 'app/controllers/mbeditor/git_controller.rb', line 106

def combined_diff
  scope = params[:scope] == 'branch' ? :branch : :local
  service = GitCombinedDiffService.new(repo_path: workspace_root, scope: scope)
  out = service.call
  response.set_header("X-Mbeditor-Diff-Base", service.base_ref.to_s) if service.base_ref
  response.set_header("X-Mbeditor-Diff-Error", service.error.to_s) if service.error
  render plain: out, content_type: "text/plain"
rescue StandardError => e
  response.set_header("X-Mbeditor-Diff-Error", e.message.to_s)
  render plain: "", content_type: "text/plain"
end

#commit_detailObject

GET /mbeditor/git/commit_detail?sha=



89
90
91
92
93
94
95
96
97
# File 'app/controllers/mbeditor/git_controller.rb', line 89

def commit_detail
  sha = params[:sha].to_s.strip
  return render json: { error: "sha required" }, status: :bad_request if sha.blank?
  return render json: { error: "Invalid sha" }, status: :bad_request unless sha.match?(/\A[0-9a-fA-F]{1,40}\z/)

  render json: GitCommitDetailService.new(repo_path: workspace_root, sha: sha).call
rescue StandardError => e
  render json: { error: e.message }, status: :unprocessable_content
end

#commit_graphObject

GET /mbeditor/git/commit_graph



81
82
83
84
85
86
# File 'app/controllers/mbeditor/git_controller.rb', line 81

def commit_graph
  commits = GitCommitGraphService.new(repo_path: workspace_root).call
  render json: { commits: commits }
rescue StandardError => e
  render json: { error: e.message }, status: :unprocessable_content
end

#diffObject

GET /mbeditor/git/diff?file=[&base=&head=]



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'app/controllers/mbeditor/git_controller.rb', line 20

def diff
  file = require_file_param
  return unless file

  base = params[:base].presence
  head = params[:head].presence
  # 'WORKING' is a frontend sentinel meaning current on-disk working tree
  head = nil if head == 'WORKING'
  # Allow full/short SHA hashes plus common git ref formats: branch names,
  # HEAD, remote tracking refs, parent notation (sha^, sha~N) and tags.
  # @ is excluded to block reflog syntax like @{-1} or HEAD@{2}.
  valid_ref = /\A[a-zA-Z0-9._\-\/\^~]+\z/
  if [base, head].any? { |s| s && (s.length > 200 || !s.match?(valid_ref)) }
    return render json: { error: 'Invalid ref' }, status: :bad_request
  end

  result = GitDiffService.new(
    repo_path: workspace_root,
    file_path: file,
    base_sha: base,
    head_sha: head
  ).call

  render json: result
rescue StandardError => e
  render json: { error: e.message }, status: :unprocessable_content
end

#file_historyObject

GET /mbeditor/git/file_history?file=



70
71
72
73
74
75
76
77
78
# File 'app/controllers/mbeditor/git_controller.rb', line 70

def file_history
  file = require_file_param
  return unless file

  commits = GitFileHistoryService.new(repo_path: workspace_root, file_path: file).call
  render json: { commits: commits }
rescue StandardError => e
  render json: { error: e.message }, status: :unprocessable_content
end

#line_diffObject

GET /mbeditor/git/line_diff?file=



60
61
62
63
64
65
66
67
# File 'app/controllers/mbeditor/git_controller.rb', line 60

def line_diff
  file = require_file_param
  return unless file

  render json: GitLineDiffService.new(repo_path: workspace_root, file_path: file).call
rescue StandardError => e
  render json: { error: e.message }, status: :unprocessable_content
end

#redmine_issueObject

GET /mbeditor/redmine/issue/:id



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/controllers/mbeditor/git_controller.rb', line 119

def redmine_issue
  unless Mbeditor.configuration.redmine_enabled
    return render json: { error: 'Redmine integration is disabled.' }, status: :service_unavailable
  end

  return render json: { error: 'Invalid issue id' }, status: :bad_request unless params[:id].to_s.match?(/\A\d+\z/)

  result = RedmineService.new(issue_id: params[:id]).call
  render json: result
rescue RedmineDisabledError => e
  render json: { error: e.message }, status: :service_unavailable
rescue RedmineConfigError => e
  render json: { error: e.message }, status: :unprocessable_content
rescue StandardError => e
  render json: { error: e.message }, status: :unprocessable_content
end