main
rb 227 lines 9.09 KB
Raw
1 class RepositoriesController < ApplicationController
2 before_action :require_sign_in!, only: [ :new, :create ]
3 before_action :noindex!, only: [ :new, :create ]
4 before_action :load_owner, only: [ :show, :tree, :cicd ]
5 before_action :load_repository, only: [ :show, :tree, :cicd ]
6 before_action :ensure_can_read!, only: [ :show, :tree, :cicd ]
7
8 # Discovery page for public code repositories — the code-side counterpart to
9 # the model library at /models.
10 def explore
11 @query = params[:q].to_s.strip
12 @sort = params.fetch(:sort, "recent")
13
14 repos = Repository.code.visible_to(current_user).includes(:user)
15 if @query.present?
16 like = "%#{@query.downcase}%"
17 repos = repos.where("LOWER(repositories.name) LIKE :q OR LOWER(repositories.description) LIKE :q", q: like)
18 end
19
20 @repositories = case @sort
21 when "stars" then repos.order(stars_count: :desc, updated_at: :desc)
22 when "name" then repos.order(Arel.sql("LOWER(repositories.name) ASC"))
23 else repos.order(updated_at: :desc)
24 end.to_a
25 end
26
27 def new
28 @repository = Repository.new(kind: params[:kind] == "model" ? "model" : "code")
29 end
30
31 def create
32 @repository = current_user.repositories.new(repository_params)
33 @repository.disk_path = GitRepositoryService.repo_path(
34 current_user.username, @repository.name
35 )
36
37 if @repository.save
38 GitRepositoryService.create_bare_repo(current_user.username, @repository.name)
39 GitRepositoryService.initialize_with_readme(
40 current_user.username, @repository.name,
41 default_branch: @repository.default_branch,
42 readme: initial_readme(@repository)
43 )
44 redirect_to repository_path(current_user.username, @repository.name),
45 notice: "#{@repository.model? ? 'Model' : 'Repository'} created successfully."
46 else
47 render :new, status: :unprocessable_entity
48 end
49 rescue => e
50 Rails.logger.error("Repo create error: #{e.message}")
51 @repository.errors.add(:base, "Failed to initialize repository on disk.")
52 render :new, status: :unprocessable_entity
53 end
54
55 def show
56 branch = params[:branch] || @repository.default_branch
57 @branch = branch
58 @branches = GitRepositoryService.branches(@repository.disk_path) rescue []
59
60 unless @repository.initialized? && GitRepositoryService.branch_exists?(@repository.disk_path, branch)
61 @empty = true
62 render(@repository.model? ? :model : :show) and return
63 end
64
65 if @repository.model?
66 return show_model(branch)
67 end
68
69 @tree = GitRepositoryService.list_tree(@repository.disk_path, branch)
70 readme = GitRepositoryService.readme_content(@repository.disk_path, branch)
71 if readme
72 @readme_html = render_markdown(readme[:content], file_path: readme[:filename], branch: branch)
73 @readme_filename = readme[:filename]
74 end
75 @clone_https_url = clone_https_url(@owner.username, @repository.name)
76 @clone_ssh_url = clone_ssh_url(@owner.username, @repository.name)
77 @commit_count = GitRepositoryService.commit_count(@repository.disk_path, branch)
78 @recent_commits = GitRepositoryService.commits(@repository.disk_path, branch, limit: 3)
79 end
80
81 def tree
82 branch = params[:branch] || @repository.default_branch
83 @branch = branch
84 @branches = GitRepositoryService.branches(@repository.disk_path) rescue []
85 tree_path = params[:path]
86
87 unless @repository.initialized? && GitRepositoryService.branch_exists?(@repository.disk_path, branch)
88 redirect_to repository_path(@owner.username, @repository.name)
89 return
90 end
91
92 @tree = GitRepositoryService.list_tree(@repository.disk_path, branch, tree_path)
93 @current_path = tree_path
94 @path_parts = tree_path.to_s.split("/").reject(&:blank?)
95 @commit_count = GitRepositoryService.commit_count(@repository.disk_path, branch)
96
97 # If it's actually a file, redirect to the blob view
98 if @tree.empty?
99 redirect_to repository_blob_path(@owner.username, @repository.name, branch, tree_path)
100 end
101 end
102
103 def cicd
104 @branch = @repository.default_branch
105 @commit_count = if @repository.initialized? && GitRepositoryService.branch_exists?(@repository.disk_path, @branch)
106 GitRepositoryService.commit_count(@repository.disk_path, @branch)
107 else
108 0
109 end
110 @pipeline_summary = {
111 success_rate: "98.4%",
112 avg_duration: "6m 12s",
113 weekly_runs: 27,
114 last_deploy: "18 minutes ago"
115 }
116 @pipeline_runs = [
117 { name: "Deploy production", ref: "main", sha: "8f3c1b2", status: "passed", duration: "5m 48s", started_at: "18 minutes ago", actor: "sigit" },
118 { name: "PR validation", ref: "feature/repo-tabs", sha: "7bd91aa", status: "running", duration: "2m 14s", started_at: "now", actor: "naufal" },
119 { name: "Nightly security scan", ref: "main", sha: "4c72e16", status: "passed", duration: "9m 03s", started_at: "7 hours ago", actor: "automation" },
120 { name: "Deploy staging", ref: "release/apr", sha: "cb194ef", status: "failed", duration: "4m 51s", started_at: "yesterday", actor: "sigit" }
121 ]
122 @environments = [
123 { name: "Production", url: "app.sigitsi.com", status: "healthy", version: "v1.24.0", updated_at: "18 minutes ago" },
124 { name: "Staging", url: "staging.sigitsi.com", status: "degraded", version: "v1.25.0-rc.2", updated_at: "42 minutes ago" },
125 { name: "Preview", url: "preview-482.sigitsi.com", status: "building", version: "feature/repo-tabs", updated_at: "2 minutes ago" }
126 ]
127 @checks = [
128 { name: "RSpec", status: "passed", detail: "248 examples in 32s" },
129 { name: "Brakeman", status: "passed", detail: "0 warnings" },
130 { name: "Lint", status: "running", detail: "RuboCop on 84 files" },
131 { name: "Deploy smoke test", status: "failed", detail: "Health endpoint timed out once" }
132 ]
133 end
134
135 private
136
137 # Renders the Hugging Face-style model page: parsed model card + a
138 # "Files and versions" listing with LFS-resolved sizes, all on one page with
139 # client-side tab switching.
140 def show_model(branch)
141 @card = @repository.model_card(branch: branch)
142 @card_html = render_markdown(@card.body, file_path: "README.md", branch: branch) if @card.body.present?
143 @files = GitRepositoryService.model_files(@repository.disk_path, branch)
144 @total_size = @files.filter_map { |f| f[:size] }.sum
145 @commit_count = GitRepositoryService.commit_count(@repository.disk_path, branch)
146 @recent_commits = GitRepositoryService.commits(@repository.disk_path, branch, limit: 1)
147 render :model
148 end
149
150 def repository_params
151 permitted = params.require(:repository).permit(:name, :description, :default_branch, :is_private, :kind)
152 permitted[:kind] = "model" if permitted[:kind] == "model"
153 permitted[:kind] = "code" unless permitted[:kind] == "model"
154 permitted
155 end
156
157 # Seeds a fresh repo's README — a starter model card with YAML front-matter
158 # for models, a plain heading for code repos.
159 def initial_readme(repository)
160 return nil unless repository.model?
161
162 <<~MARKDOWN
163 ---
164 license: apache-2.0
165 pipeline_tag: text-generation
166 library_name: transformers
167 tags:
168 - #{repository.name}
169 ---
170
171 # #{repository.name}
172
173 Describe your model here. Document its architecture, training data,
174 intended use, limitations, and how to load the weights.
175
176 Push your weight files (`*.safetensors`, `*.gguf`, `*.bin`) with Git LFS:
177
178 ```bash
179 git lfs install
180 git lfs track "*.safetensors" "*.gguf"
181 git add .gitattributes
182 git push origin #{repository.default_branch}
183 ```
184 MARKDOWN
185 end
186
187 def load_owner
188 @owner = User.find_by!(username: params[:username])
189 rescue ActiveRecord::RecordNotFound
190 render file: Rails.public_path.join("404.html"), status: :not_found, layout: false
191 end
192
193 def load_repository
194 @repository = @owner.repositories.find_by!(name: params[:repository])
195 rescue ActiveRecord::RecordNotFound
196 render file: Rails.public_path.join("404.html"), status: :not_found, layout: false
197 end
198
199 def ensure_can_read!
200 unless !@repository.is_private || (signed_in? && current_user == @owner)
201 render file: Rails.public_path.join("404.html"), status: :not_found, layout: false
202 end
203 end
204
205 # Render Markdown to sanitized HTML, resolving relative links/images against
206 # this repo at the current ref. Cached by the file's blob SHA + ref so the
207 # heavy render only runs when the content (or relative-link base) changes.
208 def render_markdown(text, file_path:, branch:)
209 context = MarkdownRenderer::Context.new(
210 username: @owner.username,
211 repository: @repository.name,
212 ref: branch,
213 dir: File.dirname(file_path.to_s).then { |d| d == "." ? "" : d }
214 )
215 sha = GitRepositoryService.blob_sha(@repository.disk_path, branch, file_path)
216 cache_key = [ "md", sha || Digest::SHA1.hexdigest(text.to_s), context.username, context.repository, context.ref, context.dir ]
217 Rails.cache.fetch(cache_key) { MarkdownRenderer.render(text, context: context) }.html_safe
218 end
219
220 def clone_https_url(username, reponame)
221 "#{request.base_url}/#{username}/#{reponame}.git"
222 end
223
224 def clone_ssh_url(username, reponame)
225 "git@#{request.host}:#{username}/#{reponame}.git"
226 end
227 end