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  }