mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-27 21:03:56 +01:00
275d4b7e3f
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
170 lines
5.9 KiB
Go
170 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
|
|
}
|