Skip to content

Commit

Permalink
Merge branch 'main' into rmstyle
Browse files Browse the repository at this point in the history
  • Loading branch information
GiteaBot authored May 29, 2023
2 parents 619b42b + 275d4b7 commit 8070934
Show file tree
Hide file tree
Showing 16 changed files with 1,238 additions and 707 deletions.
36 changes: 36 additions & 0 deletions modules/structs/repo_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,35 @@ func (o *UpdateFileOptions) Branch() string {
return o.FileOptions.BranchName
}

// ChangeFileOperation for creating, updating or deleting a file
type ChangeFileOperation struct {
// indicates what to do with the file
// required: true
// enum: create,update,delete
Operation string `json:"operation" binding:"Required"`
// path to the existing or new file
Path string `json:"path" binding:"MaxSize(500)"`
// content must be base64 encoded
// required: true
Content string `json:"content"`
// sha is the SHA for the file that already exists, required for update, delete
SHA string `json:"sha"`
// old path of the file to move
FromPath string `json:"from_path"`
}

// ChangeFilesOptions options for creating, updating or deleting multiple files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type ChangeFilesOptions struct {
FileOptions
Files []*ChangeFileOperation `json:"files"`
}

// Branch returns branch name
func (o *ChangeFilesOptions) Branch() string {
return o.FileOptions.BranchName
}

// FileOptionInterface provides a unified interface for the different file options
type FileOptionInterface interface {
Branch() string
Expand Down Expand Up @@ -126,6 +155,13 @@ type FileResponse struct {
Verification *PayloadCommitVerification `json:"verification"`
}

// FilesResponse contains information about multiple files from a repo
type FilesResponse struct {
Files []*ContentsResponse `json:"files"`
Commit *FileCommitResponse `json:"commit"`
Verification *PayloadCommitVerification `json:"verification"`
}

// FileDeleteResponse contains information about a repo's file that was deleted
type FileDeleteResponse struct {
Content interface{} `json:"content"` // to be set to nil
Expand Down
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,7 @@ func Routes(ctx gocontext.Context) *web.Route {
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(auth_model.AccessTokenScopeRepo), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch)
m.Group("/contents", func() {
m.Get("", repo.GetContentsList)
m.Post("", reqToken(auth_model.AccessTokenScopeRepo), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, repo.ChangeFiles)
m.Get("/*", repo.GetContents)
m.Group("/*", func() {
m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, repo.CreateFile)
Expand Down
195 changes: 167 additions & 28 deletions routers/api/v1/repo/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io"
"net/http"
"path"
"strings"
"time"

"code.gitea.io/gitea/models"
Expand Down Expand Up @@ -407,6 +408,96 @@ func canReadFiles(r *context.Repository) bool {
return r.Permission.CanRead(unit.TypeCode)
}

// ChangeFiles handles API call for creating or updating multiple files
func ChangeFiles(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
// ---
// summary: Create or update multiple files in a repository
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/ChangeFilesOptions"
// responses:
// "201":
// "$ref": "#/responses/FilesResponse"
// "403":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/error"

apiOpts := web.GetForm(ctx).(*api.ChangeFilesOptions)

if apiOpts.BranchName == "" {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}

files := []*files_service.ChangeRepoFile{}
for _, file := range apiOpts.Files {
changeRepoFile := &files_service.ChangeRepoFile{
Operation: file.Operation,
TreePath: file.Path,
FromTreePath: file.FromPath,
Content: file.Content,
SHA: file.SHA,
}
files = append(files, changeRepoFile)
}

opts := &files_service.ChangeRepoFilesOptions{
Files: files,
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
Signoff: apiOpts.Signoff,
}
if opts.Dates.Author.IsZero() {
opts.Dates.Author = time.Now()
}
if opts.Dates.Committer.IsZero() {
opts.Dates.Committer = time.Now()
}

if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, files)
}

if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
} else {
ctx.JSON(http.StatusCreated, filesResponse)
}
}

// CreateFile handles API call for creating a file
func CreateFile(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
Expand Down Expand Up @@ -453,11 +544,15 @@ func CreateFile(ctx *context.APIContext) {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}

opts := &files_service.UpdateRepoFileOptions{
Content: apiOpts.Content,
IsNewFile: true,
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "create",
TreePath: ctx.Params("*"),
Content: apiOpts.Content,
},
},
Message: apiOpts.Message,
TreePath: ctx.Params("*"),
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
Committer: &files_service.IdentityOptions{
Expand All @@ -482,12 +577,13 @@ func CreateFile(ctx *context.APIContext) {
}

if opts.Message == "" {
opts.Message = ctx.Tr("repo.editor.add", opts.TreePath)
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}

if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusCreated, fileResponse)
}
}
Expand Down Expand Up @@ -540,15 +636,19 @@ func UpdateFile(ctx *context.APIContext) {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}

