Fixes 4762 - Content API for Creating, Updating, Deleting Files (#6314)
This commit is contained in:
parent
059195b127
commit
2262811e40
54 changed files with 4154 additions and 563 deletions
|
@ -659,7 +659,16 @@ func RegisterRoutes(m *macaron.Macaron) {
|
|||
})
|
||||
m.Get("/refs", repo.GetGitAllRefs)
|
||||
m.Get("/refs/*", repo.GetGitRefs)
|
||||
m.Combo("/trees/:sha", context.RepoRef()).Get(repo.GetTree)
|
||||
m.Get("/trees/:sha", context.RepoRef(), repo.GetTree)
|
||||
m.Get("/blobs/:sha", context.RepoRef(), repo.GetBlob)
|
||||
}, reqRepoReader(models.UnitTypeCode))
|
||||
m.Group("/contents", func() {
|
||||
m.Get("/*", repo.GetFileContents)
|
||||
m.Group("/*", func() {
|
||||
m.Post("", bind(api.CreateFileOptions{}), repo.CreateFile)
|
||||
m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile)
|
||||
m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile)
|
||||
}, reqRepoWriter(models.UnitTypeCode), reqToken())
|
||||
}, reqRepoReader(models.UnitTypeCode))
|
||||
}, repoAssignment())
|
||||
})
|
||||
|
|
51
routers/api/v1/repo/blob.go
Normal file
51
routers/api/v1/repo/blob.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/repofiles"
|
||||
)
|
||||
|
||||
// GetBlob get the blob of a repository file.
|
||||
func GetBlob(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/git/blobs/{sha} repository GetBlob
|
||||
// ---
|
||||
// summary: Gets the blob of a repository.
|
||||
// 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: sha
|
||||
// in: path
|
||||
// description: sha of the commit
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/GitBlobResponse"
|
||||
|
||||
sha := ctx.Params("sha")
|
||||
if len(sha) == 0 {
|
||||
ctx.Error(http.StatusBadRequest, "", "sha not provided")
|
||||
return
|
||||
}
|
||||
if blob, err := repofiles.GetBlobBySHA(ctx.Repo.Repository, sha); err != nil {
|
||||
ctx.Error(http.StatusBadRequest, "", err)
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, blob)
|
||||
}
|
||||
}
|
|
@ -6,10 +6,15 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/repofiles"
|
||||
"code.gitea.io/gitea/routers/repo"
|
||||
api "code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// GetRawFile get a file by path on a repository
|
||||
|
@ -48,12 +53,12 @@ func GetRawFile(ctx *context.APIContext) {
|
|||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
ctx.Error(500, "GetBlobByPath", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetBlobByPath", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = repo.ServeBlob(ctx.Context, blob); err != nil {
|
||||
ctx.Error(500, "ServeBlob", err)
|
||||
ctx.Error(http.StatusInternalServerError, "ServeBlob", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +91,7 @@ func GetArchive(ctx *context.APIContext) {
|
|||
repoPath := models.RepoPath(ctx.Params(":username"), ctx.Params(":reponame"))
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
ctx.Error(500, "OpenRepository", err)
|
||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
|
@ -125,7 +130,7 @@ func GetEditorconfig(ctx *context.APIContext) {
|
|||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.Error(500, "GetEditorconfig", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetEditorconfig", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -136,5 +141,264 @@ func GetEditorconfig(ctx *context.APIContext) {
|
|||
ctx.NotFound(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(200, def)
|
||||
ctx.JSON(http.StatusOK, def)
|
||||
}
|
||||
|
||||
// CanWriteFiles returns true if repository is editable and user has proper access level.
|
||||
func CanWriteFiles(r *context.Repository) bool {
|
||||
return r.Permission.CanWrite(models.UnitTypeCode) && !r.Repository.IsMirror && !r.Repository.IsArchived
|
||||
}
|
||||
|
||||
// CanReadFiles returns true if repository is readable and user has proper access level.
|
||||
func CanReadFiles(r *context.Repository) bool {
|
||||
return r.Permission.CanRead(models.UnitTypeCode)
|
||||
}
|
||||
|
||||
// CreateFile handles API call for creating a file
|
||||
func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
|
||||
// ---
|
||||
// summary: Create a file 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: filepath
|
||||
// in: path
|
||||
// description: path of the file to create
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// description: "'content' must be base64 encoded\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'sha' is the SHA for the file that already exists\n\n 'new_branch' (optional) will make a new branch from 'branch' before creating the file"
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateFileOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/FileResponse"
|
||||
|
||||
opts := &repofiles.UpdateRepoFileOptions{
|
||||
Content: apiOpts.Content,
|
||||
IsNewFile: true,
|
||||
Message: apiOpts.Message,
|
||||
TreePath: ctx.Params("*"),
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
Committer: &repofiles.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &repofiles.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
},
|
||||
}
|
||||
if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateFile", err)
|
||||
} else {
|
||||
ctx.JSON(http.StatusCreated, fileResponse)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateFile handles API call for updating a file
|
||||
func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
|
||||
// ---
|
||||
// summary: Update a file 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: filepath
|
||||
// in: path
|
||||
// description: path of the file to update
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// description: "'content' must be base64 encoded\n\n 'sha' is the SHA for the file that already exists\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before updating the file"
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateFileOptions"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/FileResponse"
|
||||
|
||||
opts := &repofiles.UpdateRepoFileOptions{
|
||||
Content: apiOpts.Content,
|
||||
SHA: apiOpts.SHA,
|
||||
IsNewFile: false,
|
||||
Message: apiOpts.Message,
|
||||
FromTreePath: apiOpts.FromPath,
|
||||
TreePath: ctx.Params("*"),
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
Committer: &repofiles.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &repofiles.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
},
|
||||
}
|
||||
|
||||
if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, fileResponse)
|
||||
}
|
||||
}
|
||||
|
||||
// Called from both CreateFile or UpdateFile to handle both
|
||||
func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileOptions) (*api.FileResponse, error) {
|
||||
if !CanWriteFiles(ctx.Repo) {
|
||||
return nil, models.ErrUserDoesNotHaveAccessToRepo{
|
||||
UserID: ctx.User.ID,
|
||||
RepoName: ctx.Repo.Repository.LowerName,
|
||||
}
|
||||
}
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(opts.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.Content = string(content)
|
||||
|
||||
return repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, opts)
|
||||
}
|
||||
|
||||
// DeleteFile Delete a fle in a repository
|
||||
func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
|
||||
// ---
|
||||
// summary: Delete a file 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: filepath
|
||||
// in: path
|
||||
// description: path of the file to delete
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// description: "'sha' is the SHA for the file to be deleted\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before deleting the file"
|
||||
// schema:
|
||||
// "$ref": "#/definitions/DeleteFileOptions"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/FileDeleteResponse"
|
||||
if !CanWriteFiles(ctx.Repo) {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{
|
||||
UserID: ctx.User.ID,
|
||||
RepoName: ctx.Repo.Repository.LowerName,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
opts := &repofiles.DeleteRepoFileOptions{
|
||||
Message: apiOpts.Message,
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
SHA: apiOpts.SHA,
|
||||
TreePath: ctx.Params("*"),
|
||||
Committer: &repofiles.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &repofiles.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
},
|
||||
}
|
||||
|
||||
if fileResponse, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, opts); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteFile", err)
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, fileResponse)
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileContents Get the contents of a fle in a repository
|
||||
func GetFileContents(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetFileContents
|
||||
// ---
|
||||
// summary: Gets the contents of a file or directory in a repository
|
||||
// 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: filepath
|
||||
// in: path
|
||||
// description: path of the file to delete
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: ref
|
||||
// in: query
|
||||
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
|
||||
// required: false
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/FileContentResponse"
|
||||
|
||||
if !CanReadFiles(ctx.Repo) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetFileContents", models.ErrUserDoesNotHaveAccessToRepo{
|
||||
UserID: ctx.User.ID,
|
||||
RepoName: ctx.Repo.Repository.LowerName,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
treePath := ctx.Params("*")
|
||||
ref := ctx.QueryTrim("ref")
|
||||
|
||||
if fileContents, err := repofiles.GetFileContents(ctx.Repo.Repository, treePath, ref); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetFileContents", err)
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, fileContents)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,8 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"code.gitea.io/gitea/modules/repofiles"
|
||||
)
|
||||
|
||||
// GetTree get the tree of a repository.
|
||||
|
@ -55,92 +50,15 @@ func GetTree(ctx *context.APIContext) {
|
|||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/GitTreeResponse"
|
||||
sha := ctx.Params("sha")
|
||||
|
||||
sha := ctx.Params(":sha")
|
||||
if len(sha) == 0 {
|
||||
ctx.Error(400, "", "sha not provided")
|
||||
return
|
||||
}
|
||||
tree := GetTreeBySHA(ctx, sha)
|
||||
if tree != nil {
|
||||
if tree, err := repofiles.GetTreeBySHA(ctx.Repo.Repository, sha, ctx.QueryInt("page"), ctx.QueryInt("per_page"), ctx.QueryBool("recursive")); err != nil {
|
||||
ctx.Error(400, "", err.Error())
|
||||
} else {
|
||||
ctx.JSON(200, tree)
|
||||
} else {
|
||||
ctx.Error(400, "", "sha invalid")
|
||||
}
|
||||
}
|
||||
|
||||
// GetTreeBySHA get the GitTreeResponse of a repository using a sha hash.
|
||||
func GetTreeBySHA(ctx *context.APIContext, sha string) *gitea.GitTreeResponse {
|
||||
gitTree, err := ctx.Repo.GitRepo.GetTree(sha)
|
||||
if err != nil || gitTree == nil {
|
||||
return nil
|
||||
}
|
||||
tree := new(gitea.GitTreeResponse)
|
||||
repoID := strings.TrimRight(setting.AppURL, "/") + "/api/v1/repos/" + ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||
tree.SHA = gitTree.ID.String()
|
||||
tree.URL = repoID + "/git/trees/" + tree.SHA
|
||||
var entries git.Entries
|
||||
if ctx.QueryBool("recursive") {
|
||||
entries, err = gitTree.ListEntriesRecursive()
|
||||
} else {
|
||||
entries, err = gitTree.ListEntries()
|
||||
}
|
||||
if err != nil {
|
||||
return tree
|
||||
}
|
||||
repoIDLen := len(repoID)
|
||||
|
||||
// 51 is len(sha1) + len("/git/blobs/"). 40 + 11.
|
||||
blobURL := make([]byte, repoIDLen+51)
|
||||
copy(blobURL[:], repoID)
|
||||
copy(blobURL[repoIDLen:], "/git/blobs/")
|
||||
|
||||
// 51 is len(sha1) + len("/git/trees/"). 40 + 11.
|
||||
treeURL := make([]byte, repoIDLen+51)
|
||||
copy(treeURL[:], repoID)
|
||||
copy(treeURL[repoIDLen:], "/git/trees/")
|
||||
|
||||
// 40 is the size of the sha1 hash in hexadecimal format.
|
||||
copyPos := len(treeURL) - 40
|
||||
|
||||
page := ctx.QueryInt("page")
|
||||
perPage := ctx.QueryInt("per_page")
|
||||
if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage {
|
||||
perPage = setting.API.DefaultGitTreesPerPage
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
tree.Page = page
|
||||
tree.TotalCount = len(entries)
|
||||
rangeStart := perPage * (page - 1)
|
||||
if rangeStart >= len(entries) {
|
||||
return tree
|
||||
}
|
||||
var rangeEnd int
|
||||
if len(entries) > perPage {
|
||||
tree.Truncated = true
|
||||
}
|
||||
if rangeStart+perPage < len(entries) {
|
||||
rangeEnd = rangeStart + perPage
|
||||
} else {
|
||||
rangeEnd = len(entries)
|
||||
}
|
||||
tree.Entries = make([]gitea.GitEntry, rangeEnd-rangeStart)
|
||||
for e := rangeStart; e < rangeEnd; e++ {
|
||||
i := e - rangeStart
|
||||
tree.Entries[i].Path = entries[e].Name()
|
||||
tree.Entries[i].Mode = fmt.Sprintf("%06x", entries[e].Mode())
|
||||
tree.Entries[i].Type = string(entries[e].Type)
|
||||
tree.Entries[i].Size = entries[e].Size()
|
||||
tree.Entries[i].SHA = entries[e].ID.String()
|
||||
|
||||
if entries[e].IsDir() {
|
||||
copy(treeURL[copyPos:], entries[e].ID.String())
|
||||
tree.Entries[i].URL = string(treeURL[:])
|
||||
} else {
|
||||
copy(blobURL[copyPos:], entries[e].ID.String())
|
||||
tree.Entries[i].URL = string(blobURL[:])
|
||||
}
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
func TestGetTreeBySHA(t *testing.T) {
|
||||
models.PrepareTestEnv(t)
|
||||
sha := "master"
|
||||
ctx := test.MockContext(t, "user2/repo1")
|
||||
ctx.SetParams(":id", "1")
|
||||
ctx.SetParams(":sha", sha)
|
||||
test.LoadRepo(t, ctx, 1)
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
test.LoadGitRepo(t, ctx)
|
||||
|
||||
tree := GetTreeBySHA(&context.APIContext{Context: ctx, Org: nil}, ctx.Params("sha"))
|
||||
expectedTree := &gitea.GitTreeResponse{
|
||||
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
|
||||
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/65f1bf27bc3bf70f64657658635e66094edbcb4d",
|
||||
Entries: []gitea.GitEntry{
|
||||
{
|
||||
Path: "README.md",
|
||||
Mode: "100644",
|
||||
Type: "blob",
|
||||
Size: 30,
|
||||
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
|
||||
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f",
|
||||
},
|
||||
},
|
||||
Truncated: false,
|
||||
Page: 1,
|
||||
TotalCount: 1,
|
||||
}
|
||||
|
||||
assert.EqualValues(t, tree, expectedTree)
|
||||
}
|
|
@ -97,6 +97,7 @@ type swaggerParameterBodies struct {
|
|||
|
||||
// in:body
|
||||
CreateUserOption api.CreateUserOption
|
||||
|
||||
// in:body
|
||||
EditUserOption api.EditUserOption
|
||||
|
||||
|
@ -105,4 +106,13 @@ type swaggerParameterBodies struct {
|
|||
|
||||
// in:body
|
||||
EditAttachmentOptions api.EditAttachmentOptions
|
||||
|
||||
// in:body
|
||||
CreateFileOptions api.CreateFileOptions
|
||||
|
||||
// in:body
|
||||
UpdateFileOptions api.UpdateFileOptions
|
||||
|
||||
// in:body
|
||||
DeleteFileOptions api.DeleteFileOptions
|
||||
}
|
||||
|
|
|
@ -162,9 +162,37 @@ type swaggerGitTreeResponse struct {
|
|||
Body api.GitTreeResponse `json:"body"`
|
||||
}
|
||||
|
||||
// GitBlobResponse
|
||||
// swagger:response GitBlobResponse
|
||||
type swaggerGitBlobResponse struct {
|
||||
//in: body
|
||||
Body api.GitBlobResponse `json:"body"`
|
||||
}
|
||||
|
||||
// Commit
|
||||
// swagger:response Commit
|
||||
type swaggerCommit struct {
|
||||
//in: body
|
||||
Body api.Commit `json:"body"`
|
||||
}
|
||||
|
||||
// FileResponse
|
||||
// swagger:response FileResponse
|
||||
type swaggerFileResponse struct {
|
||||
//in: body
|
||||
Body api.FileResponse `json:"body"`
|
||||
}
|
||||
|
||||
// FileContentResponse
|
||||
// swagger:response FileContentResponse
|
||||
type swaggerFileContentResponse struct {
|
||||
//in: body
|
||||
Body api.FileContentResponse `json:"body"`
|
||||
}
|
||||
|
||||
// FileDeleteResponse
|
||||
// swagger:response FileDeleteResponse
|
||||
type swaggerFileDeleteResponse struct {
|
||||
//in: body
|
||||
Body api.FileDeleteResponse `json:"body"`
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ import (
|
|||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/repofiles"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/uploader"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
|
@ -139,7 +139,7 @@ func editFile(ctx *context.Context, isNewFile bool) {
|
|||
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
||||
}
|
||||
ctx.Data["new_branch_name"] = ""
|
||||
ctx.Data["last_commit"] = ctx.Repo.Commit.ID
|
||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
||||
ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
|
||||
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
|
||||
|
@ -159,39 +159,27 @@ func NewFile(ctx *context.Context) {
|
|||
}
|
||||
|
||||
func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bool) {
|
||||
ctx.Data["PageIsEdit"] = true
|
||||
ctx.Data["IsNewFile"] = isNewFile
|
||||
ctx.Data["RequireHighlightJS"] = true
|
||||
ctx.Data["RequireSimpleMDE"] = true
|
||||
canCommit := renderCommitRights(ctx)
|
||||
|
||||
oldBranchName := ctx.Repo.BranchName
|
||||
branchName := oldBranchName
|
||||
oldTreePath := cleanUploadFileName(ctx.Repo.TreePath)
|
||||
lastCommit := form.LastCommit
|
||||
form.LastCommit = ctx.Repo.Commit.ID.String()
|
||||
|
||||
treeNames, treePaths := getParentTreeFields(form.TreePath)
|
||||
branchName := ctx.Repo.BranchName
|
||||
if form.CommitChoice == frmCommitChoiceNewBranch {
|
||||
branchName = form.NewBranchName
|
||||
}
|
||||
|
||||
form.TreePath = cleanUploadFileName(form.TreePath)
|
||||
if len(form.TreePath) == 0 {
|
||||
ctx.Error(500, "Upload file name is invalid")
|
||||
return
|
||||
}
|
||||
treeNames, treePaths := getParentTreeFields(form.TreePath)
|
||||
|
||||
ctx.Data["PageIsEdit"] = true
|
||||
ctx.Data["IsNewFile"] = isNewFile
|
||||
ctx.Data["RequireHighlightJS"] = true
|
||||
ctx.Data["RequireSimpleMDE"] = true
|
||||
ctx.Data["TreePath"] = form.TreePath
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["TreePaths"] = treePaths
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + branchName
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + ctx.Repo.BranchName
|
||||
ctx.Data["FileContent"] = form.Content
|
||||
ctx.Data["commit_summary"] = form.CommitSummary
|
||||
ctx.Data["commit_message"] = form.CommitMessage
|
||||
ctx.Data["commit_choice"] = form.CommitChoice
|
||||
ctx.Data["new_branch_name"] = branchName
|
||||
ctx.Data["last_commit"] = form.LastCommit
|
||||
ctx.Data["new_branch_name"] = form.NewBranchName
|
||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
||||
ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
|
||||
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
|
||||
|
@ -201,101 +189,16 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
|
|||
return
|
||||
}
|
||||
|
||||
if len(form.TreePath) == 0 {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_cannot_be_empty"), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
|
||||
if oldBranchName != branchName {
|
||||
if _, err := ctx.Repo.Repository.GetBranch(branchName); err == nil {
|
||||
ctx.Data["Err_NewBranchName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
} else if !canCommit {
|
||||
// Cannot commit to a an existing branch if user doesn't have rights
|
||||
if branchName == ctx.Repo.BranchName && !canCommit {
|
||||
ctx.Data["Err_NewBranchName"] = true
|
||||
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
|
||||
var newTreePath string
|
||||
for index, part := range treeNames {
|
||||
newTreePath = path.Join(newTreePath, part)
|
||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
// Means there is no item with that name, so we're good
|
||||
break
|
||||
}
|
||||
|
||||
ctx.ServerError("Repo.Commit.GetTreeEntryByPath", err)
|
||||
return
|
||||
}
|
||||
if index != len(treeNames)-1 {
|
||||
if !entry.IsDir() {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if entry.IsLink() {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", part), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
if entry.IsDir() {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", part), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isNewFile {
|
||||
_, err := ctx.Repo.Commit.GetTreeEntryByPath(oldTreePath)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), tplEditFile, &form)
|
||||
} else {
|
||||
ctx.ServerError("GetTreeEntryByPath", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if lastCommit != ctx.Repo.CommitID {
|
||||
files, err := ctx.Repo.Commit.GetFilesChangedSinceCommit(lastCommit)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetFilesChangedSinceCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file == form.TreePath {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+lastCommit+"..."+ctx.Repo.CommitID), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if oldTreePath != form.TreePath {
|
||||
// We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber.
|
||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(form.TreePath)
|
||||
if err != nil {
|
||||
if !git.IsErrNotExist(err) {
|
||||
ctx.ServerError("GetTreeEntryByPath", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if entry != nil {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// CommitSummary is optional in the web form, if empty, give it a default message based on add or update
|
||||
// `message` will be both the summary and message combined
|
||||
message := strings.TrimSpace(form.CommitSummary)
|
||||
if len(message) == 0 {
|
||||
if isNewFile {
|
||||
|
@ -304,28 +207,74 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
|
|||
message = ctx.Tr("repo.editor.update", form.TreePath)
|
||||
}
|
||||
}
|
||||
|
||||
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
|
||||
if len(form.CommitMessage) > 0 {
|
||||
message += "\n\n" + form.CommitMessage
|
||||
}
|
||||
|
||||
if err := uploader.UpdateRepoFile(ctx.Repo.Repository, ctx.User, &uploader.UpdateRepoFileOptions{
|
||||
LastCommitID: lastCommit,
|
||||
OldBranch: oldBranchName,
|
||||
if _, err := repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.UpdateRepoFileOptions{
|
||||
LastCommitID: form.LastCommit,
|
||||
OldBranch: ctx.Repo.BranchName,
|
||||
NewBranch: branchName,
|
||||
OldTreeName: oldTreePath,
|
||||
NewTreeName: form.TreePath,
|
||||
FromTreePath: ctx.Repo.TreePath,
|
||||
TreePath: form.TreePath,
|
||||
Message: message,
|
||||
Content: strings.Replace(form.Content, "\r", "", -1),
|
||||
IsNewFile: isNewFile,
|
||||
}); err != nil {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_update_file", form.TreePath, err), tplEditFile, &form)
|
||||
return
|
||||
// This is where we handle all the errors thrown by repofiles.CreateOrUpdateRepoFile
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Repo.TreePath), tplEditFile, &form)
|
||||
} else if models.IsErrFilenameInvalid(err) {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplEditFile, &form)
|
||||
} else if models.IsErrFilePathInvalid(err) {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
if fileErr, ok := err.(models.ErrFilePathInvalid); ok {
|
||||
switch fileErr.Type {
|
||||
case git.EntryModeSymlink:
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form)
|
||||
break
|
||||
case git.EntryModeTree:
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
|
||||
break
|
||||
case git.EntryModeBlob:
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
|
||||
break
|
||||
default:
|
||||
ctx.Error(500, err.Error())
|
||||
break
|
||||
}
|
||||
} else {
|
||||
ctx.Error(500, err.Error())
|
||||
}
|
||||
} else if models.IsErrRepoFileAlreadyExists(err) {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form)
|
||||
} else if models.IsErrBranchNotExist(err) {
|
||||
// For when a user adds/updates a file to a branch that no longer exists
|
||||
if branchErr, ok := err.(models.ErrBranchNotExist); ok {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
|
||||
} else {
|
||||
ctx.Error(500, err.Error())
|
||||
}
|
||||
} else if models.IsErrBranchAlreadyExists(err) {
|
||||
// For when a user specifies a new branch that already exists
|
||||
ctx.Data["Err_NewBranchName"] = true
|
||||
if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
|
||||
} else {
|
||||
ctx.Error(500, err.Error())
|
||||
}
|
||||
} else if models.IsErrCommitIDDoesNotMatch(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplEditFile, &form)
|
||||
} else {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_update_file", form.TreePath, err), tplEditFile, &form)
|
||||
}
|
||||
} else {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName + "/" + strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(cleanUploadFileName(form.TreePath)))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
|
||||
}
|
||||
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
|
||||
}
|
||||
|
||||
// EditFilePost response for editing file
|
||||
|
@ -355,7 +304,7 @@ func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
|
|||
return
|
||||
}
|
||||
|
||||
diff, err := uploader.GetDiffPreview(ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
|
||||
diff, err := repofiles.GetDiffPreview(ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
|
||||
if err != nil {
|
||||
ctx.Error(500, "GetDiffPreview: "+err.Error())
|
||||
return
|
||||
|
@ -386,6 +335,7 @@ func DeleteFile(ctx *context.Context) {
|
|||
|
||||
ctx.Data["commit_summary"] = ""
|
||||
ctx.Data["commit_message"] = ""
|
||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
||||
if canCommit {
|
||||
ctx.Data["commit_choice"] = frmCommitChoiceDirect
|
||||
} else {
|
||||
|
@ -398,41 +348,27 @@ func DeleteFile(ctx *context.Context) {
|
|||
|
||||
// DeleteFilePost response for deleting file
|
||||
func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
|
||||
ctx.Data["PageIsDelete"] = true
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
|
||||
ctx.Repo.TreePath = cleanUploadFileName(ctx.Repo.TreePath)
|
||||
if len(ctx.Repo.TreePath) == 0 {
|
||||
ctx.Error(500, "Delete file name is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["TreePath"] = ctx.Repo.TreePath
|
||||
canCommit := renderCommitRights(ctx)
|
||||
|
||||
oldBranchName := ctx.Repo.BranchName
|
||||
branchName := oldBranchName
|
||||
|
||||
branchName := ctx.Repo.BranchName
|
||||
if form.CommitChoice == frmCommitChoiceNewBranch {
|
||||
branchName = form.NewBranchName
|
||||
}
|
||||
|
||||
ctx.Data["PageIsDelete"] = true
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["TreePath"] = ctx.Repo.TreePath
|
||||
ctx.Data["commit_summary"] = form.CommitSummary
|
||||
ctx.Data["commit_message"] = form.CommitMessage
|
||||
ctx.Data["commit_choice"] = form.CommitChoice
|
||||
ctx.Data["new_branch_name"] = branchName
|
||||
ctx.Data["new_branch_name"] = form.NewBranchName
|
||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, tplDeleteFile)
|
||||
return
|
||||
}
|
||||
|
||||
if oldBranchName != branchName {
|
||||
if _, err := ctx.Repo.Repository.GetBranch(branchName); err == nil {
|
||||
ctx.Data["Err_NewBranchName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplDeleteFile, &form)
|
||||
return
|
||||
}
|
||||
} else if !canCommit {
|
||||
if branchName != ctx.Repo.BranchName && !canCommit {
|
||||
ctx.Data["Err_NewBranchName"] = true
|
||||
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplDeleteFile, &form)
|
||||
|
@ -443,25 +379,67 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
|
|||
if len(message) == 0 {
|
||||
message = ctx.Tr("repo.editor.delete", ctx.Repo.TreePath)
|
||||
}
|
||||
|
||||
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
|
||||
if len(form.CommitMessage) > 0 {
|
||||
message += "\n\n" + form.CommitMessage
|
||||
}
|
||||
|
||||
if err := uploader.DeleteRepoFile(ctx.Repo.Repository, ctx.User, &uploader.DeleteRepoFileOptions{
|
||||
LastCommitID: ctx.Repo.CommitID,
|
||||
OldBranch: oldBranchName,
|
||||
if _, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.DeleteRepoFileOptions{
|
||||
LastCommitID: form.LastCommit,
|
||||
OldBranch: ctx.Repo.BranchName,
|
||||
NewBranch: branchName,
|
||||
TreePath: ctx.Repo.TreePath,
|
||||
Message: message,
|
||||
}); err != nil {
|
||||
ctx.ServerError("DeleteRepoFile", err)
|
||||
return
|
||||
// This is where we handle all the errors thrown by repofiles.DeleteRepoFile
|
||||
if git.IsErrNotExist(err) || models.IsErrRepoFileDoesNotExist(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_deleting_no_longer_exists", ctx.Repo.TreePath), tplEditFile, &form)
|
||||
} else if models.IsErrFilenameInvalid(err) {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", ctx.Repo.TreePath), tplEditFile, &form)
|
||||
} else if models.IsErrFilePathInvalid(err) {
|
||||
ctx.Data["Err_TreePath"] = true
|
||||
if fileErr, ok := err.(models.ErrFilePathInvalid); ok {
|
||||
switch fileErr.Type {
|
||||
case git.EntryModeSymlink:
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form)
|
||||
break
|
||||
case git.EntryModeTree:
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
|
||||
break
|
||||
case git.EntryModeBlob:
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
|
||||
break
|
||||
default:
|
||||
ctx.ServerError("DeleteRepoFile", err)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("DeleteRepoFile", err)
|
||||
}
|
||||
} else if models.IsErrBranchNotExist(err) {
|
||||
// For when a user deletes a file to a branch that no longer exists
|
||||
if branchErr, ok := err.(models.ErrBranchNotExist); ok {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
|
||||
} else {
|
||||
ctx.Error(500, err.Error())
|
||||
}
|
||||
} else if models.IsErrBranchAlreadyExists(err) {
|
||||
// For when a user specifies a new branch that already exists
|
||||
if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
|
||||
} else {
|
||||
ctx.Error(500, err.Error())
|
||||
}
|
||||
} else if models.IsErrCommitIDDoesNotMatch(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplEditFile, &form)
|
||||
} else {
|
||||
ctx.ServerError("DeleteRepoFile", err)
|
||||
}
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName))
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName))
|
||||
}
|
||||
|
||||
func renderUploadSettings(ctx *context.Context) {
|
||||
|
@ -584,7 +562,7 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
|
|||
message += "\n\n" + form.CommitMessage
|
||||
}
|
||||
|
||||
if err := uploader.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &uploader.UploadRepoFileOptions{
|
||||
if err := repofiles.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &repofiles.UploadRepoFileOptions{
|
||||
LastCommitID: ctx.Repo.CommitID,
|
||||
OldBranch: oldBranchName,
|
||||
NewBranch: branchName,
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue