-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
base: main
Are you sure you want to change the base?
Changes from all commits
b29d401
98157ad
073c593
0581b27
b9dfd3e
b1efeae
d63aca0
69e0505
22f9a51
fca0992
1228d48
948ff41
c81ab46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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" | ||
|
@@ -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()) | ||
|
@@ -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 { | ||
|
@@ -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 | ||
|
@@ -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] | ||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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. |
||
|
||
// Build result in sorted order | ||
for _, dateUnix := range dates { | ||
result = append(result, GroupedCommits{ | ||
Date: timeutil.TimeStamp(dateUnix), | ||
Commits: grouped[dateUnix], | ||
}) | ||
} | ||
|
||
return result | ||
} |
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"> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just a nit may forgatten
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it is a wrong class ...... The real |
||||||
{{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}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,8 +29,8 @@ | |
</div> | ||
{{end}} | ||
|
||
{{if and .Commits (gt .CommitCount 0)}} | ||
{{template "repo/commits_list" .}} | ||
kerwin612 marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still the question, it seems that "wiki" is still using the |
||
{{if and .GroupCommits (gt .CommitCount 0)}} | ||
{{template "repo/commits_list_group_by_date" .}} | ||
{{end}} | ||
|
||
{{template "base/paginate" .}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think this algorithm is right, or maybe it is right but it needs some tests.
For example:
Actually they are the same timestamp (1744243200), and the day depends on visitor's timezone.
UTC
, time is2025-04-10T00:00:00Z
, on local day "2025-04-10"-04:00
, time is2025-04-09T20:00:00-04:00
, on local day "2025-04-09"ps: I have no idea how GitHub handles such timezone problem then renders the "date" to end users. Just FYI.
Or maybe some tests could help to clarify the expected behavior.