Skip to content

Commit 10461b2

Browse files
bircniclaude
andcommitted
feat(repo): display co-author stacks beside commit SHAs
Show co-author avatar stacks on commit SHA surfaces including commits list rows, graph, blame, and dashboard feeds, with repository-level data propagation and tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ac82797 commit 10461b2

9 files changed

Lines changed: 60 additions & 37 deletions

File tree

modules/repository/commits.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type PushCommit struct {
2929
AuthorName string
3030
CommitterEmail string
3131
CommitterName string
32+
CoAuthors []*git.Signature
3233
Timestamp time.Time
3334
}
3435

@@ -157,6 +158,7 @@ func CommitToPushCommit(commit *git.Commit) *PushCommit {
157158
AuthorName: commit.Author.Name,
158159
CommitterEmail: commit.Committer.Email,
159160
CommitterName: commit.Committer.Name,
161+
CoAuthors: commit.CoAuthorSignatures(),
160162
Timestamp: commit.Author.When,
161163
}
162164
}

modules/repository/commits_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,18 @@ func TestCommitToPushCommit(t *testing.T) {
145145
ID: sha1,
146146
Author: sig,
147147
Committer: sig,
148-
CommitMessage: git.CommitMessage{MessageRaw: "Commit Message"},
148+
CommitMessage: git.CommitMessage{MessageRaw: "Commit Message\n\nCo-authored-by: Jane Doe <jane@example.com>"},
149149
})
150150
assert.Equal(t, hexString, pushCommit.Sha1)
151-
assert.Equal(t, "Commit Message", pushCommit.Message)
151+
assert.Equal(t, "Commit Message\n\nCo-authored-by: Jane Doe <jane@example.com>", pushCommit.Message)
152152
assert.Equal(t, "example@example.com", pushCommit.AuthorEmail)
153153
assert.Equal(t, "John Doe", pushCommit.AuthorName)
154154
assert.Equal(t, "example@example.com", pushCommit.CommitterEmail)
155155
assert.Equal(t, "John Doe", pushCommit.CommitterName)
156+
if assert.Len(t, pushCommit.CoAuthors, 1) {
157+
assert.Equal(t, "jane@example.com", pushCommit.CoAuthors[0].Email)
158+
assert.Equal(t, "Jane Doe", pushCommit.CoAuthors[0].Name)
159+
}
156160
assert.Equal(t, now, pushCommit.Timestamp)
157161
}
158162

