mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-11 11:25:42 +02:00
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>
This commit is contained in:
parent
ac82797cd8
commit
10461b2f4e
@ -29,6 +29,7 @@ type PushCommit struct {
|
||||
AuthorName string
|
||||
CommitterEmail string
|
||||
CommitterName string
|
||||
CoAuthors []*git.Signature
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
@ -157,6 +158,7 @@ func CommitToPushCommit(commit *git.Commit) *PushCommit {
|
||||
AuthorName: commit.Author.Name,
|
||||
CommitterEmail: commit.Committer.Email,
|
||||
CommitterName: commit.Committer.Name,
|
||||
CoAuthors: commit.CoAuthorSignatures(),
|
||||
Timestamp: commit.Author.When,
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,14 +145,18 @@ func TestCommitToPushCommit(t *testing.T) {
|
||||
ID: sha1,
|
||||
Author: sig,
|
||||
Committer: sig,
|
||||
CommitMessage: git.CommitMessage{MessageRaw: "Commit Message"},
|
||||
CommitMessage: git.CommitMessage{MessageRaw: "Commit Message\n\nCo-authored-by: Jane Doe <jane@example.com>"},
|
||||
})
|
||||
assert.Equal(t, hexString, pushCommit.Sha1)
|
||||
assert.Equal(t, "Commit Message", pushCommit.Message)
|
||||
assert.Equal(t, "Commit Message\n\nCo-authored-by: Jane Doe <jane@example.com>", pushCommit.Message)
|
||||
assert.Equal(t, "example@example.com", pushCommit.AuthorEmail)
|
||||
assert.Equal(t, "John Doe", pushCommit.AuthorName)
|
||||
assert.Equal(t, "example@example.com", pushCommit.CommitterEmail)
|
||||
assert.Equal(t, "John Doe", pushCommit.CommitterName)
|
||||
if assert.Len(t, pushCommit.CoAuthors, 1) {
|
||||
assert.Equal(t, "jane@example.com", pushCommit.CoAuthors[0].Email)
|
||||
assert.Equal(t, "Jane Doe", pushCommit.CoAuthors[0].Name)
|
||||
}
|
||||
assert.Equal(t, now, pushCommit.Timestamp)
|
||||
}
|
||||
|
||||
|
||||
@ -29,12 +29,14 @@ import (
|
||||
type blameRow struct {
|
||||
RowNumber int
|
||||
|
||||
Avatar template.HTML
|
||||
PreviousSha string
|
||||
PreviousShaURL string
|
||||
CommitURL string
|
||||
CommitMessage string
|
||||
CommitSince template.HTML
|
||||
AuthorUser *user_model.User
|
||||
CoAuthors []*user_model.CoAuthorUser
|
||||
Author *git.Signature
|
||||
|
||||
Code template.HTML
|
||||
EscapeStatus *charset.EscapeStatus
|
||||
@ -221,13 +223,10 @@ func processBlameParts(ctx *context.Context, blameParts []*gitrepo.BlamePart) ma
|
||||
return commitNames
|
||||
}
|
||||
|
||||
func renderBlameFillFirstBlameRow(repoLink string, avatarUtils *templates.AvatarUtils, part *gitrepo.BlamePart, commit *user_model.UserCommit, br *blameRow) {
|
||||
if commit.User != nil {
|
||||
br.Avatar = avatarUtils.Avatar(commit.User, 18)
|
||||
} else {
|
||||
br.Avatar = avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18)
|
||||
}
|
||||
|
||||
func renderBlameFillFirstBlameRow(repoLink string, part *gitrepo.BlamePart, commit *user_model.UserCommit, br *blameRow) {
|
||||
br.AuthorUser = commit.User
|
||||
br.CoAuthors = commit.CoAuthors
|
||||
br.Author = commit.Author
|
||||
br.PreviousSha = part.PreviousSha
|
||||
br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(part.PreviousSha), util.PathEscapeSegments(part.PreviousPath))
|
||||
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
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
rows := make([]*blameRow, 0)
|
||||
avatarUtils := templates.NewAvatarUtils(ctx)
|
||||
rowNumber := 0 // will be 1-based
|
||||
for _, part := range blameParts {
|
||||
for partLineIdx, line := range part.Lines {
|
||||
@ -258,7 +256,7 @@ func renderBlame(ctx *context.Context, blameParts []*gitrepo.BlamePart, commitNa
|
||||
}
|
||||
|
||||
if partLineIdx == 0 {
|
||||
renderBlameFillFirstBlameRow(ctx.Repo.RepoLink, avatarUtils, part, commitNames[part.Sha], br)
|
||||
renderBlameFillFirstBlameRow(ctx.Repo.RepoLink, part, commitNames[part.Sha], br)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,9 +93,7 @@ func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error
|
||||
// before finally retrieving the latest status
|
||||
func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_model.Repository, gitRepo *git.Repository) error {
|
||||
var err error
|
||||
var ok bool
|
||||
|
||||
emails := map[string]*user_model.User{}
|
||||
emailSet := map[string]struct{}{}
|
||||
keyMap := map[string]bool{}
|
||||
|
||||
for _, c := range graph.Commits {
|
||||
@ -106,13 +104,44 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommit: %s Error: %w", c.Rev, err)
|
||||
}
|
||||
|
||||
if c.Commit.Author != nil {
|
||||
email := c.Commit.Author.Email
|
||||
if c.User, ok = emails[email]; !ok {
|
||||
c.User, _ = user_model.GetUserByEmail(ctx, email)
|
||||
emails[email] = c.User
|
||||
emailSet[c.Commit.Author.Email] = struct{}{}
|
||||
}
|
||||
for _, sig := range c.Commit.CoAuthorSignatures() {
|
||||
emailSet[sig.Email] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
allEmails := make([]string, 0, len(emailSet))
|
||||
for email := range emailSet {
|
||||
allEmails = append(allEmails, email)
|
||||
}
|
||||
var emailUserMap *user_model.EmailUserMap
|
||||
if len(allEmails) > 0 {
|
||||
emailUserMap, err = user_model.GetUsersByEmails(ctx, allEmails)
|
||||
if err != nil {
|
||||
log.Error("GetUsersByEmails: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range graph.Commits {
|
||||
if c.Commit == nil {
|
||||
continue
|
||||
}
|
||||
if c.Commit.Author != nil && emailUserMap != nil {
|
||||
c.User = emailUserMap.GetByEmail(c.Commit.Author.Email)
|
||||
}
|
||||
coAuthorSigs := c.Commit.CoAuthorSignatures()
|
||||
c.CoAuthors = make([]*user_model.CoAuthorUser, 0, len(coAuthorSigs))
|
||||
for _, sig := range coAuthorSigs {
|
||||
var giteaUser *user_model.User
|
||||
if emailUserMap != nil {
|
||||
giteaUser = emailUserMap.GetByEmail(sig.Email)
|
||||
}
|
||||
c.CoAuthors = append(c.CoAuthors, &user_model.CoAuthorUser{
|
||||
GiteaUser: giteaUser,
|
||||
TrailerSignature: sig,
|
||||
})
|
||||
}
|
||||
|
||||
c.Verification = asymkey_service.ParseCommitWithSignature(ctx, c.Commit)
|
||||
@ -248,6 +277,7 @@ func newRefsFromRefNames(refNames []byte) []git.Reference {
|
||||
type Commit struct {
|
||||
Commit *git.Commit
|
||||
User *user_model.User
|
||||
CoAuthors []*user_model.CoAuthorUser
|
||||
Verification *asymkey_model.CommitVerification
|
||||
Status *git_model.CommitStatus
|
||||
Flow int64
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
<div class="blame-info">
|
||||
<div class="blame-data">
|
||||
<div class="blame-avatar">
|
||||
{{$row.Avatar}}
|
||||
{{template "repo/commit_coauthor_avatar_stack" dict "AuthorUser" $row.AuthorUser "AuthorSignature" $row.Author "CoAuthors" $row.CoAuthors}}
|
||||
</div>
|
||||
<div class="blame-message muted-links" title="{{$row.CommitMessage}}">
|
||||
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $row.CommitMessage $row.CommitURL $.Repository}}
|
||||
|
||||
@ -64,10 +64,10 @@ so this template should be kept as small as possible, DO NOT put large component
|
||||
{{- if $verified -}}
|
||||
{{- if and $signingUser $signingUser.ID -}}
|
||||
<span data-tooltip-content="{{$msgReason}}">{{svg "gitea-lock"}}</span>
|
||||
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.Avatar $signingUser 16}}</span>
|
||||
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.Avatar $signingUser 20}}</span>
|
||||
{{- else -}}
|
||||
<span data-tooltip-content="{{$msgReason}}">{{svg "gitea-lock-cog"}}</span>
|
||||
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.AvatarByEmail $signingEmail "" 16}}</span>
|
||||
<span data-tooltip-content="{{$msgSigningKey}}">{{ctx.AvatarUtils.AvatarByEmail $signingEmail "" 20}}</span>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<span data-tooltip-content="{{$msgReason}}">{{svg "gitea-unlock"}}</span>
|
||||
|
||||
@ -5,11 +5,7 @@
|
||||
{{$index = Eval $index "+" 1}}
|
||||
<div class="flex-text-block" id="{{$tag}}">{{/*singular-commit*/}}
|
||||
<span class="badge badge-commit">{{svg "octicon-git-commit"}}</span>
|
||||
{{if .User}}
|
||||
<a class="avatar" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20}}</a>
|
||||
{{else}}
|
||||
{{ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 20}}
|
||||
{{end}}
|
||||
{{template "repo/commit_coauthor_avatar_stack" dict "AuthorUser" .User "AuthorSignature" .Author "CoAuthors" .CoAuthors}}
|
||||
|
||||
{{$commitBaseLink := printf "%s/commit" $.comment.Issue.PullRequest.BaseRepo.Link}}
|
||||
{{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}}
|
||||
|
||||
@ -41,14 +41,7 @@
|
||||
</span>
|
||||
|
||||
<span class="flex-text-inline tw-text-12">
|
||||
{{if $commit.User}}
|
||||
{{ctx.AvatarUtils.Avatar $commit.User 18}}
|
||||
{{$commit.User.GetShortDisplayNameLinkHTML}}
|
||||
{{else}}
|
||||
{{$gitUserName := $commit.Commit.Author.Name}}
|
||||
{{ctx.AvatarUtils.AvatarByEmail $commit.Commit.Author.Email $gitUserName 18}}
|
||||
{{$gitUserName}}
|
||||
{{end}}
|
||||
{{template "repo/commit_coauthor_avatars" dict "AuthorUser" $commit.User "AuthorSignature" $commit.Commit.Author "CoAuthors" $commit.CoAuthors}}
|
||||
</span>
|
||||
|
||||
<span class="time flex-text-inline">{{DateUtils.FullTime $commit.Date}}</span>
|
||||
|
||||
@ -91,7 +91,7 @@
|
||||
{{range $pushCommit := $push.Commits}}
|
||||
{{$commitLink := printf "%s/commit/%s" $repoLink .Sha1}}
|
||||
<div class="flex-text-block">
|
||||
<img loading="lazy" alt class="ui avatar" src="{{$push.AvatarLink ctx .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16">
|
||||
{{template "repo/commit_coauthor_avatar_stack" dict "AuthorSignature" (dict "Email" .AuthorEmail "Name" .AuthorName) "CoAuthorSignatures" .CoAuthors}}
|
||||
<a class="ui sha label" href="{{$commitLink}}">{{ShortSha .Sha1}}</a>
|
||||
<span class="tw-inline-block tw-truncate">
|
||||
{{ctx.RenderUtils.RenderCommitMessage $pushCommit.Message $repo}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user