code.gitea.io/gitea@v1.22.3/services/convert/convert.go (about) 1 // Copyright 2015 The Gogs Authors. All rights reserved. 2 // Copyright 2018 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package convert 6 7 import ( 8 "context" 9 "fmt" 10 "strconv" 11 "strings" 12 "time" 13 14 asymkey_model "code.gitea.io/gitea/models/asymkey" 15 "code.gitea.io/gitea/models/auth" 16 git_model "code.gitea.io/gitea/models/git" 17 issues_model "code.gitea.io/gitea/models/issues" 18 "code.gitea.io/gitea/models/organization" 19 "code.gitea.io/gitea/models/perm" 20 access_model "code.gitea.io/gitea/models/perm/access" 21 repo_model "code.gitea.io/gitea/models/repo" 22 "code.gitea.io/gitea/models/unit" 23 user_model "code.gitea.io/gitea/models/user" 24 "code.gitea.io/gitea/modules/container" 25 "code.gitea.io/gitea/modules/git" 26 "code.gitea.io/gitea/modules/log" 27 api "code.gitea.io/gitea/modules/structs" 28 "code.gitea.io/gitea/modules/util" 29 "code.gitea.io/gitea/services/gitdiff" 30 ) 31 32 // ToEmail convert models.EmailAddress to api.Email 33 func ToEmail(email *user_model.EmailAddress) *api.Email { 34 return &api.Email{ 35 Email: email.Email, 36 Verified: email.IsActivated, 37 Primary: email.IsPrimary, 38 } 39 } 40 41 // ToEmail convert models.EmailAddress to api.Email 42 func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email { 43 return &api.Email{ 44 Email: email.Email, 45 Verified: email.IsActivated, 46 Primary: email.IsPrimary, 47 UserID: email.UID, 48 UserName: email.Name, 49 } 50 } 51 52 // ToBranch convert a git.Commit and git.Branch to an api.Branch 53 func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { 54 if bp == nil { 55 var hasPerm bool 56 var canPush bool 57 var err error 58 if user != nil { 59 hasPerm, err = access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite) 60 if err != nil { 61 return nil, err 62 } 63 64 perms, err := access_model.GetUserRepoPermission(ctx, repo, user) 65 if err != nil { 66 return nil, err 67 } 68 canPush = issues_model.CanMaintainerWriteToBranch(ctx, perms, branchName, user) 69 } 70 71 return &api.Branch{ 72 Name: branchName, 73 Commit: ToPayloadCommit(ctx, repo, c), 74 Protected: false, 75 RequiredApprovals: 0, 76 EnableStatusCheck: false, 77 StatusCheckContexts: []string{}, 78 UserCanPush: canPush, 79 UserCanMerge: hasPerm, 80 }, nil 81 } 82 83 branch := &api.Branch{ 84 Name: branchName, 85 Commit: ToPayloadCommit(ctx, repo, c), 86 Protected: true, 87 RequiredApprovals: bp.RequiredApprovals, 88 EnableStatusCheck: bp.EnableStatusCheck, 89 StatusCheckContexts: bp.StatusCheckContexts, 90 } 91 92 if isRepoAdmin { 93 branch.EffectiveBranchProtectionName = bp.RuleName 94 } 95 96 if user != nil { 97 permission, err := access_model.GetUserRepoPermission(ctx, repo, user) 98 if err != nil { 99 return nil, err 100 } 101 bp.Repo = repo 102 branch.UserCanPush = bp.CanUserPush(ctx, user) 103 branch.UserCanMerge = git_model.IsUserMergeWhitelisted(ctx, bp, user.ID, permission) 104 } 105 106 return branch, nil 107 } 108 109 // getWhitelistEntities returns the names of the entities that are in the whitelist 110 func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T, whitelistIDs []int64) []string { 111 whitelistUserIDsSet := container.SetOf(whitelistIDs...) 112 whitelistNames := make([]string, 0) 113 for _, entity := range entities { 114 switch v := any(entity).(type) { 115 case *user_model.User: 116 if whitelistUserIDsSet.Contains(v.ID) { 117 whitelistNames = append(whitelistNames, v.Name) 118 } 119 case *organization.Team: 120 if whitelistUserIDsSet.Contains(v.ID) { 121 whitelistNames = append(whitelistNames, v.Name) 122 } 123 } 124 } 125 126 return whitelistNames 127 } 128 129 // ToBranchProtection convert a ProtectedBranch to api.BranchProtection 130 func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection { 131 readers, err := access_model.GetRepoReaders(ctx, repo) 132 if err != nil { 133 log.Error("GetRepoReaders: %v", err) 134 } 135 136 pushWhitelistUsernames := getWhitelistEntities(readers, bp.WhitelistUserIDs) 137 mergeWhitelistUsernames := getWhitelistEntities(readers, bp.MergeWhitelistUserIDs) 138 approvalsWhitelistUsernames := getWhitelistEntities(readers, bp.ApprovalsWhitelistUserIDs) 139 140 teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead) 141 if err != nil { 142 log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err) 143 } 144 145 pushWhitelistTeams := getWhitelistEntities(teamReaders, bp.WhitelistTeamIDs) 146 mergeWhitelistTeams := getWhitelistEntities(teamReaders, bp.MergeWhitelistTeamIDs) 147 approvalsWhitelistTeams := getWhitelistEntities(teamReaders, bp.ApprovalsWhitelistTeamIDs) 148 149 branchName := "" 150 if !git_model.IsRuleNameSpecial(bp.RuleName) { 151 branchName = bp.RuleName 152 } 153 154 return &api.BranchProtection{ 155 BranchName: branchName, 156 RuleName: bp.RuleName, 157 EnablePush: bp.CanPush, 158 EnablePushWhitelist: bp.EnableWhitelist, 159 PushWhitelistUsernames: pushWhitelistUsernames, 160 PushWhitelistTeams: pushWhitelistTeams, 161 PushWhitelistDeployKeys: bp.WhitelistDeployKeys, 162 EnableMergeWhitelist: bp.EnableMergeWhitelist, 163 MergeWhitelistUsernames: mergeWhitelistUsernames, 164 MergeWhitelistTeams: mergeWhitelistTeams, 165 EnableStatusCheck: bp.EnableStatusCheck, 166 StatusCheckContexts: bp.StatusCheckContexts, 167 RequiredApprovals: bp.RequiredApprovals, 168 EnableApprovalsWhitelist: bp.EnableApprovalsWhitelist, 169 ApprovalsWhitelistUsernames: approvalsWhitelistUsernames, 170 ApprovalsWhitelistTeams: approvalsWhitelistTeams, 171 BlockOnRejectedReviews: bp.BlockOnRejectedReviews, 172 BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests, 173 BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch, 174 DismissStaleApprovals: bp.DismissStaleApprovals, 175 IgnoreStaleApprovals: bp.IgnoreStaleApprovals, 176 RequireSignedCommits: bp.RequireSignedCommits, 177 ProtectedFilePatterns: bp.ProtectedFilePatterns, 178 UnprotectedFilePatterns: bp.UnprotectedFilePatterns, 179 Created: bp.CreatedUnix.AsTime(), 180 Updated: bp.UpdatedUnix.AsTime(), 181 } 182 } 183 184 // ToTag convert a git.Tag to an api.Tag 185 func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag { 186 return &api.Tag{ 187 Name: t.Name, 188 Message: strings.TrimSpace(t.Message), 189 ID: t.ID.String(), 190 Commit: ToCommitMeta(repo, t), 191 ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), 192 TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"), 193 } 194 } 195 196 // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification 197 func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification { 198 verif := asymkey_model.ParseCommitWithSignature(ctx, c) 199 commitVerification := &api.PayloadCommitVerification{ 200 Verified: verif.Verified, 201 Reason: verif.Reason, 202 } 203 if c.Signature != nil { 204 commitVerification.Signature = c.Signature.Signature 205 commitVerification.Payload = c.Signature.Payload 206 } 207 if verif.SigningUser != nil { 208 commitVerification.Signer = &api.PayloadUser{ 209 Name: verif.SigningUser.Name, 210 Email: verif.SigningUser.Email, 211 } 212 } 213 return commitVerification 214 } 215 216 // ToPublicKey convert asymkey_model.PublicKey to api.PublicKey 217 func ToPublicKey(apiLink string, key *asymkey_model.PublicKey) *api.PublicKey { 218 return &api.PublicKey{ 219 ID: key.ID, 220 Key: key.Content, 221 URL: fmt.Sprintf("%s%d", apiLink, key.ID), 222 Title: key.Name, 223 Fingerprint: key.Fingerprint, 224 Created: key.CreatedUnix.AsTime(), 225 } 226 } 227 228 // ToGPGKey converts models.GPGKey to api.GPGKey 229 func ToGPGKey(key *asymkey_model.GPGKey) *api.GPGKey { 230 subkeys := make([]*api.GPGKey, len(key.SubsKey)) 231 for id, k := range key.SubsKey { 232 subkeys[id] = &api.GPGKey{ 233 ID: k.ID, 234 PrimaryKeyID: k.PrimaryKeyID, 235 KeyID: k.KeyID, 236 PublicKey: k.Content, 237 Created: k.CreatedUnix.AsTime(), 238 Expires: k.ExpiredUnix.AsTime(), 239 CanSign: k.CanSign, 240 CanEncryptComms: k.CanEncryptComms, 241 CanEncryptStorage: k.CanEncryptStorage, 242 CanCertify: k.CanSign, 243 Verified: k.Verified, 244 } 245 } 246 emails := make([]*api.GPGKeyEmail, len(key.Emails)) 247 for i, e := range key.Emails { 248 emails[i] = ToGPGKeyEmail(e) 249 } 250 return &api.GPGKey{ 251 ID: key.ID, 252 PrimaryKeyID: key.PrimaryKeyID, 253 KeyID: key.KeyID, 254 PublicKey: key.Content, 255 Created: key.CreatedUnix.AsTime(), 256 Expires: key.ExpiredUnix.AsTime(), 257 Emails: emails, 258 SubsKey: subkeys, 259 CanSign: key.CanSign, 260 CanEncryptComms: key.CanEncryptComms, 261 CanEncryptStorage: key.CanEncryptStorage, 262 CanCertify: key.CanSign, 263 Verified: key.Verified, 264 } 265 } 266 267 // ToGPGKeyEmail convert models.EmailAddress to api.GPGKeyEmail 268 func ToGPGKeyEmail(email *user_model.EmailAddress) *api.GPGKeyEmail { 269 return &api.GPGKeyEmail{ 270 Email: email.Email, 271 Verified: email.IsActivated, 272 } 273 } 274 275 // ToGitHook convert git.Hook to api.GitHook 276 func ToGitHook(h *git.Hook) *api.GitHook { 277 return &api.GitHook{ 278 Name: h.Name(), 279 IsActive: h.IsActive, 280 Content: h.Content, 281 } 282 } 283 284 // ToDeployKey convert asymkey_model.DeployKey to api.DeployKey 285 func ToDeployKey(apiLink string, key *asymkey_model.DeployKey) *api.DeployKey { 286 return &api.DeployKey{ 287 ID: key.ID, 288 KeyID: key.KeyID, 289 Key: key.Content, 290 Fingerprint: key.Fingerprint, 291 URL: fmt.Sprintf("%s%d", apiLink, key.ID), 292 Title: key.Name, 293 Created: key.CreatedUnix.AsTime(), 294 ReadOnly: key.Mode == perm.AccessModeRead, // All deploy keys are read-only. 295 } 296 } 297 298 // ToOrganization convert user_model.User to api.Organization 299 func ToOrganization(ctx context.Context, org *organization.Organization) *api.Organization { 300 return &api.Organization{ 301 ID: org.ID, 302 AvatarURL: org.AsUser().AvatarLink(ctx), 303 Name: org.Name, 304 UserName: org.Name, 305 FullName: org.FullName, 306 Email: org.Email, 307 Description: org.Description, 308 Website: org.Website, 309 Location: org.Location, 310 Visibility: org.Visibility.String(), 311 RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess, 312 } 313 } 314 315 // ToTeam convert models.Team to api.Team 316 func ToTeam(ctx context.Context, team *organization.Team, loadOrg ...bool) (*api.Team, error) { 317 teams, err := ToTeams(ctx, []*organization.Team{team}, len(loadOrg) != 0 && loadOrg[0]) 318 if err != nil || len(teams) == 0 { 319 return nil, err 320 } 321 return teams[0], nil 322 } 323 324 // ToTeams convert models.Team list to api.Team list 325 func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]*api.Team, error) { 326 cache := make(map[int64]*api.Organization) 327 apiTeams := make([]*api.Team, 0, len(teams)) 328 for _, t := range teams { 329 if err := t.LoadUnits(ctx); err != nil { 330 return nil, err 331 } 332 333 apiTeam := &api.Team{ 334 ID: t.ID, 335 Name: t.Name, 336 Description: t.Description, 337 IncludesAllRepositories: t.IncludesAllRepositories, 338 CanCreateOrgRepo: t.CanCreateOrgRepo, 339 Permission: t.AccessMode.ToString(), 340 Units: t.GetUnitNames(), 341 UnitsMap: t.GetUnitsMap(), 342 } 343 344 if loadOrgs { 345 apiOrg, ok := cache[t.OrgID] 346 if !ok { 347 org, err := organization.GetOrgByID(ctx, t.OrgID) 348 if err != nil { 349 return nil, err 350 } 351 apiOrg = ToOrganization(ctx, org) 352 cache[t.OrgID] = apiOrg 353 } 354 apiTeam.Organization = apiOrg 355 } 356 357 apiTeams = append(apiTeams, apiTeam) 358 } 359 return apiTeams, nil 360 } 361 362 // ToAnnotatedTag convert git.Tag to api.AnnotatedTag 363 func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag { 364 return &api.AnnotatedTag{ 365 Tag: t.Name, 366 SHA: t.ID.String(), 367 Object: ToAnnotatedTagObject(repo, c), 368 Message: t.Message, 369 URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()), 370 Tagger: ToCommitUser(t.Tagger), 371 Verification: ToVerification(ctx, c), 372 } 373 } 374 375 // ToAnnotatedTagObject convert a git.Commit to an api.AnnotatedTagObject 376 func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.AnnotatedTagObject { 377 return &api.AnnotatedTagObject{ 378 SHA: commit.ID.String(), 379 Type: string(git.ObjectCommit), 380 URL: util.URLJoin(repo.APIURL(), "git/commits", commit.ID.String()), 381 } 382 } 383 384 // ToTopicResponse convert from models.Topic to api.TopicResponse 385 func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse { 386 return &api.TopicResponse{ 387 ID: topic.ID, 388 Name: topic.Name, 389 RepoCount: topic.RepoCount, 390 Created: topic.CreatedUnix.AsTime(), 391 Updated: topic.UpdatedUnix.AsTime(), 392 } 393 } 394 395 // ToOAuth2Application convert from auth.OAuth2Application to api.OAuth2Application 396 func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application { 397 return &api.OAuth2Application{ 398 ID: app.ID, 399 Name: app.Name, 400 ClientID: app.ClientID, 401 ClientSecret: app.ClientSecret, 402 ConfidentialClient: app.ConfidentialClient, 403 RedirectURIs: app.RedirectURIs, 404 Created: app.CreatedUnix.AsTime(), 405 } 406 } 407 408 // ToLFSLock convert a LFSLock to api.LFSLock 409 func ToLFSLock(ctx context.Context, l *git_model.LFSLock) *api.LFSLock { 410 u, err := user_model.GetUserByID(ctx, l.OwnerID) 411 if err != nil { 412 return nil 413 } 414 return &api.LFSLock{ 415 ID: strconv.FormatInt(l.ID, 10), 416 Path: l.Path, 417 LockedAt: l.Created.Round(time.Second), 418 Owner: &api.LFSLockOwner{ 419 Name: u.Name, 420 }, 421 } 422 } 423 424 // ToChangedFile convert a gitdiff.DiffFile to api.ChangedFile 425 func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit string) *api.ChangedFile { 426 status := "changed" 427 previousFilename := "" 428 if f.IsDeleted { 429 status = "deleted" 430 } else if f.IsCreated { 431 status = "added" 432 } else if f.IsRenamed && f.Type == gitdiff.DiffFileCopy { 433 status = "copied" 434 } else if f.IsRenamed && f.Type == gitdiff.DiffFileRename { 435 status = "renamed" 436 previousFilename = f.OldName 437 } else if f.Addition == 0 && f.Deletion == 0 { 438 status = "unchanged" 439 } 440 441 file := &api.ChangedFile{ 442 Filename: f.GetDiffFileName(), 443 Status: status, 444 Additions: f.Addition, 445 Deletions: f.Deletion, 446 Changes: f.Addition + f.Deletion, 447 PreviousFilename: previousFilename, 448 HTMLURL: fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())), 449 ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit), 450 RawURL: fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())), 451 } 452 453 return file 454 }