| 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 |