Improve commit and diff views, show real commit feed on profile

- Redesign commit diff view to group changes by file, show additions/deletions, and improve layout - Show real recent commits on user profile, not hardcoded list - Refactor syntax highlighting to highlight lines for better display - Update registration/login branding to "siGit Code & Deploy" - Update dev:seed_repo to always point to project .git for local dev - Add .smb/config.toml for project metadata - Set format: false on blob/raw routes to avoid .html extension issues

Seto Elkahfi committed Apr 12, 2026 at 14:02 UTC 9e5813f06e6e3d6f2c07976731efb538bbb150dc
11 files changed +191 -73
.smb/config.toml
+11
new file mode 100644 index 0000000..99e6f56 --- /dev/null +++ b/.smb/config.toml @@ -0,0 +1,11 @@ +name = "sigitsi" +description = "sigitsi" + +[project] +id = 56 +runner = 0 +name = "sigitsi" +repository = "sigitsi" +description = "siGit Si" +created_at = "2025-06-28T22:43:02.646Z" +updated_at = "2025-06-28T22:43:02.646Z"
app/controllers/blobs_controller.rb
+23 -5
index 8817e31..dab4fc8 100644 --- a/app/controllers/blobs_controller.rb +++ b/app/controllers/blobs_controller.rb @@ -21,7 +21,7 @@ class BlobsController < ApplicationController @file_size = content.bytesize unless @is_binary - @highlighted = highlight_code(content, @filename) + @highlighted_lines = highlight_lines(content, @filename) @line_count = content.lines.count end end @@ -66,11 +66,29 @@ class BlobsController < ApplicationController content.bytes.first(8192).include?(0) end - def highlight_code(content, filename) + def highlight_lines(content, filename) lexer = Rouge::Lexer.guess(filename: filename, source: content) - formatter = Rouge::Formatters::HTML.new - formatter.format(lexer.lex(content)).html_safe + html_formatter = Rouge::Formatters::HTML.new + + # Tokenize the entire file at once (preserves stateful lexer context across + # lines — e.g. multiline strings, heredocs) then split the token stream on + # newline boundaries to get one HTML fragment per source line. + lines = [[]] + lexer.lex(content).each do |token, value| + value.split(/(\n)/, -1).each do |part| + if part == "\n" + lines << [] + else + lines.last << [token, part] unless part.empty? + end + end + end + + # Drop a trailing empty entry when the file ends with a newline. + lines.pop if lines.last&.empty? + + lines.map { |line_tokens| html_formatter.format(line_tokens).html_safe } rescue StandardError - Rouge::Formatters::HTML.new.format(Rouge::Lexers::PlainText.new.lex(content)).html_safe + content.lines.map { |l| ERB::Util.html_escape(l.chomp).html_safe } end end
app/controllers/users_controller.rb
+10
index d3d1204..8054724 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -8,6 +8,16 @@ class UsersController < ApplicationController else @profile_user.repositories.where(is_private: false).order(updated_at: :desc) end + + feed_repo = @repositories.find { |r| r.name == "sigit-si" } || @repositories.first + if feed_repo&.initialized? + @feed_repo = feed_repo + @feed_commits = GitRepositoryService.commits( + feed_repo.disk_path, + feed_repo.default_branch, + limit: 3 + ) + end rescue ActiveRecord::RecordNotFound render file: Rails.public_path.join("404.html"), status: :not_found, layout: false end
app/services/git_repository_service.rb
+19 -13
index b8dbb1e..41e046b 100644 --- a/app/services/git_repository_service.rb +++ b/app/services/git_repository_service.rb @@ -82,26 +82,32 @@ class GitRepositoryService end def self.commit(path, sha) - format = "%H%x00%h%x00%s%x00%B%x00%an%x00%ae%x00%ad%x00%T" + # %B contains embedded newlines, so we cannot rely on splitting by "\n" to + # find the end of the format output. Instead we append a sentinel that git + # will never emit naturally and split on that. + sentinel = "SIGIT-EOH" + format = "%H%x00%h%x00%s%x00%B%x00%an%x00%ae%x00%ad%x00%T%x00#{sentinel}" out, _err, status = Open3.capture3( "git", "--git-dir", path, "show", - "--format=#{format}", "--stat", "--no-patch", + "--no-patch", "--format=#{format}", sha ) return nil unless status.success? - lines = out.split("\n") - parts = lines.first.to_s.split("\x00") - return nil unless parts.length == 8 + + header_raw = out.split(sentinel, 2).first.to_s + parts = header_raw.split("\x00", 9) # 8 fields + possible trailing newline + return nil unless parts.length >= 8 + diff_out, _e, _s = Open3.capture3("git", "--git-dir", path, "show", "--format=", sha) { - sha: parts[0], - short_sha: parts[1], - subject: parts[2], - body: parts[3], - author_name: parts[4], - author_email: parts[5], - authored_date: (Time.parse(parts[6]) rescue nil), - diff: diff_out + sha: parts[0].strip, + short_sha: parts[1].strip, + subject: parts[2].strip, + body: parts[3], + author_name: parts[4].strip, + author_email: parts[5].strip, + authored_date: (Time.parse(parts[6].strip) rescue nil), + diff: diff_out } end
app/views/blobs/show.html.erb
+5 -5
index da0a3d1..55921e8 100644 --- a/app/views/blobs/show.html.erb +++ b/app/views/blobs/show.html.erb @@ -44,15 +44,15 @@ Binary file — <%= number_to_human_size(@file_size) %> </div> <% else %> - <div class="code-container rounded-none border-0 relative"> + <div class="code-container rounded-none border-0 relative overflow-x-auto"> <table class="w-full"> - <tbody> - <% @raw_content.lines.each_with_index do |line, i| %> + <tbody class="highlight"> + <% @highlighted_lines.each_with_index do |highlighted_line, i| %> <tr class="hover:bg-brand-500/10"> - <td class="text-right text-gray-500 pr-4 pl-4 py-0 select-none w-10 text-xs border-r border-surface-600 align-top" id="L<%= i+1 %>"> + <td class="text-right text-gray-500 pr-4 pl-4 py-0 select-none w-10 text-xs border-r border-surface-600 align-top font-mono" id="L<%= i+1 %>"> <a href="#L<%= i+1 %>" class="hover:text-brand-500"><%= i+1 %></a> </td> - <td class="pl-4 pr-4 py-0 font-mono text-xs leading-5 whitespace-pre-wrap break-all text-gray-200"><%= line %></td> + <td class="pl-4 pr-4 py-0 font-mono text-xs leading-5 whitespace-pre text-gray-200"><%= highlighted_line %></td> </tr> <% end %> </tbody>
app/views/commits/show.html.erb
+87 -23
index a3a30dc..2e26922 100644 --- a/app/views/commits/show.html.erb +++ b/app/views/commits/show.html.erb @@ -2,43 +2,107 @@ <div class="border-b border-surface-600 bg-surface-800"> <div class="max-w-6xl mx-auto px-4 sm:px-6 pt-6 pb-4"> - <div class="flex items-center gap-2 text-sm mb-3"> + <div class="flex items-center gap-2 text-sm mb-4"> <%= link_to "@#{@owner.username}", user_profile_path(@owner.username), class: "text-brand-500 hover:underline font-medium" %> <span class="text-gray-500">/</span> <%= link_to @repository.name, repository_path(@owner.username, @repository.name), class: "text-brand-500 hover:underline font-semibold" %> + <span class="text-gray-500">/</span> + <%= link_to "commits", repository_commits_path(@owner.username, @repository.name, @repository.default_branch), class: "text-gray-400 hover:text-gray-200 transition-colors" %> + <span class="text-gray-500">/</span> + <span class="font-mono text-gray-400"><%= @commit[:short_sha] %></span> </div> + <h1 class="text-xl font-semibold text-gray-100 mb-1"><%= @commit[:subject] %></h1> - <% if @commit[:body].present? && @commit[:body] != @commit[:subject] %> - <p class="text-sm text-gray-400 whitespace-pre-line mb-3"><%= @commit[:body].delete_prefix(@commit[:subject]).strip %></p> + + <% body = @commit[:body].to_s.delete_prefix(@commit[:subject]).strip %> + <% if body.present? %> + <p class="text-sm text-gray-400 whitespace-pre-line mt-2 mb-3"><%= body %></p> <% end %> - <div class="flex items-center gap-4 text-xs text-gray-400"> + + <div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-gray-400 mt-3"> <span><strong class="text-gray-300"><%= @commit[:author_name] %></strong> committed</span> <% if @commit[:authored_date] %> <span><%= @commit[:authored_date].strftime("%b %-d, %Y at %H:%M UTC") %></span> <% end %> - <span class="font-mono badge-gray"><%= @commit[:sha] %></span> + <span class="font-mono badge-gray select-all"><%= @commit[:sha] %></span> </div> </div> </div> -<div class="max-w-6xl mx-auto px-4 sm:px-6 py-6"> - <% if @commit[:diff].present? %> - <div class="card"> - <div class="px-4 py-2.5 border-b border-surface-600 bg-surface-600 text-xs font-medium text-gray-400">Diff</div> - <div class="code-container rounded-none border-0 font-mono text-xs leading-5 overflow-x-auto"> - <% @commit[:diff].lines.each do |line| %> - <% css = if line.start_with?("+") && !line.start_with?("+++") - "diff-add block px-4" - elsif line.start_with?("-") && !line.start_with?("---") - "diff-remove block px-4" - elsif line.start_with?("@@") - "diff-header block px-4" - else - "block px-4 text-gray-400" - end %> - <span class="<%= css %>"><%= line %></span> - <% end %> - </div> +<div class="max-w-6xl mx-auto px-4 sm:px-6 py-6 space-y-3"> + <% + file_diffs = [] + current = nil + + @commit[:diff].to_s.each_line do |line| + if line.start_with?("diff --git ") + file_diffs << current if current + current = { header: line, lines: [] } + elsif current + current[:lines] << line + end + end + file_diffs << current if current + %> + + <% if file_diffs.empty? %> + <p class="text-sm text-gray-400">No diff available for this commit.</p> + <% else %> + <% + total_additions = 0 + total_deletions = 0 + file_diffs.each do |fd| + total_additions += fd[:lines].count { |l| l.start_with?("+") && !l.start_with?("+++") } + total_deletions += fd[:lines].count { |l| l.start_with?("-") && !l.start_with?("---") } + end + %> + + <div class="text-xs text-gray-400 mb-4"> + <span class="text-gray-300 font-medium"><%= file_diffs.size %> <%= "file".pluralize(file_diffs.size) %> changed</span> + <% if total_additions > 0 %> + <span class="text-green-400 ml-2">+<%= total_additions %></span> + <% end %> + <% if total_deletions > 0 %> + <span class="text-red-400 ml-1">-<%= total_deletions %></span> + <% end %> </div> + + <% file_diffs.each do |fd| %> + <% + m = fd[:header].match(/diff --git a\/(.*) b\/(.*)/) + filename = m ? m[2].chomp : fd[:header].chomp + additions = fd[:lines].count { |l| l.start_with?("+") && !l.start_with?("+++") } + deletions = fd[:lines].count { |l| l.start_with?("-") && !l.start_with?("---") } + %> + + <div class="card overflow-hidden"> + <div class="flex items-center justify-between px-4 py-2.5 border-b border-surface-600 bg-surface-700"> + <span class="font-mono text-xs text-gray-200 truncate"><%= filename %></span> + <div class="flex items-center gap-2 shrink-0 ml-4 font-mono text-xs"> + <% if additions > 0 %> + <span class="text-green-400">+<%= additions %></span> + <% end %> + <% if deletions > 0 %> + <span class="text-red-400">-<%= deletions %></span> + <% end %> + </div> + </div> + + <div class="font-mono text-xs leading-5 overflow-x-auto"> + <% fd[:lines].each do |line| %> + <% css = if line.start_with?("+") && !line.start_with?("+++") + "diff-add block px-4" + elsif line.start_with?("-") && !line.start_with?("---") + "diff-remove block px-4" + elsif line.start_with?("@@") + "diff-header block px-4" + else + "block px-4 text-gray-500" + end %> + <span class="<%= css %>"><%= line.chomp %></span> + <% end %> + </div> + </div> + <% end %> <% end %> </div>
app/views/registrations/new.html.erb
+1 -1
index 71df018..041ea0c 100644 --- a/app/views/registrations/new.html.erb +++ b/app/views/registrations/new.html.erb @@ -5,7 +5,7 @@ <div class="mb-10 flex flex-col items-center gap-3"> <%= link_to root_path, class: "flex items-center gap-3 text-gray-100 hover:text-brand-500 transition-colors" do %> <img src="/icon.png" alt="siGit" class="h-9 w-auto"> - <span class="text-xl font-semibold tracking-tight">siGit</span> + <span class="text-xl font-semibold tracking-tight">siGit Code &amp; Deploy</span> <% end %> <p class="text-sm text-gray-500">Create your account</p> </div>
app/views/sessions/new.html.erb
+1 -1
index 3c2a4f8..976b244 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -5,7 +5,7 @@ <div class="mb-10 flex flex-col items-center gap-3"> <%= link_to root_path, class: "flex items-center gap-3 text-gray-100 hover:text-brand-500 transition-colors" do %> <img src="/icon.png" alt="siGit" class="h-9 w-auto"> - <span class="text-xl font-semibold tracking-tight">siGit</span> + <span class="text-xl font-semibold tracking-tight">siGit Code &amp; Deploy</span> <% end %> <p class="text-sm text-gray-500">Sign in to continue</p> </div>
app/views/users/show.html.erb
+15 -17
index c0069e7..1527d8a 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -65,26 +65,24 @@ <p class="text-xs text-gray-500 mb-2">2 hours ago</p> <p class="text-sm mb-3"> <span class="text-gray-400">Pushed </span> - <span class="font-semibold text-gray-200">3 commits</span> + <span class="font-semibold text-gray-200"><%= @feed_commits&.size || 0 %> commits</span> <span class="text-gray-400"> to </span> - <%= link_to "@#{@profile_user.username}/sigit-si", repository_path(@profile_user.username, "sigit-si"), class: "text-brand-500 hover:underline font-medium" %> + <% if @feed_repo %> + <%= link_to "@#{@profile_user.username}/#{@feed_repo.name}", repository_path(@profile_user.username, @feed_repo.name), class: "text-brand-500 hover:underline font-medium" %> + <% end %> <span class="text-gray-400"> on </span> - <code class="bg-surface-600 px-1.5 py-0.5 rounded text-xs text-gray-200">main</code> + <code class="bg-surface-600 px-1.5 py-0.5 rounded text-xs text-gray-200"><%= @feed_repo&.default_branch || "main" %></code> </p> - <ul class="space-y-1.5 border-t border-surface-600 pt-3"> - <li class="flex items-start gap-2 text-xs text-gray-400"> - <%= link_to "a3f9d12", repository_commit_path(@profile_user.username, "sigit-si", "a3f9d12"), class: "text-brand-400 shrink-0 font-mono hover:underline" %> - <span>Fix avatar fallback when gravatar URL is empty</span> - </li> - <li class="flex items-start gap-2 text-xs text-gray-400"> - <%= link_to "b1e2c34", repository_commit_path(@profile_user.username, "sigit-si", "b1e2c34"), class: "text-brand-400 shrink-0 font-mono hover:underline" %> - <span>Add dev panel to footer, remove Rails 8 credit</span> - </li> - <li class="flex items-start gap-2 text-xs text-gray-400"> - <%= link_to "8d04f71", repository_commit_path(@profile_user.username, "sigit-si", "8d04f71"), class: "text-brand-400 shrink-0 font-mono hover:underline" %> - <span>Scope private repos to authenticated owner only</span> - </li> - </ul> + <% if @feed_commits.present? %> + <ul class="space-y-1.5 border-t border-surface-600 pt-3"> + <% @feed_commits.each do |commit| %> + <li class="flex items-start gap-2 text-xs text-gray-400"> + <%= link_to commit[:short_sha], repository_commit_path(@profile_user.username, @feed_repo.name, commit[:sha]), class: "text-brand-400 shrink-0 font-mono hover:underline" %> + <span><%= commit[:subject] %></span> + </li> + <% end %> + </ul> + <% end %> </div> </li>
config/routes.rb
+2 -2
index d0d43ed..c700995 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,8 +34,8 @@ Rails.application.routes.draw do get "/:username/:repository/commits/:branch", to: "commits#index", as: :repository_commits, constraints: { branch: /[^\/]+/, repository: /[^\/.][^\/]*/ } get "/:username/:repository/commit/:sha", to: "commits#show", as: :repository_commit - get "/:username/:repository/blob/:branch/*path", to: "blobs#show", as: :repository_blob - get "/:username/:repository/raw/:branch/*path", to: "blobs#raw", as: :repository_blob_raw + get "/:username/:repository/blob/:branch/*path", to: "blobs#show", as: :repository_blob, format: false + get "/:username/:repository/raw/:branch/*path", to: "blobs#raw", as: :repository_blob_raw, format: false get "/:username/:repository/tree/:branch/*path", to: "repositories#tree", as: :repository_tree get "/:username/:repository/tree/:branch", to: "repositories#tree", as: :repository_branch get "/:username/:repository", to: "repositories#show", as: :repository
lib/tasks/dev.rake
+17 -6
index e891cf8..5bfa74f 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -19,15 +19,26 @@ namespace :dev do user = User.find_by(username: username) abort " [dev:seed_repo] No user '#{username}' found. Sign in once first." unless user - # ── Skip if already exists ──────────────────────────────────────────────── - if user.repositories.exists?(name: repo_name) - puts " [dev:seed_repo] '#{username}/#{repo_name}' already exists — skipping." + # ── Point disk_path at the project's own .git directory ────────────────── + # This gives the web UI access to every real commit SHA without needing a + # separate bare repo or a push step. + git_path = Rails.root.join(".git").to_s + + repo = user.repositories.find_or_initialize_by(name: repo_name) + if repo.persisted? && repo.disk_path == git_path + puts " [dev:seed_repo] '#{username}/#{repo_name}' already up to date — skipping." next end - local_repos = Rails.root.join(".local-repos").to_s - bare_path = File.join(local_repos, username, "#{repo_name}.git") - puts " [dev:seed_repo] Creating bare repo at #{bare_path} …" + repo.assign_attributes( + description: description, + disk_path: git_path, + default_branch: branch, + is_private: false + ) + repo.save! + puts " [dev:seed_repo] #{repo.previously_new_record? ? "Created" : "Updated"} '#{username}/#{repo_name}' → #{git_path}" + next # ── 1. Create bare repo ─────────────────────────────────────────────────── FileUtils.mkdir_p(bare_path)