1// Copyright 2020 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package convert
5
6import (
7 "context"
8 "net/url"
9 "time"
10
11 repo_model "forgejo.org/models/repo"
12 user_model "forgejo.org/models/user"
13 "forgejo.org/modules/git"
14 "forgejo.org/modules/log"
15 api "forgejo.org/modules/structs"
16 "forgejo.org/modules/util"
17 ctx "forgejo.org/services/context"
18 "forgejo.org/services/gitdiff"
19)
20
21// ToCommitUser convert a git.Signature to an api.CommitUser
22func ToCommitUser(sig *git.Signature) *api.CommitUser {
23 return &api.CommitUser{
24 Identity: api.Identity{
25 Name: sig.Name,
26 Email: sig.Email,
27 },
28 Date: sig.When.UTC().Format(time.RFC3339),
29 }
30}
31
32// ToCommitMeta convert a git.Tag to an api.CommitMeta
33func ToCommitMeta(repo *repo_model.Repository, tag *git.Tag) *api.CommitMeta {
34 return &api.CommitMeta{
35 SHA: tag.Object.String(),
36 URL: util.URLJoin(repo.APIURL(), "git/commits", tag.ID.String()),
37 Created: tag.Tagger.When,
38 }
39}
40
41// ToPayloadCommit convert a git.Commit to api.PayloadCommit
42func ToPayloadCommit(ctx context.Context, repo *repo_model.Repository, c *git.Commit) *api.PayloadCommit {
43 authorUsername := ""
44 if author, err := user_model.GetUserByEmail(ctx, c.Author.Email); err == nil {
45 authorUsername = author.Name
46 } else if !user_model.IsErrUserNotExist(err) {
47 log.Error("GetUserByEmail: %v", err)
48 }
49
50 committerUsername := ""
51 if committer, err := user_model.GetUserByEmail(ctx, c.Committer.Email); err == nil {
52 committerUsername = committer.Name
53 } else if !user_model.IsErrUserNotExist(err) {
54 log.Error("GetUserByEmail: %v", err)
55 }
56
57 return &api.PayloadCommit{
58 ID: c.ID.String(),
59 Message: c.Message(),
60 URL: util.URLJoin(repo.HTMLURL(), "commit", c.ID.String()),
61 Author: &api.PayloadUser{
62 Name: c.Author.Name,
63 Email: c.Author.Email,
64 UserName: authorUsername,
65 },
66 Committer: &api.PayloadUser{
67 Name: c.Committer.Name,
68 Email: c.Committer.Email,
69 UserName: committerUsername,
70 },
71 Timestamp: c.Author.When,
72 Verification: ToVerification(ctx, c),
73 }
74}
75
76type ToCommitOptions struct {
77 Stat bool
78 Verification bool
79 Files bool
80}
81
82func ParseCommitOptions(ctx *ctx.APIContext) ToCommitOptions {
83 return ToCommitOptions{
84 Stat: ctx.FormString("stat") == "" || ctx.FormBool("stat"),
85 Files: ctx.FormString("files") == "" || ctx.FormBool("files"),
86 Verification: ctx.FormString("verification") == "" || ctx.FormBool("verification"),
87 }
88}
89
90// ToCommit convert a git.Commit to api.Commit
91func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User, opts ToCommitOptions) (*api.Commit, error) {
92 var apiAuthor, apiCommitter *api.User
93
94 // Retrieve author and committer information
95
96 var cacheAuthor *user_model.User
97 var ok bool
98 if userCache == nil {
99 cacheAuthor = (*user_model.User)(nil)
100 ok = false
101 } else {
102 cacheAuthor, ok = userCache[commit.Author.Email]
103 }
104
105 if ok {
106 apiAuthor = ToUser(ctx, cacheAuthor, nil)
107 } else {
108 author, err := user_model.GetUserByEmail(ctx, commit.Author.Email)
109 if err != nil && !user_model.IsErrUserNotExist(err) {
110 return nil, err
111 } else if err == nil {
112 apiAuthor = ToUser(ctx, author, nil)
113 if userCache != nil {
114 userCache[commit.Author.Email] = author
115 }
116 }
117 }
118
119 var cacheCommitter *user_model.User
120 if userCache == nil {
121 cacheCommitter = (*user_model.User)(nil)
122 ok = false
123 } else {
124 cacheCommitter, ok = userCache[commit.Committer.Email]
125 }
126
127 if ok {
128 apiCommitter = ToUser(ctx, cacheCommitter, nil)
129 } else {
130 committer, err := user_model.GetUserByEmail(ctx, commit.Committer.Email)
131 if err != nil && !user_model.IsErrUserNotExist(err) {
132 return nil, err
133 } else if err == nil {
134 apiCommitter = ToUser(ctx, committer, nil)
135 if userCache != nil {
136 userCache[commit.Committer.Email] = committer
137 }
138 }
139 }
140
141 // Retrieve parent(s) of the commit
142 apiParents := make([]*api.CommitMeta, commit.ParentCount())
143 for i := 0; i < commit.ParentCount(); i++ {
144 sha, _ := commit.ParentID(i)
145 apiParents[i] = &api.CommitMeta{
146 URL: repo.APIURL() + "/git/commits/" + url.PathEscape(sha.String()),
147 SHA: sha.String(),
148 }
149 }
150
151 res := &api.Commit{
152 CommitMeta: &api.CommitMeta{
153 URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
154 SHA: commit.ID.String(),
155 Created: commit.Committer.When,
156 },
157 HTMLURL: repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()),
158 RepoCommit: &api.RepoCommit{
159 URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
160 Author: &api.CommitUser{
161 Identity: api.Identity{
162 Name: commit.Author.Name,
163 Email: commit.Author.Email,
164 },
165 Date: commit.Author.When.Format(time.RFC3339),
166 },
167 Committer: &api.CommitUser{
168 Identity: api.Identity{
169 Name: commit.Committer.Name,
170 Email: commit.Committer.Email,
171 },
172 Date: commit.Committer.When.Format(time.RFC3339),
173 },
174 Message: commit.Message(),
175 Tree: &api.CommitMeta{
176 URL: repo.APIURL() + "/git/trees/" + url.PathEscape(commit.ID.String()),
177 SHA: commit.ID.String(),
178 Created: commit.Committer.When,
179 },
180 },
181 Author: apiAuthor,
182 Committer: apiCommitter,
183 Parents: apiParents,
184 }
185
186 // Retrieve verification for commit
187 if opts.Verification {
188 res.RepoCommit.Verification = ToVerification(ctx, commit)
189 }
190
191 // Retrieve files affected by the commit
192 if opts.Files {
193 fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String())
194 if err != nil {
195 return nil, err
196 }
197
198 affectedFileList := make([]*api.CommitAffectedFiles, 0, len(fileStatus.Added)+len(fileStatus.Removed)+len(fileStatus.Modified))
199 for filestatus, files := range map[string][]string{"added": fileStatus.Added, "removed": fileStatus.Removed, "modified": fileStatus.Modified} {
200 for _, filename := range files {
201 affectedFileList = append(affectedFileList, &api.CommitAffectedFiles{
202 Filename: filename,
203 Status: filestatus,
204 })
205 }
206 }
207
208 res.Files = affectedFileList
209 }
210
211 // Get diff stats for commit
212 if opts.Stat {
213 diff, err := gitdiff.GetDiff(ctx, gitRepo, &gitdiff.DiffOptions{
214 AfterCommitID: commit.ID.String(),
215 })
216 if err != nil {
217 return nil, err
218 }
219
220 res.Stats = &api.CommitStats{
221 Total: diff.TotalAddition + diff.TotalDeletion,
222 Additions: diff.TotalAddition,
223 Deletions: diff.TotalDeletion,
224 }
225 }
226
227 return res, nil
228}