From c1b1c31686d017b71bb5caeea22476ff5d896ffd Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 16 May 2026 16:32:51 +0300 Subject: [PATCH] commit: redesign commit and commits log pages Switch commit rendering to parse_unified_diff so the commit view shares the PR-style hunk table (file headers, add/del counts, new/deleted/ renamed badges, binary-file fallback) instead of dumping a single highlighted blob per file. Drop the now-unused Change struct and get_changes helper. In the commits log, group by full date, render avatars/usernames from get_user_by_id, and add per-commit hash + "browse repo at this point" links. Add the matching translation keys in en.tr / ru.tr. --- commit/commit.v | 62 +-------- commit/commit_routes.v | 33 +++-- static/css/commits.scss | 286 ++++++++++++++++++++++++++++++++-------- templates/commit.html | 82 +++++++++--- templates/commits.html | 55 +++++--- translations/en.tr | 36 +++++ translations/ru.tr | 36 +++++ 7 files changed, 422 insertions(+), 168 deletions(-) diff --git a/commit/commit.v b/commit/commit.v index 6ad86f6..dfadcd9 100644 --- a/commit/commit.v +++ b/commit/commit.v @@ -16,69 +16,15 @@ mut: message string } -struct Change { -mut: - file string - additions int - deletions int - diff string - message string -} - fn (commit Commit) relative() string { return time.unix(commit.created_at).relative() } -fn (commit Commit) get_changes(repo Repo) []Change { - git_changes := repo.git('show ${commit.hash}') - - mut change := Change{} - mut changes := []Change{} - mut started := false - for line in git_changes.split_into_lines() { - args := line.split(' ') - if args.len <= 0 { - continue - } - - match args[0] { - 'diff' { - started = true - if change.file.len > 0 { - changes << change - change = Change{} - } - change.file = args[2][2..] - } - 'index' { - continue - } - '---' { - continue - } - '+++' { - continue - } - '@@' { - change.diff = line - } - else { - if started { - if line.bytes()[0] == `+` { - change.additions++ - } - if line.bytes()[0] == `-` { - change.deletions++ - } - change.message += '${line}\n' - } - } - } +fn (commit Commit) short_hash() string { + if commit.hash.len <= 7 { + return commit.hash } - - changes << change - - return changes + return commit.hash[..7] } fn (mut app App) commit_exists(repo_id int, branch_id int, hash string) bool { diff --git a/commit/commit_routes.v b/commit/commit_routes.v index 2b45eb3..a969f38 100644 --- a/commit/commit_routes.v +++ b/commit/commit_routes.v @@ -1,7 +1,6 @@ module main import veb -import highlight import time import api @@ -46,16 +45,13 @@ pub fn (mut app App) commits(mut ctx Context, username string, repo_name string, mut commits := app.find_repo_commits_as_page(repo.id, branch.id, offset) - msg := if b_author { 'by' } else { 'on' } - mut d_commits := map[string][]Commit{} + mut author_avatars := map[int]string{} + mut author_usernames := map[int]string{} for commit in commits { date := time.unix(commit.created_at) - day := date.day - month := date.month - year := date.year author := commit.author_id.str() - date_s := '${day}.${month}.${year}' + date_s := date.custom_format('MMMM D, YYYY') if b_author { if author !in d_commits { @@ -68,6 +64,13 @@ pub fn (mut app App) commits(mut ctx Context, username string, repo_name string, } d_commits[date_s] << commit } + + if commit.author_id != 0 && commit.author_id !in author_avatars { + if user := app.get_user_by_id(commit.author_id) { + author_avatars[commit.author_id] = app.prepare_user_avatar_url(user.avatar) + author_usernames[commit.author_id] = user.username + } + } } return $veb.html() @@ -88,20 +91,14 @@ pub fn (mut app App) commit(mut ctx Context, username string, repo_name string, patch_url := '/${username}/${repo_name}/commit/${hash}.patch' commit := app.find_repo_commit_by_hash(repo.id, hash) - changes := commit.get_changes(repo) + raw_diff := repo.git('show --no-color --pretty=format: ${commit.hash}') + file_diffs := parse_unified_diff(raw_diff) mut all_adds := 0 mut all_dels := 0 - mut sources := map[string]veb.RawHtml{} - mut change := Change{} - mut highlighted_src := '' - mut i := 0 - for i = 0; i < changes.len; i++ { - change = changes[i] - all_adds += change.additions - all_dels += change.deletions - highlighted_src, _, _ = highlight.highlight_text(change.message, change.file, true) - sources[change.file] = veb.RawHtml(highlighted_src) + for fd in file_diffs { + all_adds += fd.additions + all_dels += fd.deletions } return $veb.html() diff --git a/static/css/commits.scss b/static/css/commits.scss index 2d46a6c..a22c451 100644 --- a/static/css/commits.scss +++ b/static/css/commits.scss @@ -1,31 +1,76 @@ .commit-day-title { - color: #767676; - padding: 30px 0 10px 0; + color: $gray-dark; + font-size: 13px; + padding: 24px 0 10px 0; } -.clog { +.clog-block { border: 1px solid $gray; - padding: 10px; - border-top: 0; + border-radius: $medium-radius; + overflow: hidden; + background-color: $white; } -.clog:first-child { +.clog { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; border-top: 1px solid $gray; - border-top-left-radius: $medium-radius !important; - border-top-right-radius: $medium-radius !important; } -.clog:last-child { - border-bottom-left-radius: $medium-radius !important; - border-bottom-right-radius: $medium-radius !important; +.clog:first-child { + border-top: 0; +} + +.clog-avatar-link { + flex: 0 0 auto; + display: block; + line-height: 0; } img.clog-avatar { border-radius: $small-radius; - width: 35px; - //display: inline-block; - float: left; - margin-right: 10px; + width: 20px; + height: 20px; + object-fit: cover; + display: block; +} + +.clog-actions { + flex: 0 0 auto; + display: flex; + align-items: center; + gap: 6px; +} + +.clog-browse { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: 1px solid $gray; + border-radius: $small-radius; + background-color: $white; + color: $gray-dark; + text-decoration: none; +} + +.clog-browse:hover { + color: $link-color; + border-color: $link-color; +} + +.clog-browse svg { + width: 14px; + height: 14px; + display: block; +} + +.clog-body { + flex: 1 1 auto; + min-width: 0; } .clog-msg { @@ -36,66 +81,193 @@ img.clog-avatar { a { color: $black !important; font-size: 15px; - font-weight: 700; + font-weight: 600; } - padding-bottom: 5px; +} + +.clog-meta { + font-size: 12px; + color: $gray-dark; + margin-top: 4px; +} + +.clog-author { + color: $black; + font-weight: 600; + text-decoration: none; + margin-right: 6px; +} + +a.clog-author:hover { + color: $link-color; } .clog-time { - font-size: 11px; + font-size: 12px; color: $gray-dark; } -.commit-info-block { - border: 1px solid #dfdfdf; - border-radius: 3px; - padding: 10px; +.clog-hash { + flex: 0 0 auto; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; + background-color: $gray-light; + border: 1px solid $gray; + border-radius: $small-radius; + padding: 2px 8px; + font-size: 12px; + color: $black; + text-decoration: none; } -.commit-file-diff { - margin-top: 10px; +.clog-hash:hover { + color: $link-color; + border-color: $link-color; +} - pre { - overflow-y: hidden; - overflow-x: scroll; - } +.commit-page { + margin-top: 16px; +} - pre > *{ - font-family:Roboto-Mono, Menlo; - } +.commit-page__header { + border: 1px solid $gray; + border-radius: $medium-radius; + padding: 14px 16px; + background-color: #f6f8fa; + margin-bottom: 16px; +} - .hl_table a { - padding-right: 5px; - padding-left: 5px; - float: right; - } +.commit-page__title { + font-size: 18px; + font-weight: 600; + margin: 0 0 8px 0; + color: $black; + line-height: 1.4; +} - .hl_table { - width: 1000px; - border: 1px solid #dfdfdf; - border-radius: 3px; - } +.commit-page__meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + font-size: 13px; + color: $gray-dark; + margin-bottom: 10px; +} - .hl_table b { - color: rgb(150, 77, 0); - } +.commit-page__author { + font-weight: 600; + color: $black; +} - .hl_table u { - color: rgb(0, 124, 255); - text-decoration: none; - } +.commit-page__sep { + color: $gray; +} - .hl_table i { - color: rgb(3, 125, 3); - } +.commit-page__hash { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; + background-color: $white; + border: 1px solid $gray; + border-radius: $small-radius; + padding: 1px 6px; + font-size: 12px; + color: $black; +} - .a { - background-color: #90fb90; - } +.commit-page__browse { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border: 1px solid $gray; + border-radius: $small-radius; + background-color: $white; + color: $gray-dark; + text-decoration: none; +} - .d { - background-color: #f27e7e; - } +.commit-page__browse:hover { + color: $link-color; + border-color: $link-color; +} + +.commit-page__browse svg { + width: 14px; + height: 14px; + display: block; +} + +.commit-page__patch { + margin-left: auto; + font-size: 13px; + color: $link-color; + text-decoration: none; +} + +.commit-page__patch:hover { + text-decoration: underline; +} + +.commit-page__stats { + display: flex; + gap: 12px; + font-size: 13px; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; +} + +.commit-page__files { + color: $black; +} + +.commit-page__adds { + color: #1f883d; + font-weight: 600; +} + +.commit-page__dels { + color: #cf222e; + font-weight: 600; +} + +.commit-page__empty { + padding: 24px; + text-align: center; + color: $gray-dark; + border: 1px solid $gray; + border-radius: $medium-radius; + background-color: $white; +} + +.pr-diff__tag { + display: inline-block; + font-size: 11px; + font-family: system-ui, sans-serif; + font-weight: 500; + padding: 1px 6px; + margin-left: 6px; + border-radius: 10px; + background-color: $gray-light; + color: $gray-dark; + border: 1px solid $gray; + vertical-align: middle; +} + +.pr-diff__tag--new { + background-color: #dafbe1; + color: #1f883d; + border-color: #aceebb; +} + +.pr-diff__tag--deleted { + background-color: #ffebe9; + color: #cf222e; + border-color: #ffcecb; +} + +.pr-diff__oldpath { + color: $gray-dark; + font-weight: 400; + margin-right: 4px; } .buttons { diff --git a/templates/commit.html b/templates/commit.html index 575bb63..075ece5 100644 --- a/templates/commit.html +++ b/templates/commit.html @@ -9,25 +9,73 @@
@include 'layout/repo_menu.html' -
- span.additions { - Additions: @all_adds - } - - span.deletions { - Deletions: @all_dels - } +
+
+

@commit.message

+
+ @commit.author + %commit_committed @commit.relative() + · + @commit.hash + + + + %commit_view_patch +
+
+ @file_diffs.len %commit_files_changed + +@all_adds + -@all_dels +
+
- - View patch - + @if file_diffs.len == 0 +
%commit_no_changes
+ @else + @for fd in file_diffs +
+
+ + @if fd.is_renamed && fd.old_path != fd.path + %commit_file_renamed + @fd.old_path → + @end + @fd.path + @if fd.is_new + %commit_file_new + @end + @if fd.is_deleted + %commit_file_deleted + @end + + + +@fd.additions + -@fd.deletions + +
+ @if fd.is_binary +
%commit_binary_file
+ @else + + @for hunk in fd.hunks + + + + @for dline in hunk.lines + + + + + + + @end + @end +
@hunk.header
@{dline.old_line_str()}@{dline.new_line_str()}@{dline.sign()}
@{render_diff_line(dline.content, fd.path)}
+ @end +
+ @end + @end
- - @for _, src in sources -
-
@src
-
- @end
@include 'layout/footer.html' diff --git a/templates/commits.html b/templates/commits.html index 4e900a5..bcffb0b 100644 --- a/templates/commits.html +++ b/templates/commits.html @@ -10,26 +10,45 @@ @include 'layout/repo_menu.html' @for day, commits2 in d_commits - .commit-day { - .commit-day-title { - Commits @msg @day - } - .clog-block { +
+
+ %commits_on @day +
+
@for commit in commits2 - .clog { - .clog-msg { - @commit.message - } - - @commit.author - - span.clog-time { - commited @commit.relative() - } - } +
+ @if commit.author_id in author_usernames + + @commit.author + + @else + + @commit.author + + @end +
+ +
+ @if commit.author_id in author_usernames + @commit.author + @else + @commit.author + @end + %commit_committed @commit.relative() +
+
+ +
@end - } - } +
+
@end @if d_commits.len > 0 diff --git a/translations/en.tr b/translations/en.tr index 4860ce0..3e03b6c 100644 --- a/translations/en.tr +++ b/translations/en.tr @@ -854,3 +854,39 @@ New issues admin_stats_last_n_days Last 30 days ----- +commit_committed +committed +----- +commit_view_patch +View patch +----- +commit_files_changed +files changed +----- +commit_additions +additions +----- +commit_deletions +deletions +----- +commit_no_changes +No file changes in this commit. +----- +commit_binary_file +Binary file not shown. +----- +commit_file_new +new file +----- +commit_file_deleted +deleted +----- +commit_file_renamed +renamed +----- +commits_on +Commits on +----- +browse_repo_at_point +Browse repository at this point +----- diff --git a/translations/ru.tr b/translations/ru.tr index 16b5fbd..01922de 100644 --- a/translations/ru.tr +++ b/translations/ru.tr @@ -854,3 +854,39 @@ admin_stats_new_issues admin_stats_last_n_days За 30 дней ----- +commit_committed +закоммитил +----- +commit_view_patch +Посмотреть патч +----- +commit_files_changed +файлов изменено +----- +commit_additions +добавлений +----- +commit_deletions +удалений +----- +commit_no_changes +В этом коммите нет изменений файлов. +----- +commit_binary_file +Бинарный файл не отображается. +----- +commit_file_new +новый файл +----- +commit_file_deleted +удалён +----- +commit_file_renamed +переименован +----- +commits_on +Коммиты от +----- +browse_repo_at_point +Посмотреть репозиторий на этот момент +----- -- 2.39.5