Merge fe9be75a8977affbeec227b1c90170d468f77ce1 into 0f63a5ef48b23c6ab26a4b13cfd26edbe4efbfa3

This commit is contained in:
Kerwin Bryant 2025-05-10 14:54:36 +02:00 committed by GitHub
commit b78f6cfd85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 418 additions and 26 deletions

View File

@ -10,7 +10,9 @@ import (
"html/template"
"net/http"
"path"
"sort"
"strings"
"time"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
@ -28,6 +30,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
@ -83,11 +86,12 @@ func Commits(ctx *context.Context) {
ctx.ServerError("CommitsByRange", err)
return
}
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
processedCommits, err := processGitCommits(ctx, commits)
if err != nil {
ctx.ServerError("processGitCommits", err)
return
}
ctx.Data["GroupCommits"] = GroupCommitsByDate(processedCommits)
commitIDs := make([]string, 0, len(commits))
for _, c := range commits {
commitIDs = append(commitIDs, c.ID.String())
@ -199,11 +203,12 @@ func SearchCommits(ctx *context.Context) {
return
}
ctx.Data["CommitCount"] = len(commits)
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
processedCommits, err := processGitCommits(ctx, commits)
if err != nil {
ctx.ServerError("processGitCommits", err)
return
}
ctx.Data["GroupCommits"] = GroupCommitsByDate(processedCommits)
ctx.Data["Keyword"] = query
if all {
@ -245,11 +250,12 @@ func FileHistory(ctx *context.Context) {
ctx.ServerError("CommitsByFileAndRange", err)
return
}
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
processedCommits, err := processGitCommits(ctx, commits)
if err != nil {
ctx.ServerError("processGitCommits", err)
return
}
ctx.Data["GroupCommits"] = GroupCommitsByDate(processedCommits)
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
@ -463,3 +469,57 @@ func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_m
}
return commits, nil
}
// GroupedCommits defines the structure for grouped commits.
type GroupedCommits struct {
Date timeutil.TimeStamp
Commits []*git_model.SignCommitWithStatuses
}
// GroupCommitsByDate groups the commits by date (in days) using UTC timezone.
func GroupCommitsByDate(commits []*git_model.SignCommitWithStatuses) []GroupedCommits {
// Use Unix timestamp of date as key (truncated to day)
grouped := make(map[int64][]*git_model.SignCommitWithStatuses)
for _, commit := range commits {
var sigTime time.Time
if commit.Committer != nil {
sigTime = commit.Committer.When
} else if commit.Author != nil {
sigTime = commit.Author.When
}
// Convert time to UTC timezone first
sigTimeUTC := sigTime.UTC()
// Truncate time to date part (remove hours, minutes, seconds)
year, month, day := sigTimeUTC.Date()
dateOnly := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
dateUnix := dateOnly.Unix()
grouped[dateUnix] = append(grouped[dateUnix], commit)
}
// Create result slice with pre-allocated capacity
result := make([]GroupedCommits, 0, len(grouped))
// Collect all dates and sort them
dates := make([]int64, 0, len(grouped))
for dateUnix := range grouped {
dates = append(dates, dateUnix)
}
// Sort dates in descending order (most recent first)
sort.Slice(dates, func(i, j int) bool {
return dates[i] > dates[j]
})
// Build result in sorted order
for _, dateUnix := range dates {
result = append(result, GroupedCommits{
Date: timeutil.TimeStamp(dateUnix),
Commits: grouped[dateUnix],
})
}
return result
}

View File

@ -0,0 +1,114 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"testing"
"time"
"code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
)
func TestGroupCommitsByDate(t *testing.T) {
// Create test data
// These two commits represent the same moment but in different timezones
// commit1: 2025-04-10T08:00:00+08:00
// commit2: 2025-04-09T23:00:00-01:00
// Their UTC time is both 2025-04-10T00:00:00Z
// Create the first commit (Asia timezone +8)
asiaTimezone := time.FixedZone("Asia/Shanghai", 8*60*60)
commit1Time := time.Date(2025, 4, 10, 8, 0, 0, 0, asiaTimezone)
commit1 := &git_model.SignCommitWithStatuses{
SignCommit: &asymkey.SignCommit{
UserCommit: &user.UserCommit{
Commit: &git.Commit{
Committer: &git.Signature{
When: commit1Time,
},
},
},
},
}
// Create the second commit (Western timezone -1)
westTimezone := time.FixedZone("West", -1*60*60)
commit2Time := time.Date(2025, 4, 9, 23, 0, 0, 0, westTimezone)
commit2 := &git_model.SignCommitWithStatuses{
SignCommit: &asymkey.SignCommit{
UserCommit: &user.UserCommit{
Commit: &git.Commit{
Committer: &git.Signature{
When: commit2Time,
},
},
},
},
}
// Verify that the two timestamps actually represent the same moment
assert.Equal(t, commit1Time.Unix(), commit2Time.Unix(), "The two commits should have the same Unix timestamp")
// Test the modified grouping behavior
commits := []*git_model.SignCommitWithStatuses{commit1, commit2}
grouped := GroupCommitsByDate(commits)
// Output the grouping results for observation
t.Logf("Number of grouped results: %d", len(grouped))
for i, group := range grouped {
t.Logf("Group %d: Date %s, Number of commits %d", i, time.Unix(int64(group.Date), 0).Format("2006-01-02"), len(group.Commits))
for j, c := range group.Commits {
t.Logf(" Commit %d: Time %s", j, c.SignCommit.UserCommit.Commit.Committer.When.Format(time.RFC3339))
}
}
// After modification, these two commits should be grouped together as they are on the same day in UTC timezone
assert.Len(t, grouped, 1, "After modification, the two commits should be grouped together")
// Verify the group date (should be 2025-04-10, the date in UTC timezone)
utcDate := time.Date(2025, 4, 10, 0, 0, 0, 0, time.UTC)
assert.Equal(t, timeutil.TimeStamp(utcDate.Unix()), grouped[0].Date)
assert.Len(t, grouped[0].Commits, 2)
// Verify that both commits are in this group
commitMap := make(map[*git_model.SignCommitWithStatuses]bool)
for _, c := range grouped[0].Commits {
commitMap[c] = true
}
assert.True(t, commitMap[commit1], "The first commit should be in the group")
assert.True(t, commitMap[commit2], "The second commit should be in the group")
// Add a commit with a different date for testing
nextDayTimezone := time.FixedZone("NextDay", 0)
commit3Time := time.Date(2025, 4, 11, 0, 0, 0, 0, nextDayTimezone)
commit3 := &git_model.SignCommitWithStatuses{
SignCommit: &asymkey.SignCommit{
UserCommit: &user.UserCommit{
Commit: &git.Commit{
Committer: &git.Signature{
When: commit3Time,
},
},
},
},
}
// Test with commits from different dates
commits = append(commits, commit3)
grouped = GroupCommitsByDate(commits)
// Now there should be two groups
assert.Len(t, grouped, 2, "There should be two different date groups")
// Verify date sorting (descending, most recent date first)
assert.True(t, time.Unix(int64(grouped[0].Date), 0).After(time.Unix(int64(grouped[1].Date), 0)),
"Dates should be sorted in descending order")
}