routers/web/repo/blame.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ import (
2929
type blameRow struct {
3030
RowNumber int
3131

32-
Avatar template.HTML
3332
PreviousSha string
3433
PreviousShaURL string
3534
CommitURL string
3635
CommitMessage string
3736
CommitSince template.HTML
37+
AuthorUser *user_model.User
38+
CoAuthors []*user_model.CoAuthorUser
39+
Author *git.Signature
3840

3941
Code template.HTML
4042
EscapeStatus *charset.EscapeStatus
@@ -221,13 +223,10 @@ func processBlameParts(ctx *context.Context, blameParts []*gitrepo.BlamePart) ma
221223
return commitNames
222224
}
223225

224-
func renderBlameFillFirstBlameRow(repoLink string, avatarUtils *templates.AvatarUtils, part *gitrepo.BlamePart, commit *user_model.UserCommit, br *blameRow) {
225-
if commit.User != nil {
226-
br.Avatar = avatarUtils.Avatar(commit.User, 18)
227-
} else {
228-
br.Avatar = avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18)
229-
}
230-
226+
func renderBlameFillFirstBlameRow(repoLink string, part *gitrepo.BlamePart, commit *user_model.UserCommit, br *blameRow) {
227+
br.AuthorUser = commit.User
228+
br.CoAuthors = commit.CoAuthors
229+
br.Author = commit.Author
231230
br.PreviousSha = part.PreviousSha
232231
br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(part.PreviousSha), util.PathEscapeSegments(part.PreviousPath))
233232
br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha))
@@ -243,7 +242,6 @@ func renderBlame(ctx *context.Context, blameParts []*gitrepo.BlamePart, commitNa
243242

244243
buf := &bytes.Buffer{}
245244
rows := make([]*blameRow, 0)
246-
avatarUtils := templates.NewAvatarUtils(ctx)
247245
rowNumber := 0 // will be 1-based
248246
for _, part := range blameParts {
249247
for partLineIdx, line := range part.Lines {
@@ -258,7 +256,7 @@ func renderBlame(ctx *context.Context, blameParts []*gitrepo.BlamePart, commitNa
258256
}
259257

260258
if partLineIdx == 0 {
261-
renderBlameFillFirstBlameRow(ctx.Repo.RepoLink, avatarUtils, part, commitNames[part.Sha], br)
259+
renderBlameFillFirstBlameRow(ctx.Repo.RepoLink, part, commitNames[part.Sha], br)
262260
}
263261
}
264262
}

services/repository/gitgraph/graph_models.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,7 @@ func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error
9393
// before finally retrieving the latest status
9494
func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_model.Repository, gitRepo *git.Repository) error {
9595
var err error
96-
var ok bool
97-
98-
emails := map[string]*user_model.User{}
96+
emailSet := map[string]struct{}{}
9997
keyMap := map[string]bool{}
10098

10199
for _, c := range graph.Commits {
@@ -106,13 +104,44 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_
106104
if err != nil {
107105
return fmt.Errorf("GetCommit: %s Error: %w", c.Rev, err)
108106
}
109-
110107
if c.Commit.Author != nil {
111-
email := c.Commit.Author.Email
112-
if c.User, ok = emails[email]; !ok {
113-
c.User, _ = user_model.GetUserByEmail(ctx, email)
114-
emails[email] = c.User
108+
emailSet[c.Commit.Author.Email] = struct{}{}
109+
}
110+
for _, sig := range c.Commit.CoAuthorSignatures() {
111+
emailSet[sig.Email] = struct{}{}
112+
}
113+
}
114+
115+
allEmails := make([]string, 0, len(emailSet))
116+
for email := range emailSet {
117+
allEmails = append(allEmails, email)
118+
}
119+
var emailUserMap *user_model.EmailUserMap
120+
if len(allEmails) > 0 {
121+
emailUserMap, err = user_model.GetUsersByEmails(ctx, allEmails)
122+
if err != nil {
123+
log.Error("GetUsersByEmails: %v", err)
124+
}
125+
}
126+
127+
for _, c := range graph.Commits {
128+
if c.Commit == nil {
129+
continue
130+
}
131+
if c.Commit.Author != nil && emailUserMap != nil {
132+
c.User = emailUserMap.GetByEmail(c.Commit.Author.Email)
133+
}
134+
coAuthorSigs := c.Commit.CoAuthorSignatures()
135+
c.CoAuthors = make([]*user_model.CoAuthorUser, 0, len(coAuthorSigs))
136+
for _, sig := range coAuthorSigs {
137+
var giteaUser *user_model.User
138+
if emailUserMap != nil {
139+
giteaUser = emailUserMap.GetByEmail(sig.Email)
115140
}
141+
c.CoAuthors = append(c.CoAuthors, &user_model.CoAuthorUser{
142+
GiteaUser: giteaUser,
143+
TrailerSignature: sig,
144+
})
116145
}
117146

118147
c.Verification = asymkey_service.ParseCommitWithSignature(ctx, c.Commit)
@@ -248,6 +277,7 @@ func newRefsFromRefNames(refNames []byte) []git.Reference {
248277
type Commit struct {
249278
Commit *git.Commit
250279
User *user_model.User
280+
CoAuthors []*user_model.CoAuthorUser
251281
Verification *asymkey_model.CommitVerification
252282
Status *git_model.CommitStatus
253283
Flow int64

templates/repo/blame.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<div class="blame-info">
4444
<div class="blame-data">
4545
<div class="blame-avatar">
46-
{{$row.Avatar}}
46+
{{template "repo/commit_coauthor_avatar_stack" dict "AuthorUser" $row.AuthorUser "AuthorSignature" $row.Author "CoAuthors" $row.CoAuthors}}
4747
</div>
4848
<div class="blame-message muted-links" title="{{$row.CommitMessage}}">
4949
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $row.CommitMessage $row.CommitURL $.Repository}}

templates/repo/commit_sign_badge.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ so this template should be kept as small as possible, DO NOT put large component
6464
{{- if $verified -}}
6565
{{- if and $signingUser $signingUser.ID -}}
6666
<span data-tooltip-content="{{$msgReason}}">{{svg "gitea-lock"}}</span>
67-
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.Avatar $signingUser 16}}</span>
67+
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.Avatar $signingUser 20}}</span>
6868
{{- else -}}
6969
<span data-tooltip-content="{{$msgReason}}">{{svg "gitea-lock-cog"}}</span>
70-
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.AvatarByEmail $signingEmail "" 16}}</span>
70+
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.AvatarByEmail $signingEmail "" 20}}</span>
7171
{{- end -}}
7272
{{- else -}}
7373
<span data-tooltip-content="{{$msgReason}}">{{svg "gitea-unlock"}}</span>

