Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Group commit list page by date #34098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
kerwin612 wants to merge 17 commits into go-gitea:main
base: main
Choose a base branch
Loading
from kerwin612:refactor-commit-list-page-group-by-date
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b29d401
Complete the grouping design within the existing table.
kerwin612 Apr 3, 2025
98157ad
Merge branch 'main' into refactor-commit-list-page-group-by-date
kerwin612 Apr 14, 2025
073c593
revert
kerwin612 Apr 7, 2025
0581b27
Refactor the structure of the commit list page to grouping by date.
kerwin612 Apr 7, 2025
b9dfd3e
Merge branch main into refactor-commit-list-page-group-by-date
kerwin612 Apr 14, 2025
b1efeae
Merge branch 'main' into refactor-commit-list-page-group-by-date
kerwin612 Apr 21, 2025
d63aca0
fix
kerwin612 Apr 21, 2025
69e0505
fix
kerwin612 Apr 21, 2025
22f9a51
fix
kerwin612 Apr 21, 2025
fca0992
fix
kerwin612 Apr 21, 2025
1228d48
Merge branch 'main' into refactor-commit-list-page-group-by-date
kerwin612 Apr 21, 2025
948ff41
fix
kerwin612 Apr 21, 2025
c81ab46
Merge branch 'main' into refactor-commit-list-page-group-by-date
kerwin612 Apr 22, 2025
5d0f6a0
Merge branch 'main' into refactor-commit-list-page-group-by-date
kerwin612 Apr 29, 2025
dd62a17
Update templates/repo/commits_list_group_by_date.tmpl
kerwin612 Apr 29, 2025
90dcac2
fix
kerwin612 Apr 29, 2025
fe9be75
use utctime group; add unit test;
kerwin612 Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 63 additions & 3 deletions routers/web/repo/commit.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -27,6 +29,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"
Expand Down Expand Up @@ -82,11 +85,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())
Expand Down Expand Up @@ -198,11 +202,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 {
Expand Down Expand Up @@ -244,11 +249,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
Expand Down Expand Up @@ -458,3 +464,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]
})
Copy link
Contributor

@wxiaoguang wxiaoguang Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another edge case: git commit's date could be manually set.

So you could see a commit list like this:

commit4 2025年04月10日
commit3 2025年04月01日
commit2 2025年04月30日
commit1 2025年04月15日

And the graph is commit1->commit2->commit3->commit4.

What's the expected output for such commit list? It needs some tests to clarify the behavior.

Copy link
Member Author

@kerwin612 kerwin612 Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The to be discussed here is which time field to use. Currently, the list uses the committer's time both before and after grouping. If adjustment is needed, we can make modifications later.

Copy link
Contributor

@wxiaoguang wxiaoguang May 26, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't mean "which field". I mean "how to group them", assume these dates are all from the "committer's time"

commit4, committer date: 2025年04月10日
commit3, committer date: 2025年04月01日
commit2, committer date: 2025年04月30日
commit1, committer date: 2025年04月15日

What's the expected output?


Or to be more precise

commit4, committer date: 2025年04月10日 09:00
commit3, committer date: 2025年04月01日 08:00
commit2, committer date: 2025年04月10日 07:00
commit1, committer date: 2025年04月01日 06:00

What's the expected output?


// Build result in sorted order
for _, dateUnix := range dates {
result = append(result, GroupedCommits{
Date: timeutil.TimeStamp(dateUnix),
Commits: grouped[dateUnix],
})
}

return result
}
114 changes: 114 additions & 0 deletions routers/web/repo/commit_test.go
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -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月10日T08:00:00+08:00
// commit2: 2025年04月09日T23:00:00-01:00
// Their UTC time is both 2025年04月10日T00: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")
}
2 changes: 1 addition & 1 deletion routers/web/repo/compare.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,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
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/pull.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,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)
Expand Down
111 changes: 111 additions & 0 deletions templates/repo/commits_list_group_by_date.tmpl
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -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}}
4 changes: 2 additions & 2 deletions templates/repo/commits_table.tmpl
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
</div>
{{end}}

{{if and .Commits (gt .CommitCount 0)}}
{{template "repo/commits_list" .}}
Copy link
Contributor

@wxiaoguang wxiaoguang Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still the question, it seems that "wiki" is still using the repo/commits_list, why not unify them to the new layout? Is there any strong reason that wiki must have a different layout?

{{if and .GroupCommits (gt .CommitCount 0)}}
{{template "repo/commits_list_group_by_date" .}}
{{end}}

{{template "base/paginate" .}}
2 changes: 1 addition & 1 deletion tests/integration/git_general_test.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,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)

Expand Down
Loading

AltStyle によって変換されたページ (->オリジナル) /