mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 11:41:32 +01:00 
			
		
		
		
	This PR creates an API endpoint for creating/updating/deleting multiple files in one API call similar to the solution provided by [GitLab](https://docs.gitlab.com/ee/api/commits.html#create-a-commit-with-multiple-files-and-actions). To archive this, the CreateOrUpdateRepoFile and DeleteRepoFIle functions in files service are unified into one function supporting multiple files and actions. Resolves #14619
		
			
				
	
	
		
			171 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2019 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package files
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	repo_model "code.gitea.io/gitea/models/repo"
 | |
| 	user_model "code.gitea.io/gitea/models/user"
 | |
| 	"code.gitea.io/gitea/modules/git"
 | |
| 	api "code.gitea.io/gitea/modules/structs"
 | |
| 	"code.gitea.io/gitea/modules/util"
 | |
| )
 | |
| 
 | |
| func GetFilesResponseFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, branch string, treeNames []string) (*api.FilesResponse, error) {
 | |
| 	files := []*api.ContentsResponse{}
 | |
| 	for _, file := range treeNames {
 | |
| 		fileContents, _ := GetContents(ctx, repo, file, branch, false) // ok if fails, then will be nil
 | |
| 		files = append(files, fileContents)
 | |
| 	}
 | |
| 	fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
 | |
| 	verification := GetPayloadCommitVerification(ctx, commit)
 | |
| 	filesResponse := &api.FilesResponse{
 | |
| 		Files:        files,
 | |
| 		Commit:       fileCommitResponse,
 | |
| 		Verification: verification,
 | |
| 	}
 | |
| 	return filesResponse, nil
 | |
| }
 | |
| 
 | |
| // GetFileResponseFromCommit Constructs a FileResponse from a Commit object
 | |
| func GetFileResponseFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, branch, treeName string) (*api.FileResponse, error) {
 | |
| 	fileContents, _ := GetContents(ctx, repo, treeName, branch, false) // ok if fails, then will be nil
 | |
| 	fileCommitResponse, _ := GetFileCommitResponse(repo, commit)       // ok if fails, then will be nil
 | |
| 	verification := GetPayloadCommitVerification(ctx, commit)
 | |
| 	fileResponse := &api.FileResponse{
 | |
| 		Content:      fileContents,
 | |
| 		Commit:       fileCommitResponse,
 | |
| 		Verification: verification,
 | |
| 	}
 | |
| 	return fileResponse, nil
 | |
| }
 | |
| 
 | |
| // constructs a FileResponse with the file at the index from FilesResponse
 | |
| func GetFileResponseFromFilesResponse(filesResponse *api.FilesResponse, index int) *api.FileResponse {
 | |
| 	content := &api.ContentsResponse{}
 | |
| 	if len(filesResponse.Files) > index {
 | |
| 		content = filesResponse.Files[index]
 | |
| 	}
 | |
| 	fileResponse := &api.FileResponse{
 | |
| 		Content:      content,
 | |
| 		Commit:       filesResponse.Commit,
 | |
| 		Verification: filesResponse.Verification,
 | |
| 	}
 | |
| 	return fileResponse
 | |
| }
 | |
| 
 | |
| // GetFileCommitResponse Constructs a FileCommitResponse from a Commit object
 | |
| func GetFileCommitResponse(repo *repo_model.Repository, commit *git.Commit) (*api.FileCommitResponse, error) {
 | |
| 	if repo == nil {
 | |
| 		return nil, fmt.Errorf("repo cannot be nil")
 | |
| 	}
 | |
| 	if commit == nil {
 | |
| 		return nil, fmt.Errorf("commit cannot be nil")
 | |
| 	}
 | |
| 	commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()))
 | |
| 	commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.Tree.ID.String()))
 | |
| 	parents := make([]*api.CommitMeta, commit.ParentCount())
 | |
