Skip to content

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
63 changes: 60 additions & 3 deletions routers/web/repo/commit.go
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,54 @@ 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).
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
}

// Truncate time to date part (remove hours, minutes, seconds)
year, month, day := sigTime.Date()
dateOnly := time.Date(year, month, day, 0, 0, 0, 0, sigTime.Location())
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
}
2 changes: 1 addition & 1 deletion routers/web/repo/compare.go
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
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
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
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" .}}
{{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
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
6 changes: 3 additions & 3 deletions tests/integration/pull_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,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)

Expand Down Expand Up @@ -83,12 +83,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])
}
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/repo_commits_search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
}

Expand Down
16 changes: 8 additions & 8 deletions tests/integration/repo_commits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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))
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand All @@ -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())
}
12 changes: 6 additions & 6 deletions tests/integration/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
})
}
Loading