mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-27 09:45:45 +03:00
9249c810b8
* Switch code editor to Monaco This switches out CodeMirror for Monaco which is based on the same code base as VS code and should work pretty similar to it. It does add a few async chunks, totalling around 10MB to our build. It currently supports around 65 languages and in the default configuration, each language would emit one ugly [number].js chunk, so I opted to combine them all into a single file for now. CodeMirror is still being used under the hood by SimpleMDE so it can not be removed yet. * inline editorconfig, fix diff, use for markdown, remove more dead code * refactors, remove jquery usage * use tab_width * fix intellisense * rename function for clarity * misc tweaks, enable webpack progress display * only use --progress on dev build * remove useless borders in arc-green * fix typo * remove obsolete comment * small refactor * fix file creation and various refactors * unset useTabStops too when no editorconfig * small refactor * disable webpack's [big] warnings * remove useless await * fix dark theme check * rename chunk to 'monaco' * add to .gitignore and delete webpack dest before build * increase editor height * support more editorconfig properties * remove empty element filter * rename Co-authored-by: John Olheiser <john.olheiser@gmail.com>
783 lines
28 KiB
Go
783 lines
28 KiB
Go
// Copyright 2016 The Gogs 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 (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/models"
|
|
"code.gitea.io/gitea/modules/auth"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/charset"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/repofiles"
|
|
repo_module "code.gitea.io/gitea/modules/repository"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/upload"
|
|
"code.gitea.io/gitea/modules/util"
|
|
"code.gitea.io/gitea/routers/utils"
|
|
)
|
|
|
|
const (
|
|
tplEditFile base.TplName = "repo/editor/edit"
|
|
tplEditDiffPreview base.TplName = "repo/editor/diff_preview"
|
|
tplDeleteFile base.TplName = "repo/editor/delete"
|
|
tplUploadFile base.TplName = "repo/editor/upload"
|
|
|
|
frmCommitChoiceDirect string = "direct"
|
|
frmCommitChoiceNewBranch string = "commit-to-new-branch"
|
|
)
|
|
|
|
func renderCommitRights(ctx *context.Context) bool {
|
|
canCommitToBranch, err := ctx.Repo.CanCommitToBranch(ctx.User)
|
|
if err != nil {
|
|
log.Error("CanCommitToBranch: %v", err)
|
|
}
|
|
ctx.Data["CanCommitToBranch"] = canCommitToBranch
|
|
|
|
return canCommitToBranch.CanCommitToBranch
|
|
}
|
|
|
|
// getParentTreeFields returns list of parent tree names and corresponding tree paths
|
|
// based on given tree path.
|
|
func getParentTreeFields(treePath string) (treeNames []string, treePaths []string) {
|
|
if len(treePath) == 0 {
|
|
return treeNames, treePaths
|
|
}
|
|
|
|
treeNames = strings.Split(treePath, "/")
|
|
treePaths = make([]string, len(treeNames))
|
|
for i := range treeNames {
|
|
treePaths[i] = strings.Join(treeNames[:i+1], "/")
|
|
}
|
|
return treeNames, treePaths
|
|
}
|
|
|
|
func editFile(ctx *context.Context, isNewFile bool) {
|
|
ctx.Data["PageIsEdit"] = true
|
|
ctx.Data["IsNewFile"] = isNewFile
|
|
ctx.Data["RequireHighlightJS"] = true
|
|
ctx.Data["RequireSimpleMDE"] = true
|
|
canCommit := renderCommitRights(ctx)
|
|
|
|
treePath := cleanUploadFileName(ctx.Repo.TreePath)
|
|
if treePath != ctx.Repo.TreePath {
|
|
if isNewFile {
|
|
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_new", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
|
|
} else {
|
|
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_edit", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
|
|
}
|
|
return
|
|
}
|
|
|
|
treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
|
|
|
|
if !isNewFile {
|
|
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
|
if err != nil {
|
|
ctx.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
|
|
return
|
|
}
|
|
|
|
// No way to edit a directory online.
|
|
if entry.IsDir() {
|
|
ctx.NotFound("entry.IsDir", nil)
|
|
return
|
|
}
|
|
|
|
blob := entry.Blob()
|
|
if blob.Size() >= setting.UI.MaxDisplayFileSize {
|
|
ctx.NotFound("blob.Size", err)
|
|
return
|
|
}
|
|
|
|
dataRc, err := blob.DataAsync()
|
|
if err != nil {
|
|
ctx.NotFound("blob.Data", err)
|
|
return
|
|
}
|
|
defer dataRc.Close()
|
|
|
|
ctx.Data["FileSize"] = blob.Size()
|
|
ctx.Data["FileName"] = blob.Name()
|
|
|
|
buf := make([]byte, 1024)
|
|
n, _ := dataRc.Read(buf)
|
|
buf = buf[:n]
|
|
|
|
// Only text file are editable online.
|
|
if !base.IsTextFile(buf) {
|
|
ctx.NotFound("base.IsTextFile", nil)
|
|
return
|
|
}
|
|
|
|
d, _ := ioutil.ReadAll(dataRc)
|
|
buf = append(buf, d...)
|
|
if content, err := charset.ToUTF8WithErr(buf); err != nil {
|
|
log.Error("ToUTF8WithErr: %v", err)
|
|
ctx.Data["FileContent"] = string(buf)
|
|
} else {
|
|
ctx.Data["FileContent"] = content
|
|
}
|
|
} else {
|
|
treeNames = append(treeNames, "") // Append empty string to allow user name the new file.
|
|
}
|
|
|
|
ctx.Data["TreeNames"] = treeNames
|
|
ctx.Data["TreePaths"] = treePaths
|
|
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
|
ctx.Data["commit_summary"] = ""
|
|
ctx.Data["commit_message"] = ""
|
|
if canCommit {
|
|
ctx.Data["commit_choice"] = frmCommitChoiceDirect
|
|
} else {
|
|
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
|
}
|
|
ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
|
|
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, ",")
|
|
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, treePath)
|
|
|
|
ctx.HTML(200, tplEditFile)
|
|
}
|
|
|
|
// GetEditorConfig returns a editorconfig JSON string for given treePath or "null"
|
|
func GetEditorConfig(ctx *context.Context, treePath string) string {
|
|
ec, err := ctx.Repo.GetEditorconfig()
|
|
if err == nil {
|
|
def, err := ec.GetDefinitionForFilename(treePath)
|
|
if err == nil {
|
|
jsonStr, _ := json.Marshal(def)
|
|
return string(jsonStr)
|
|
}
|
|
}
|
|
return "null"
|
|
}
|
|
|
|
// EditFile render edit file page
|
|
func EditFile(ctx *context.Context) {
|
|
editFile(ctx, false)
|
|
}
|
|
|
|
// NewFile render create file page
|
|
func NewFile(ctx *context.Context) {
|
|
editFile(ctx, true)
|
|
}
|
|
|
|
func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bool) {
|
|
canCommit := renderCommitRights(ctx)
|
|
treeNames, treePaths := getParentTreeFields(form.TreePath)
|
|
branchName := ctx.Repo.BranchName
|
|
if form.CommitChoice == frmCommitChoiceNewBranch {
|
|
branchName = form.NewBranchName
|
|
}
|
|
|
|
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/" + 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"] = 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, ",")
|
|
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, form.TreePath)
|
|
|
|
if ctx.HasError() {
|
|
ctx.HTML(200, tplEditFile)
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 {
|
|
message = ctx.Tr("repo.editor.add", form.TreePath)
|
|
} else {
|
|
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 := repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.UpdateRepoFileOptions{
|
|
LastCommitID: form.LastCommit,
|
|
OldBranch: ctx.Repo.BranchName,
|
|
NewBranch: branchName,
|
|
FromTreePath: ctx.Repo.TreePath,
|
|
TreePath: form.TreePath,
|
|
Message: message,
|
|
Content: strings.Replace(form.Content, "\r", "", -1),
|
|
IsNewFile: isNewFile,
|
|
}); err != nil {
|
|
// 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.IsErrLFSFileLocked(err) {
|
|
ctx.Data["Err_TreePath"] = true
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(models.ErrLFSFileLocked).Path, err.(models.ErrLFSFileLocked).UserName), 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)
|
|
case git.EntryModeTree:
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
|
|
case git.EntryModeBlob:
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
|
|
default:
|
|
ctx.Error(500, err.Error())
|
|
}
|
|
} 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 git.IsErrBranchNotExist(err) {
|
|
// For when a user adds/updates a file to a branch that no longer exists
|
|
if branchErr, ok := err.(git.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 if git.IsErrPushOutOfDate(err) {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+form.NewBranchName), tplEditFile, &form)
|
|
} else if git.IsErrPushRejected(err) {
|
|
errPushRej := err.(*git.ErrPushRejected)
|
|
if len(errPushRej.Message) == 0 {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form)
|
|
} else {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)), tplEditFile, &form)
|
|
}
|
|
} else {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_update_file", form.TreePath, utils.SanitizeFlashErrorString(err.Error())), tplEditFile, &form)
|
|
}
|
|
}
|
|
|
|
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
|
|
} else {
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
|
|
}
|
|
}
|
|
|
|
// EditFilePost response for editing file
|
|
func EditFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
|
|
editFilePost(ctx, form, false)
|
|
}
|
|
|
|
// NewFilePost response for creating file
|
|
func NewFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
|
|
editFilePost(ctx, form, true)
|
|
}
|
|
|
|
// DiffPreviewPost render preview diff page
|
|
func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
|
|
treePath := cleanUploadFileName(ctx.Repo.TreePath)
|
|
if len(treePath) == 0 {
|
|
ctx.Error(500, "file name to diff is invalid")
|
|
return
|
|
}
|
|
|
|
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
|
|
if err != nil {
|
|
ctx.Error(500, "GetTreeEntryByPath: "+err.Error())
|
|
return
|
|
} else if entry.IsDir() {
|
|
ctx.Error(422)
|
|
return
|
|
}
|
|
|
|
diff, err := repofiles.GetDiffPreview(ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
|
|
if err != nil {
|
|
ctx.Error(500, "GetDiffPreview: "+err.Error())
|
|
return
|
|
}
|
|
|
|
if diff.NumFiles() == 0 {
|
|
ctx.PlainText(200, []byte(ctx.Tr("repo.editor.no_changes_to_show")))
|
|
return
|
|
}
|
|
ctx.Data["File"] = diff.Files[0]
|
|
|
|
ctx.HTML(200, tplEditDiffPreview)
|
|
}
|
|
|
|
// DeleteFile render delete file page
|
|
func DeleteFile(ctx *context.Context) {
|
|
ctx.Data["PageIsDelete"] = true
|
|
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
|
treePath := cleanUploadFileName(ctx.Repo.TreePath)
|
|
|
|
if treePath != ctx.Repo.TreePath {
|
|
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_delete", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
|
|
return
|
|
}
|
|
|
|
ctx.Data["TreePath"] = treePath
|
|
canCommit := renderCommitRights(ctx)
|
|
|
|
ctx.Data["commit_summary"] = ""
|
|
ctx.Data["commit_message"] = ""
|
|
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
|
if canCommit {
|
|
ctx.Data["commit_choice"] = frmCommitChoiceDirect
|
|
} else {
|
|
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
|
}
|
|
ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
|
|
|
|
ctx.HTML(200, tplDeleteFile)
|
|
}
|
|
|
|
// DeleteFilePost response for deleting file
|
|
func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
|
|
canCommit := renderCommitRights(ctx)
|
|
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"] = form.NewBranchName
|
|
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
|
|
|
if ctx.HasError() {
|
|
ctx.HTML(200, tplDeleteFile)
|
|
return
|
|
}
|
|
|
|
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)
|
|
return
|
|
}
|
|
|
|
message := strings.TrimSpace(form.CommitSummary)
|
|
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 := 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 {
|
|
// 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), tplDeleteFile, &form)
|
|
} else if models.IsErrFilenameInvalid(err) {
|
|
ctx.Data["Err_TreePath"] = true
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", ctx.Repo.TreePath), tplDeleteFile, &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), tplDeleteFile, &form)
|
|
case git.EntryModeTree:
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplDeleteFile, &form)
|
|
case git.EntryModeBlob:
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplDeleteFile, &form)
|
|
default:
|
|
ctx.ServerError("DeleteRepoFile", err)
|
|
}
|
|
} else {
|
|
ctx.ServerError("DeleteRepoFile", err)
|
|
}
|
|
} else if git.IsErrBranchNotExist(err) {
|
|
// For when a user deletes a file to a branch that no longer exists
|
|
if branchErr, ok := err.(git.ErrBranchNotExist); ok {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplDeleteFile, &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), tplDeleteFile, &form)
|
|
} else {
|
|
ctx.Error(500, err.Error())
|
|
}
|
|
} else if models.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplDeleteFile, &form)
|
|
} else if git.IsErrPushRejected(err) {
|
|
errPushRej := err.(*git.ErrPushRejected)
|
|
if len(errPushRej.Message) == 0 {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplDeleteFile, &form)
|
|
} else {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)), tplDeleteFile, &form)
|
|
}
|
|
} else {
|
|
ctx.ServerError("DeleteRepoFile", err)
|
|
}
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
|
|
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
|
|
} else {
|
|
treePath := filepath.Dir(ctx.Repo.TreePath)
|
|
if treePath == "." {
|
|
treePath = "" // the file deleted was in the root, so we return the user to the root directory
|
|
}
|
|
if len(treePath) > 0 {
|
|
// Need to get the latest commit since it changed
|
|
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
|
|
if err == nil && commit != nil {
|
|
// We have the comment, now find what directory we can return the user to
|
|
// (must have entries)
|
|
treePath = GetClosestParentWithFiles(treePath, commit)
|
|
} else {
|
|
treePath = "" // otherwise return them to the root of the repo
|
|
}
|
|
}
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(treePath))
|
|
}
|
|
}
|
|
|
|
func renderUploadSettings(ctx *context.Context) {
|
|
ctx.Data["RequireTribute"] = true
|
|
ctx.Data["RequireSimpleMDE"] = true
|
|
ctx.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",")
|
|
ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
|
|
ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
|
|
}
|
|
|
|
// UploadFile render upload file page
|
|
func UploadFile(ctx *context.Context) {
|
|
ctx.Data["PageIsUpload"] = true
|
|
renderUploadSettings(ctx)
|
|
canCommit := renderCommitRights(ctx)
|
|
treePath := cleanUploadFileName(ctx.Repo.TreePath)
|
|
if treePath != ctx.Repo.TreePath {
|
|
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_upload", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
|
|
return
|
|
}
|
|
ctx.Repo.TreePath = treePath
|
|
|
|
treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
|
|
if len(treeNames) == 0 {
|
|
// We must at least have one element for user to input.
|
|
treeNames = []string{""}
|
|
}
|
|
|
|
ctx.Data["TreeNames"] = treeNames
|
|
ctx.Data["TreePaths"] = treePaths
|
|
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
|
ctx.Data["commit_summary"] = ""
|
|
ctx.Data["commit_message"] = ""
|
|
if canCommit {
|
|
ctx.Data["commit_choice"] = frmCommitChoiceDirect
|
|
} else {
|
|
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
|
}
|
|
ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
|
|
|
|
ctx.HTML(200, tplUploadFile)
|
|
}
|
|
|
|
// UploadFilePost response for uploading file
|
|
func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
|
|
ctx.Data["PageIsUpload"] = true
|
|
renderUploadSettings(ctx)
|
|
canCommit := renderCommitRights(ctx)
|
|
|
|
oldBranchName := ctx.Repo.BranchName
|
|
branchName := oldBranchName
|
|
|
|
if form.CommitChoice == frmCommitChoiceNewBranch {
|
|
branchName = form.NewBranchName
|
|
}
|
|
|
|
form.TreePath = cleanUploadFileName(form.TreePath)
|
|
|
|
treeNames, treePaths := getParentTreeFields(form.TreePath)
|
|
if len(treeNames) == 0 {
|
|
// We must at least have one element for user to input.
|
|
treeNames = []string{""}
|
|
}
|
|
|
|
ctx.Data["TreePath"] = form.TreePath
|
|
ctx.Data["TreeNames"] = treeNames
|
|
ctx.Data["TreePaths"] = treePaths
|
|
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + branchName
|
|
ctx.Data["commit_summary"] = form.CommitSummary
|
|
ctx.Data["commit_message"] = form.CommitMessage
|
|
ctx.Data["commit_choice"] = form.CommitChoice
|
|
ctx.Data["new_branch_name"] = branchName
|
|
|
|
if ctx.HasError() {
|
|
ctx.HTML(200, tplUploadFile)
|
|
return
|
|
}
|
|
|
|
if oldBranchName != branchName {
|
|
if _, err := repo_module.GetBranch(ctx.Repo.Repository, branchName); err == nil {
|
|
ctx.Data["Err_NewBranchName"] = true
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplUploadFile, &form)
|
|
return
|
|
}
|
|
} else if !canCommit {
|
|
ctx.Data["Err_NewBranchName"] = true
|
|
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplUploadFile, &form)
|
|
return
|
|
}
|
|
|
|
var newTreePath string
|
|
for _, 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
|
|
}
|
|
|
|
// User can only upload files to a directory.
|
|
if !entry.IsDir() {
|
|
ctx.Data["Err_TreePath"] = true
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form)
|
|
return
|
|
}
|
|
}
|
|
|
|
message := strings.TrimSpace(form.CommitSummary)
|
|
if len(message) == 0 {
|
|
message = ctx.Tr("repo.editor.upload_files_to_dir", form.TreePath)
|
|
}
|
|
|
|
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
|
|
if len(form.CommitMessage) > 0 {
|
|
message += "\n\n" + form.CommitMessage
|
|
}
|
|
|
|
if err := repofiles.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &repofiles.UploadRepoFileOptions{
|
|
LastCommitID: ctx.Repo.CommitID,
|
|
OldBranch: oldBranchName,
|
|
NewBranch: branchName,
|
|
TreePath: form.TreePath,
|
|
Message: message,
|
|
Files: form.Files,
|
|
}); err != nil {
|
|
if models.IsErrLFSFileLocked(err) {
|
|
ctx.Data["Err_TreePath"] = true
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(models.ErrLFSFileLocked).Path, err.(models.ErrLFSFileLocked).UserName), tplUploadFile, &form)
|
|
} else if models.IsErrFilenameInvalid(err) {
|
|
ctx.Data["Err_TreePath"] = true
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplUploadFile, &form)
|
|
} else if models.IsErrFilePathInvalid(err) {
|
|
ctx.Data["Err_TreePath"] = true
|
|
fileErr := err.(models.ErrFilePathInvalid)
|
|
switch fileErr.Type {
|
|
case git.EntryModeSymlink:
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplUploadFile, &form)
|
|
case git.EntryModeTree:
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplUploadFile, &form)
|
|
case git.EntryModeBlob:
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplUploadFile, &form)
|
|
default:
|
|
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), tplUploadFile, &form)
|
|
} else if git.IsErrBranchNotExist(err) {
|
|
branchErr := err.(git.ErrBranchNotExist)
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form)
|
|
} else if models.IsErrBranchAlreadyExists(err) {
|
|
// For when a user specifies a new branch that already exists
|
|
ctx.Data["Err_NewBranchName"] = true
|
|
branchErr := err.(models.ErrBranchAlreadyExists)
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
|
|
} else if git.IsErrPushOutOfDate(err) {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+ctx.Repo.CommitID+"..."+form.NewBranchName), tplUploadFile, &form)
|
|
} else if git.IsErrPushRejected(err) {
|
|
errPushRej := err.(*git.ErrPushRejected)
|
|
if len(errPushRej.Message) == 0 {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplUploadFile, &form)
|
|
} else {
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)), tplUploadFile, &form)
|
|
}
|
|
} else {
|
|
// os.ErrNotExist - upload file missing in the intervening time?!
|
|
log.Error("Error during upload to repo: %-v to filepath: %s on %s from %s: %v", ctx.Repo.Repository, form.TreePath, oldBranchName, form.NewBranchName, err)
|
|
ctx.RenderWithErr(ctx.Tr("repo.editor.unable_to_upload_files", form.TreePath, err), tplUploadFile, &form)
|
|
}
|
|
return
|
|
}
|
|
|
|
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
|
|
} else {
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
|
|
}
|
|
}
|
|
|
|
func cleanUploadFileName(name string) string {
|
|
// Rebase the filename
|
|
name = strings.Trim(path.Clean("/"+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
|
|
}
|
|
|
|
// UploadFileToServer upload file to server file dir not git
|
|
func UploadFileToServer(ctx *context.Context) {
|
|
file, header, err := ctx.Req.FormFile("file")
|
|
if err != nil {
|
|
ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
buf := make([]byte, 1024)
|
|
n, _ := file.Read(buf)
|
|
if n > 0 {
|
|
buf = buf[:n]
|
|
}
|
|
|
|
if len(setting.Repository.Upload.AllowedTypes) > 0 {
|
|
err = upload.VerifyAllowedContentType(buf, setting.Repository.Upload.AllowedTypes)
|
|
if err != nil {
|
|
ctx.Error(400, err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
name := cleanUploadFileName(header.Filename)
|
|
if len(name) == 0 {
|
|
ctx.Error(500, "Upload file name is invalid")
|
|
return
|
|
}
|
|
|
|
upload, err := models.NewUpload(name, buf, file)
|
|
if err != nil {
|
|
ctx.Error(500, fmt.Sprintf("NewUpload: %v", err))
|
|
return
|
|
}
|
|
|
|
log.Trace("New file uploaded: %s", upload.UUID)
|
|
ctx.JSON(200, map[string]string{
|
|
"uuid": upload.UUID,
|
|
})
|
|
}
|
|
|
|
// RemoveUploadFileFromServer remove file from server file dir
|
|
func RemoveUploadFileFromServer(ctx *context.Context, form auth.RemoveUploadFileForm) {
|
|
if len(form.File) == 0 {
|
|
ctx.Status(204)
|
|
return
|
|
}
|
|
|
|
if err := models.DeleteUploadByUUID(form.File); err != nil {
|
|
ctx.Error(500, fmt.Sprintf("DeleteUploadByUUID: %v", err))
|
|
return
|
|
}
|
|
|
|
log.Trace("Upload file removed: %s", form.File)
|
|
ctx.Status(204)
|
|
}
|
|
|
|
// GetUniquePatchBranchName Gets a unique branch name for a new patch branch
|
|
// It will be in the form of <username>-patch-<num> where <num> is the first branch of this format
|
|
// that doesn't already exist. If we exceed 1000 tries or an error is thrown, we just return "" so the user has to
|
|
// type in the branch name themselves (will be an empty field)
|
|
func GetUniquePatchBranchName(ctx *context.Context) string {
|
|
prefix := ctx.User.LowerName + "-patch-"
|
|
for i := 1; i <= 1000; i++ {
|
|
branchName := fmt.Sprintf("%s%d", prefix, i)
|
|
if _, err := repo_module.GetBranch(ctx.Repo.Repository, branchName); err != nil {
|
|
if git.IsErrBranchNotExist(err) {
|
|
return branchName
|
|
}
|
|
log.Error("GetUniquePatchBranchName: %v", err)
|
|
return ""
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetClosestParentWithFiles Recursively gets the path of parent in a tree that has files (used when file in a tree is
|
|
// deleted). Returns "" for the root if no parents other than the root have files. If the given treePath isn't a
|
|
// SubTree or it has no entries, we go up one dir and see if we can return the user to that listing.
|
|
func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
|
|
if len(treePath) == 0 || treePath == "." {
|
|
return ""
|
|
}
|
|
// see if the tree has entries
|
|
if tree, err := commit.SubTree(treePath); err != nil {
|
|
// failed to get tree, going up a dir
|
|
return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
|
|
} else if entries, err := tree.ListEntries(); err != nil || len(entries) == 0 {
|
|
// no files in this dir, going up a dir
|
|
return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
|
|
}
|
|
return treePath
|
|
}
|