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  }