code.gitea.io/gitea@v1.19.3/modules/repository/commits.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package repository 5 6 import ( 7 "context" 8 "fmt" 9 "net/url" 10 "time" 11 12 "code.gitea.io/gitea/models/avatars" 13 user_model "code.gitea.io/gitea/models/user" 14 "code.gitea.io/gitea/modules/git" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17 api "code.gitea.io/gitea/modules/structs" 18 ) 19 20 // PushCommit represents a commit in a push operation. 21 type PushCommit struct { 22 Sha1 string 23 Message string 24 AuthorEmail string 25 AuthorName string 26 CommitterEmail string 27 CommitterName string 28 Timestamp time.Time 29 } 30 31 // PushCommits represents list of commits in a push operation. 32 type PushCommits struct { 33 Commits []*PushCommit 34 HeadCommit *PushCommit 35 CompareURL string 36 Len int 37 38 avatars map[string]string 39 emailUsers map[string]*user_model.User 40 } 41 42 // NewPushCommits creates a new PushCommits object. 43 func NewPushCommits() *PushCommits { 44 return &PushCommits{ 45 avatars: make(map[string]string), 46 emailUsers: make(map[string]*user_model.User), 47 } 48 } 49 50 // toAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object. 51 func (pc *PushCommits) toAPIPayloadCommit(ctx context.Context, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) { 52 var err error 53 authorUsername := "" 54 author, ok := pc.emailUsers[commit.AuthorEmail] 55 if !ok { 56 author, err = user_model.GetUserByEmail(ctx, commit.AuthorEmail) 57 if err == nil { 58 authorUsername = author.Name 59 pc.emailUsers[commit.AuthorEmail] = author 60 } 61 } else { 62 authorUsername = author.Name 63 } 64 65 committerUsername := "" 66 committer, ok := pc.emailUsers[commit.CommitterEmail] 67 if !ok { 68 committer, err = user_model.GetUserByEmail(ctx, commit.CommitterEmail) 69 if err == nil { 70 // TODO: check errors other than email not found. 71 committerUsername = committer.Name 72 pc.emailUsers[commit.CommitterEmail] = committer 73 } 74 } else { 75 committerUsername = committer.Name 76 } 77 78 fileStatus, err := git.GetCommitFileStatus(ctx, repoPath, commit.Sha1) 79 if err != nil { 80 return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %w", commit.Sha1, err) 81 } 82 83 return &api.PayloadCommit{ 84 ID: commit.Sha1, 85 Message: commit.Message, 86 URL: fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(commit.Sha1)), 87 Author: &api.PayloadUser{ 88 Name: commit.AuthorName, 89 Email: commit.AuthorEmail, 90 UserName: authorUsername, 91 }, 92 Committer: &api.PayloadUser{ 93 Name: commit.CommitterName, 94 Email: commit.CommitterEmail, 95 UserName: committerUsername, 96 }, 97 Added: fileStatus.Added, 98 Removed: fileStatus.Removed, 99 Modified: fileStatus.Modified, 100 Timestamp: commit.Timestamp, 101 }, nil 102 } 103 104 // ToAPIPayloadCommits converts a PushCommits object to api.PayloadCommit format. 105 // It returns all converted commits and, if provided, the head commit or an error otherwise. 106 func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLink string) ([]*api.PayloadCommit, *api.PayloadCommit, error) { 107 commits := make([]*api.PayloadCommit, len(pc.Commits)) 108 var headCommit *api.PayloadCommit 109 110 if pc.emailUsers == nil { 111 pc.emailUsers = make(map[string]*user_model.User) 112 } 113 for i, commit := range pc.Commits { 114 apiCommit, err := pc.toAPIPayloadCommit(ctx, repoPath, repoLink, commit) 115 if err != nil { 116 return nil, nil, err 117 } 118 119 commits[i] = apiCommit 120 if pc.HeadCommit != nil && pc.HeadCommit.Sha1 == commits[i].ID { 121 headCommit = apiCommit 122 } 123 } 124 if pc.HeadCommit != nil && headCommit == nil { 125 var err error 126 headCommit, err = pc.toAPIPayloadCommit(ctx, repoPath, repoLink, pc.HeadCommit) 127 if err != nil { 128 return nil, nil, err 129 } 130 } 131 return commits, headCommit, nil 132 } 133 134 // AvatarLink tries to match user in database with e-mail 135 // in order to show custom avatar, and falls back to general avatar link. 136 func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string { 137 if pc.avatars == nil { 138 pc.avatars = make(map[string]string) 139 } 140 avatar, ok := pc.avatars[email] 141 if ok { 142 return avatar 143 } 144 145 size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor 146 147 u, ok := pc.emailUsers[email] 148 if !ok { 149 var err error 150 u, err = user_model.GetUserByEmail(ctx, email) 151 if err != nil { 152 pc.avatars[email] = avatars.GenerateEmailAvatarFastLink(ctx, email, size) 153 if !user_model.IsErrUserNotExist(err) { 154 log.Error("GetUserByEmail: %v", err) 155 return "" 156 } 157 } else { 158 pc.emailUsers[email] = u 159 } 160 } 161 if u != nil { 162 pc.avatars[email] = u.AvatarLinkWithSize(ctx, size) 163 } 164 165 return pc.avatars[email] 166 } 167 168 // CommitToPushCommit transforms a git.Commit to PushCommit type. 169 func CommitToPushCommit(commit *git.Commit) *PushCommit { 170 return &PushCommit{ 171 Sha1: commit.ID.String(), 172 Message: commit.Message(), 173 AuthorEmail: commit.Author.Email, 174 AuthorName: commit.Author.Name, 175 CommitterEmail: commit.Committer.Email, 176 CommitterName: commit.Committer.Name, 177 Timestamp: commit.Author.When, 178 } 179 } 180 181 // GitToPushCommits transforms a list of git.Commits to PushCommits type. 182 func GitToPushCommits(gitCommits []*git.Commit) *PushCommits { 183 commits := make([]*PushCommit, 0, len(gitCommits)) 184 for _, commit := range gitCommits { 185 commits = append(commits, CommitToPushCommit(commit)) 186 } 187 return &PushCommits{ 188 Commits: commits, 189 HeadCommit: nil, 190 CompareURL: "", 191 Len: len(commits), 192 avatars: make(map[string]string), 193 emailUsers: make(map[string]*user_model.User), 194 } 195 }