View File

@ -665,7 +665,7 @@ func PrepareCompareDiff(
ctx.ServerError("processGitCommits", err)
return false
}
ctx.Data["Commits"] = commits
ctx.Data["GroupCommits"] = GroupCommitsByDate(commits)
ctx.Data["CommitCount"] = len(commits)
title := ci.HeadBranch

View File

@ -627,7 +627,7 @@ func ViewPullCommits(ctx *context.Context) {
ctx.ServerError("processGitCommits", err)
return
}
ctx.Data["Commits"] = commits
ctx.Data["GroupCommits"] = GroupCommitsByDate(commits)
ctx.Data["CommitCount"] = len(commits)
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)

View File

@ -0,0 +1,111 @@
{{range $index, $groupCommit := .GroupCommits}}
<div class="ui timeline commits-list-group-by-date" data-index="{{$index}}">
<div class="timeline-badge-wrapper">
<div class="timeline-badge">
{{svg "octicon-git-commit"}}
</div>
</div>
<div class="timeline-body">
<h3 class="flex-text-block tw-py-2 timeline-heading">
Commits on {{DateUtils.AbsoluteShort $groupCommit.Date}}
</h3>
<div class="tw-flex tw-mt-2 timeline-list-container">
<ul class="commits-list">
{{range $groupCommit.Commits}}
{{$commitRepoLink := $.RepoLink}}{{if $.CommitRepoLink}}{{$commitRepoLink = $.CommitRepoLink}}{{end}}
<li class="commits-list-item">
<div class="tw-pt-4 tw-pl-4 title">
<h4>
<span class="message-wrapper">
{{if $.PageIsWiki}}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | ctx.RenderUtils.RenderEmoji}}</span>
{{else}}
{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.Repository.ComposeCommentMetas ctx)}}</span>
{{end}}
</span>
{{if IsMultilineCommitMessage .Message}}
<button class="ui button ellipsis-button" aria-expanded="false" data-global-click="onRepoEllipsisButtonClick">...</button>
{{end}}
{{if IsMultilineCommitMessage .Message}}
<pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeCommentMetas ctx)}}</pre>
{{end}}
{{if $.CommitsTagsMap}}
{{range (index $.CommitsTagsMap .ID.String)}}
{{- template "repo/tag/name" dict "RepoLink" $.Repository.Link "TagName" .TagName "IsRelease" (not .IsTag) -}}
{{end}}
{{end}}
</h4>
</div>
<div class="tw-flex tw-items-center tw-gap-1 tw-pb-4 tw-pl-4 description">
<div class="author">
{{$userName := .Author.Name}}
{{if .User}}
{{if and .User.FullName DefaultShowFullName}}
{{$userName = .User.FullName}}
{{end}}
{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}<a class="muted author-wrapper" href="{{.User.HomeLink}}">{{$userName}}</a>
{{else}}
{{ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 28 "tw-mr-2"}}
<span class="author-wrapper">{{$userName}}</span>
{{end}}
</div>
<span>
{{if .Committer}}
committed
{{else}}
authored
{{end}}
</span>
{{if .Committer}}
{{DateUtils.TimeSince .Committer.When}}
{{else}}
{{DateUtils.TimeSince .Author.When}}
{{end}}
{{if .Statuses}}
<span>·</span>
{{end}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
</div>
<div class="tw-flex tw-flex-wrap tw-items-center tw-gap-2 tw-pr-4 metadata">
{{$commitBaseLink := ""}}
{{if $.PageIsWiki}}
{{$commitBaseLink = printf "%s/wiki/commit" $commitRepoLink}}
{{else if $.PageIsPullCommits}}
{{$commitBaseLink = printf "%s/pulls/%d/commits" $commitRepoLink $.Issue.Index}}
{{else if $.Reponame}}
{{$commitBaseLink = printf "%s/commit" $commitRepoLink}}
{{end}}
<div class="commit-sign-badge">
{{template "repo/commit_sign_badge" dict "Commit" . "CommitBaseLink" $commitBaseLink "CommitSignVerification" .Verification}}
</div>
<div class="tw-flex tw-flex-wrap tw-items-center tw-gap-2">
<div>
<button class="btn interact-bg tw-p-2 copy-commit-id" data-tooltip-content="{{ctx.Locale.Tr "copy_hash"}}" data-clipboard-text="{{.ID}}">{{svg "octicon-copy"}}</button>
</div>
{{/* at the moment, wiki doesn't support these "view" links like "view at history point" */}}
{{if not $.PageIsWiki}}
{{/* view single file diff */}}
{{if $.FileTreePath}}
<a class="btn interact-bg tw-p-2 view-single-diff" data-tooltip-content="{{ctx.Locale.Tr "repo.commits.view_file_diff"}}"
href="{{$commitRepoLink}}/commit/{{.ID.String}}?files={{$.FileTreePath}}"
>{{svg "octicon-file-diff"}}</a>
{{end}}
{{/* view at history point */}}
{{$viewCommitLink := printf "%s/src/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
{{if $.FileTreePath}}{{$viewCommitLink = printf "%s/%s" $viewCommitLink (PathEscapeSegments $.FileTreePath)}}{{end}}
<a class="btn interact-bg tw-p-2 view-commit-path" data-tooltip-content="{{ctx.Locale.Tr "repo.commits.view_path"}}" href="{{$viewCommitLink}}">{{svg "octicon-file-code"}}</a>
{{end}}
</div>
</div>
</li>
{{end}}
</ul>
</div>
</div>
</div>
{{end}}

View File

@ -29,8 +29,8 @@
</div>
{{end}}
{{if and .Commits (gt .CommitCount 0)}}
{{template "repo/commits_list" .}}
{{if and .GroupCommits (gt .CommitCount 0)}}
{{template "repo/commits_list_group_by_date" .}}
{{end}}
{{template "base/paginate" .}}

View File

@ -707,7 +707,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
commitURL, exists := doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge a").Last().Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)

View File

@ -49,7 +49,7 @@ func TestPullCreate_CommitStatus(t *testing.T) {
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
commitURL, exists := doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge a").Last().Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
@ -87,12 +87,12 @@ func TestPullCreate_CommitStatus(t *testing.T) {
resp = session.MakeRequest(t, req, http.StatusOK)
doc = NewHTMLParser(t, resp.Body)
commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
commitURL, exists = doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge a").Last().Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
assert.Equal(t, commitID, path.Base(commitURL))
cls, ok := doc.doc.Find("#commits-table tbody tr td.message .commit-status").Last().Attr("class")
cls, ok := doc.doc.Find(".timeline.commits-list-group-by-date .description .commit-status").Last().Attr("class")
assert.True(t, ok)
assert.Contains(t, cls, statesIcons[status])
}

View File

@ -22,7 +22,7 @@ func testRepoCommitsSearch(t *testing.T, query, commit string) {
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
sel := doc.doc.Find("#commits-table tbody tr td.sha a")
sel := doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge a")
assert.Equal(t, commit, strings.TrimSpace(sel.Text()))
}

View File

@ -33,7 +33,7 @@ func TestRepoCommits(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href")
commitURL, exists := doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge .commit-id-short").Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
}
@ -50,7 +50,7 @@ func Test_ReposGitCommitListNotMaster(t *testing.T) {
doc := NewHTMLParser(t, resp.Body)
commits := []string{}
doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) {
doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge .commit-id-short").Each(func(i int, s *goquery.Selection) {
commitURL, exists := s.Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
@ -63,7 +63,7 @@ func Test_ReposGitCommitListNotMaster(t *testing.T) {
assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", commits[2])
userNames := []string{}
doc.doc.Find("#commits-table .author-wrapper").Each(func(i int, s *goquery.Selection) {
doc.doc.Find(".timeline.commits-list-group-by-date .description .author-wrapper").Each(func(i int, s *goquery.Selection) {
userPath, exists := s.Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, userPath)
@ -87,7 +87,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href")
commitURL, exists := doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge .commit-id-short").Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
@ -105,7 +105,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
doc = NewHTMLParser(t, resp.Body)
// Check if commit status is displayed in message column (.tippy-target to ignore the tippy trigger)
sel := doc.doc.Find("#commits-table .message .tippy-target .commit-status")
sel := doc.doc.Find(".timeline.commits-list-group-by-date .description .tippy-target .commit-status")
assert.Equal(t, 1, sel.Length())
for _, class := range classes {
assert.True(t, sel.HasClass(class))
@ -181,7 +181,7 @@ func TestRepoCommitsStatusParallel(t *testing.T) {
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href")
commitURL, exists := doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge .commit-id-short").Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
@ -216,7 +216,7 @@ func TestRepoCommitsStatusMultiple(t *testing.T) {
doc := NewHTMLParser(t, resp.Body)
// Get first commit URL
commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href")
commitURL, exists := doc.doc.Find(".timeline.commits-list-group-by-date .commit-sign-badge .commit-id-short").Attr("href")
assert.True(t, exists)
assert.NotEmpty(t, commitURL)
@ -241,6 +241,6 @@ func TestRepoCommitsStatusMultiple(t *testing.T) {
doc = NewHTMLParser(t, resp.Body)
// Check that the data-global-init="initCommitStatuses" (for trigger) and commit-status (svg) are present
sel := doc.doc.Find(`#commits-table .message [data-global-init="initCommitStatuses"] .commit-status`)
sel := doc.doc.Find(`.timeline.commits-list-group-by-date .description [data-global-init="initCommitStatuses"] .commit-status`)
assert.Equal(t, 1, sel.Length())
}

View File

@ -48,9 +48,9 @@ func TestCommitListActions(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
AssertHTMLElement(t, htmlDoc, `.commit-list .copy-commit-id`, true)
AssertHTMLElement(t, htmlDoc, `.commit-list .view-single-diff`, false)
AssertHTMLElement(t, htmlDoc, `.commit-list .view-commit-path`, true)
AssertHTMLElement(t, htmlDoc, `.timeline.commits-list-group-by-date .copy-commit-id`, true)
AssertHTMLElement(t, htmlDoc, `.timeline.commits-list-group-by-date .view-single-diff`, false)
AssertHTMLElement(t, htmlDoc, `.timeline.commits-list-group-by-date .view-commit-path`, true)
})
t.Run("RepoFileHistory", func(t *testing.T) {
@ -60,8 +60,8 @@ func TestCommitListActions(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
AssertHTMLElement(t, htmlDoc, `.commit-list .copy-commit-id`, true)
AssertHTMLElement(t, htmlDoc, `.commit-list .view-single-diff`, true)
AssertHTMLElement(t, htmlDoc, `.commit-list .view-commit-path`, true)
AssertHTMLElement(t, htmlDoc, `.timeline.commits-list-group-by-date .copy-commit-id`, true)
AssertHTMLElement(t, htmlDoc, `.timeline.commits-list-group-by-date .view-single-diff`, true)
AssertHTMLElement(t, htmlDoc, `.timeline.commits-list-group-by-date .view-commit-path`, true)
})
}

View File

@ -2281,3 +2281,110 @@ tbody.commit-list {
.branch-selector-dropdown .scrolling.menu .loading-indicator {
height: 4em;
}
.commits-list-group-by-date.timeline {
display: flex;
margin-left: 16px;
position: relative;
padding-bottom: 6px;
}
.commits-list-group-by-date.timeline[data-index="0"] {
margin-top: 6px;
}
.commits-list-group-by-date.timeline::before {
background-color: var(--color-timeline);
bottom: 0;
content: "";
display: block;
left: 0;
position: absolute;
top: 0;
width: 2px;
}
.commits-list-group-by-date.timeline .timeline-badge-wrapper {
position: relative;
z-index: 1;
}
.commits-list-group-by-date.timeline .timeline-badge {
border-style: solid;
border-radius: var(--border-radius-full);
color: var(--color-text);
background-color: var(--color-box-body);
float: left;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-left: -16px;
width: 34px;
height: 34px;
overflow: hidden;
}
.commits-list-group-by-date.timeline .timeline-heading {
font-size: 15px;
font-weight: var(--font-weight-normal);
color: var(--color-text-light-2);
margin: 0;
}
.commits-list-group-by-date.timeline .timeline-body {
max-width: 100%;
flex: auto;
padding-left: 5px;
}
.commits-list-group-by-date.timeline .timeline-list-container {
border: 1px solid var(--color-secondary);
background: var(--color-box-body);
border-radius: var(--border-radius);
}
.commits-list-group-by-date.timeline .commits-list {
list-style: none;
margin: 0;
padding: 0;
flex: auto;
}
.commits-list-group-by-date.timeline .commits-list-item {
list-style: none;
display: grid;
gap: .5rem;
min-height: 2rem;
position: relative;
--core-grid-template-columns: minmax(30%, 1fr);
--last-grid-template-column: minmax(0, max-content);
grid-template-columns: var(--core-grid-template-columns) var(--last-grid-template-column);
grid-template-areas: "primary metadata" "main-content metadata";
grid-template-rows: repeat(2, auto);
}
.commits-list-group-by-date.timeline .commits-list-item:not(:last-child) {
border-bottom: 1px solid var(--color-secondary);
}
.commits-list-group-by-date.timeline .commits-list-item:hover {
background-color: var(--color-hover)
}
.commits-list-group-by-date.timeline .commits-list-item .title {
grid-area: primary;
}
.commits-list-group-by-date.timeline .commits-list-item .description {
grid-area: main-content;
}
.commits-list-group-by-date.timeline .commits-list-item .metadata {
grid-area: metadata;
}
.commits-list-group-by-date.timeline .author img {
width: 16px;
height: 16px;
}