opts := &files_service.UpdateRepoFileOptions{
Content: apiOpts.Content,
SHA: apiOpts.SHA,
IsNewFile: false,
Message: apiOpts.Message,
FromTreePath: apiOpts.FromPath,
TreePath: ctx.Params("*"),
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "update",
Content: apiOpts.Content,
SHA: apiOpts.SHA,
FromTreePath: apiOpts.FromPath,
TreePath: ctx.Params("*"),
},
},
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
Expand All @@ -571,12 +671,13 @@ func UpdateFile(ctx *context.APIContext) {
}

if opts.Message == "" {
opts.Message = ctx.Tr("repo.editor.update", opts.TreePath)
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}

if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse)
}
}
Expand All @@ -600,21 +701,53 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
}

// Called from both CreateFile or UpdateFile to handle both
func createOrUpdateFile(ctx *context.APIContext, opts *files_service.UpdateRepoFileOptions) (*api.FileResponse, error) {
func createOrUpdateFiles(ctx *context.APIContext, opts *files_service.ChangeRepoFilesOptions) (*api.FilesResponse, error) {
if !canWriteFiles(ctx, opts.OldBranch) {
return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.Doer.ID,
RepoName: ctx.Repo.Repository.LowerName,
}
}

content, err := base64.StdEncoding.DecodeString(opts.Content)
if err != nil {
return nil, err
for _, file := range opts.Files {
content, err := base64.StdEncoding.DecodeString(file.Content)
if err != nil {
return nil, err
}
file.Content = string(content)
}
opts.Content = string(content)

return files_service.CreateOrUpdateRepoFile(ctx, ctx.Repo.Repository, ctx.Doer, opts)
return files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts)
}

// format commit message if empty
func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.ChangeRepoFile) string {
var (
createFiles []string
updateFiles []string
deleteFiles []string
)
for _, file := range files {
switch file.Operation {
case "create":
createFiles = append(createFiles, file.TreePath)
case "update":
updateFiles = append(updateFiles, file.TreePath)
case "delete":
deleteFiles = append(deleteFiles, file.TreePath)
}
}
message := ""
if len(createFiles) != 0 {
message += ctx.Tr("repo.editor.add", strings.Join(createFiles, ", ")+"\n")
}
if len(updateFiles) != 0 {
message += ctx.Tr("repo.editor.update", strings.Join(updateFiles, ", ")+"\n")
}
if len(deleteFiles) != 0 {
message += ctx.Tr("repo.editor.delete", strings.Join(deleteFiles, ", "))
}
return strings.Trim(message, "\n")
}

// DeleteFile Delete a file in a repository
Expand Down Expand Up @@ -670,12 +803,17 @@ func DeleteFile(ctx *context.APIContext) {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}

opts := &files_service.DeleteRepoFileOptions{
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
SHA: apiOpts.SHA,
TreePath: ctx.Params("*"),
},
},
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
SHA: apiOpts.SHA,
TreePath: ctx.Params("*"),
Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
Expand All @@ -698,10 +836,10 @@ func DeleteFile(ctx *context.APIContext) {
}

if opts.Message == "" {
opts.Message = ctx.Tr("repo.editor.delete", opts.TreePath)
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}

if fileResponse, err := files_service.DeleteRepoFile(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "DeleteFile", err)
return
Expand All @@ -718,6 +856,7 @@ func DeleteFile(ctx *context.APIContext) {
}
ctx.Error(http.StatusInternalServerError, "DeleteFile", err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse) // FIXME on APIv2: return http.StatusNoContent
}
}
Expand Down
3 changes: 3 additions & 0 deletions routers/api/v1/swagger/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ type swaggerParameterBodies struct {
// in:body
EditAttachmentOptions api.EditAttachmentOptions

// in:body
ChangeFilesOptions api.ChangeFilesOptions

// in:body
CreateFileOptions api.CreateFileOptions

Expand Down
7 changes: 7 additions & 0 deletions routers/api/v1/swagger/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,13 @@ type swaggerFileResponse struct {
Body api.FileResponse `json:"body"`
}

// FilesResponse
// swagger:response FilesResponse
type swaggerFilesResponse struct {
// in: body
Body api.FilesResponse `json:"body"`
}

// ContentsResponse
// swagger:response ContentsResponse
type swaggerContentsResponse struct {
Expand Down
Loading

0 comments on commit 8070934

Please sign in to comment.