| 	for i := 0; i <= commit.ParentCount(); i++ {
 | |
| 		if parent, err := commit.Parent(i); err == nil && parent != nil {
 | |
| 			parentCommitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(parent.ID.String()))
 | |
| 			parents[i] = &api.CommitMeta{
 | |
| 				SHA: parent.ID.String(),
 | |
| 				URL: parentCommitURL.String(),
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	commitHTMLURL, _ := url.Parse(repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()))
 | |
| 	fileCommit := &api.FileCommitResponse{
 | |
| 		CommitMeta: api.CommitMeta{
 | |
| 			SHA: commit.ID.String(),
 | |
| 			URL: commitURL.String(),
 | |
| 		},
 | |
| 		HTMLURL: commitHTMLURL.String(),
 | |
| 		Author: &api.CommitUser{
 | |
| 			Identity: api.Identity{
 | |
| 				Name:  commit.Author.Name,
 | |
| 				Email: commit.Author.Email,
 | |
| 			},
 | |
| 			Date: commit.Author.When.UTC().Format(time.RFC3339),
 | |
| 		},
 | |
| 		Committer: &api.CommitUser{
 | |
| 			Identity: api.Identity{
 | |
| 				Name:  commit.Committer.Name,
 | |
| 				Email: commit.Committer.Email,
 | |
| 			},
 | |
| 			Date: commit.Committer.When.UTC().Format(time.RFC3339),
 | |
| 		},
 | |
| 		Message: commit.Message(),
 | |
| 		Tree: &api.CommitMeta{
 | |
| 			URL: commitTreeURL.String(),
 | |
| 			SHA: commit.Tree.ID.String(),
 | |
| 		},
 | |
| 		Parents: parents,
 | |
| 	}
 | |
| 	return fileCommit, nil
 | |
| }
 | |
| 
 | |
| // GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions
 | |
| func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_model.User) (authorUser, committerUser *user_model.User) {
 | |
| 	// Committer and author are optional. If they are not the doer (not same email address)
 | |
| 	// then we use bogus User objects for them to store their FullName and Email.
 | |
| 	// If only one of the two are provided, we set both of them to it.
 | |
| 	// If neither are provided, both are the doer.
 | |
| 	if committer != nil && committer.Email != "" {
 | |
| 		if doer != nil && strings.EqualFold(doer.Email, committer.Email) {
 | |
| 			committerUser = doer // the committer is the doer, so will use their user object
 | |
| 			if committer.Name != "" {
 | |
| 				committerUser.FullName = committer.Name
 | |
| 			}
 | |
| 		} else {
 | |
| 			committerUser = &user_model.User{
 | |
| 				FullName: committer.Name,
 | |
| 				Email:    committer.Email,
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if author != nil && author.Email != "" {
 | |
| 		if doer != nil && strings.EqualFold(doer.Email, author.Email) {
 | |
| 			authorUser = doer // the author is the doer, so will use their user object
 | |
| 			if authorUser.Name != "" {
 | |
| 				authorUser.FullName = author.Name
 | |
| 			}
 | |
| 		} else {
 | |
| 			authorUser = &user_model.User{
 | |
| 				FullName: author.Name,
 | |
| 				Email:    author.Email,
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if authorUser == nil {
 | |
| 		if committerUser != nil {
 | |
| 			authorUser = committerUser // No valid author was given so use the committer
 | |
| 		} else if doer != nil {
 | |
| 			authorUser = doer // No valid author was given and no valid committer so use the doer
 | |
| 		}
 | |
| 	}
 | |
| 	if committerUser == nil {
 | |
| 		committerUser = authorUser // No valid committer so use the author as the committer (was set to a valid user above)
 | |
| 	}
 | |
| 	return authorUser, committerUser
 | |
| }
 | |
| 
 | |
| // CleanUploadFileName Trims a filename and returns empty string if it is a .git directory
 | |
| func CleanUploadFileName(name string) string {
 | |
| 	// Rebase the filename
 | |
| 	name = util.PathJoinRel(name)
 | |
| 	// Git disallows any filenames to have a .git directory in them.
 | |
| 	for _, part := range strings.Split(name, "/") {
 | |
| 		if strings.ToLower(part) == ".git" {
 | |
| 			return ""
 | |
| 		}
 | |
| 	}
 | |
| 	return name
 | |
| }
 |