templates/repo/commits_list_small.tmpl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
{{$index = Eval $index "+" 1}}
66
<div class="flex-text-block" id="{{$tag}}">{{/*singular-commit*/}}
77
<span class="badge badge-commit">{{svg "octicon-git-commit"}}</span>
8-
{{if .User}}
9-
<a class="avatar" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20}}</a>
10-
{{else}}
11-
{{ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 20}}
12-
{{end}}
8+
{{template "repo/commit_coauthor_avatar_stack" dict "AuthorUser" .User "AuthorSignature" .Author "CoAuthors" .CoAuthors}}
139

1410
{{$commitBaseLink := printf "%s/commit" $.comment.Issue.PullRequest.BaseRepo.Link}}
1511
{{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}}

templates/repo/graph/commits.tmpl

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,7 @@
4141
</span>
4242

4343
<span class="flex-text-inline tw-text-12">
44-
{{if $commit.User}}
45-
{{ctx.AvatarUtils.Avatar $commit.User 18}}
46-
{{$commit.User.GetShortDisplayNameLinkHTML}}
47-
{{else}}
48-
{{$gitUserName := $commit.Commit.Author.Name}}
49-
{{ctx.AvatarUtils.AvatarByEmail $commit.Commit.Author.Email $gitUserName 18}}
50-
{{$gitUserName}}
51-
{{end}}
44+
{{template "repo/commit_coauthor_avatars" dict "AuthorUser" $commit.User "AuthorSignature" $commit.Commit.Author "CoAuthors" $commit.CoAuthors}}
5245
</span>
5346

5447
<span class="time flex-text-inline">{{DateUtils.FullTime $commit.Date}}</span>

templates/user/dashboard/feeds.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
{{range $pushCommit := $push.Commits}}
9292
{{$commitLink := printf "%s/commit/%s" $repoLink .Sha1}}
9393
<div class="flex-text-block">
94-
<img loading="lazy" alt class="ui avatar" src="{{$push.AvatarLink ctx .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16">
94+
{{template "repo/commit_coauthor_avatar_stack" dict "AuthorSignature" (dict "Email" .AuthorEmail "Name" .AuthorName) "CoAuthorSignatures" .CoAuthors}}
9595
<a class="ui sha label" href="{{$commitLink}}">{{ShortSha .Sha1}}</a>
9696
<span class="tw-inline-block tw-truncate">
9797
{{ctx.RenderUtils.RenderCommitMessage $pushCommit.Message $repo}}

0 commit comments

Comments
 (0)