mirror of
https://github.com/go-gitea/gitea
synced 2025-12-07 05:18:29 +00:00
Add "Go to file", "Delete Directory" to repo file list page (#35911)
/claim #35898 Resolves #35898 ### Summary of key changes: 1. Add file name search/Go to file functionality to repo button row. 2. Add backend functionality to delete directory 3. Add context menu for directories with functionality to copy path & delete a directory 4. Move Add/Upload file dropdown to right for parity with Github UI 5. Add tree view to the edit/upload UI --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -42,8 +41,8 @@ type blameRow struct {
|
||||
|
||||
// RefBlame render blame page
|
||||
func RefBlame(ctx *context.Context) {
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["IsBlame"] = true
|
||||
prepareRepoViewContent(ctx, ctx.Repo.RefTypeNameSubURL())
|
||||
|
||||
// Get current entry user currently looking at.
|
||||
if ctx.Repo.TreePath == "" {
|
||||
@@ -56,17 +55,6 @@ func RefBlame(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
treeNames := strings.Split(ctx.Repo.TreePath, "/")
|
||||
var paths []string
|
||||
for i := range treeNames {
|
||||
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
|
||||
}
|
||||
|
||||
ctx.Data["Paths"] = paths
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
|
||||
blob := entry.Blob()
|
||||
fileSize := blob.Size()
|
||||
ctx.Data["FileSize"] = fileSize
|
||||
|
||||
@@ -41,7 +41,12 @@ const (
|
||||
editorCommitChoiceNewBranch string = "commit-to-new-branch"
|
||||
)
|
||||
|
||||
func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions {
|
||||
func prepareEditorPage(ctx *context.Context, editorAction string) *context.CommitFormOptions {
|
||||
prepareHomeTreeSideBarSwitch(ctx)
|
||||
return prepareEditorPageFormOptions(ctx, editorAction)
|
||||
}
|
||||
|
||||
func prepareEditorPageFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions {
|
||||
cleanedTreePath := files_service.CleanGitTreePath(ctx.Repo.TreePath)
|
||||
if cleanedTreePath != ctx.Repo.TreePath {
|
||||
redirectTo := fmt.Sprintf("%s/%s/%s/%s", ctx.Repo.RepoLink, editorAction, util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(cleanedTreePath))
|
||||
@@ -283,7 +288,7 @@ func EditFile(ctx *context.Context) {
|
||||
// on the "New File" page, we should add an empty path field to make end users could input a new name
|
||||
prepareTreePathFieldsAndPaths(ctx, util.Iif(isNewFile, ctx.Repo.TreePath+"/", ctx.Repo.TreePath))
|
||||
|
||||
prepareEditorCommitFormOptions(ctx, editorAction)
|
||||
prepareEditorPage(ctx, editorAction)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -376,15 +381,16 @@ func EditFilePost(ctx *context.Context) {
|
||||
|
||||
// DeleteFile render delete file page
|
||||
func DeleteFile(ctx *context.Context) {
|
||||
prepareEditorCommitFormOptions(ctx, "_delete")
|
||||
prepareEditorPage(ctx, "_delete")
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.Data["PageIsDelete"] = true
|
||||
prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath)
|
||||
ctx.HTML(http.StatusOK, tplDeleteFile)
|
||||
}
|
||||
|
||||
// DeleteFilePost response for deleting file
|
||||
// DeleteFilePost response for deleting file or directory
|
||||
func DeleteFilePost(ctx *context.Context) {
|
||||
parsed := prepareEditorCommitSubmittedForm[*forms.DeleteRepoFileForm](ctx)
|
||||
if ctx.Written() {
|
||||
@@ -392,17 +398,37 @@ func DeleteFilePost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
treePath := ctx.Repo.TreePath
|
||||
_, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
|
||||
if treePath == "" {
|
||||
ctx.JSONError("cannot delete root directory") // it should not happen unless someone is trying to be malicious
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the path is a directory
|
||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
var commitMessage string
|
||||
if entry.IsDir() {
|
||||
commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete_directory", treePath))
|
||||
} else {
|
||||
commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath))
|
||||
}
|
||||
|
||||
_, err = files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
|
||||
LastCommitID: parsed.form.LastCommit,
|
||||
OldBranch: parsed.OldBranchName,
|
||||
NewBranch: parsed.NewBranchName,
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "delete",
|
||||
TreePath: treePath,
|
||||
Operation: "delete",
|
||||
TreePath: treePath,
|
||||
DeleteRecursively: true,
|
||||
},
|
||||
},
|
||||
Message: parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath)),
|
||||
Message: commitMessage,
|
||||
Signoff: parsed.form.Signoff,
|
||||
Author: parsed.GitCommitter,
|
||||
Committer: parsed.GitCommitter,
|
||||
@@ -412,7 +438,11 @@ func DeleteFilePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
|
||||
if entry.IsDir() {
|
||||
ctx.Flash.Success(ctx.Tr("repo.editor.directory_delete_success", treePath))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
|
||||
}
|
||||
redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath)
|
||||
redirectForCommitChoice(ctx, parsed, redirectTreePath)
|
||||
}
|
||||
@@ -420,7 +450,7 @@ func DeleteFilePost(ctx *context.Context) {
|
||||
func UploadFile(ctx *context.Context) {
|
||||
ctx.Data["PageIsUpload"] = true
|
||||
prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath)
|
||||
opts := prepareEditorCommitFormOptions(ctx, "_upload")
|
||||
opts := prepareEditorPage(ctx, "_upload")
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func NewDiffPatch(ctx *context.Context) {
|
||||
prepareEditorCommitFormOptions(ctx, "_diffpatch")
|
||||
prepareEditorPage(ctx, "_diffpatch")
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func CherryPick(ctx *context.Context) {
|
||||
prepareEditorCommitFormOptions(ctx, "_cherrypick")
|
||||
prepareEditorPage(ctx, "_cherrypick")
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
const (
|
||||
tplFindFiles templates.TplName = "repo/find/files"
|
||||
)
|
||||
|
||||
// FindFiles render the page to find repository files
|
||||
func FindFiles(ctx *context.Context) {
|
||||
path := ctx.PathParam("*")
|
||||
ctx.Data["TreeLink"] = ctx.Repo.RepoLink + "/src/" + util.PathEscapeSegments(path)
|
||||
ctx.Data["DataLink"] = ctx.Repo.RepoLink + "/tree-list/" + util.PathEscapeSegments(path)
|
||||
ctx.HTML(http.StatusOK, tplFindFiles)
|
||||
}
|
||||
@@ -245,27 +245,17 @@ func LastCommit(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// The "/lastcommit/" endpoint is used to render the embedded HTML content for the directory file listing with latest commit info
|
||||
// It needs to construct correct links to the file items, but the route only accepts a commit ID, not a full ref name (branch or tag).
|
||||
// So we need to get the ref name from the query parameter "refSubUrl".
|
||||
// TODO: LAST-COMMIT-ASYNC-LOADING: it needs more tests to cover this
|
||||
refSubURL := path.Clean(ctx.FormString("refSubUrl"))
|
||||
prepareRepoViewContent(ctx, util.IfZero(refSubURL, ctx.Repo.RefTypeNameSubURL()))
|
||||
renderDirectoryFiles(ctx, 0)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
var treeNames []string
|
||||
paths := make([]string, 0, 5)
|
||||
if len(ctx.Repo.TreePath) > 0 {
|
||||
treeNames = strings.Split(ctx.Repo.TreePath, "/")
|
||||
for i := range treeNames {
|
||||
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
|
||||
}
|
||||
|
||||
ctx.Data["HasParentPath"] = true
|
||||
if len(paths)-2 >= 0 {
|
||||
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
||||
}
|
||||
}
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
|
||||
ctx.Data["BranchLink"] = branchLink
|
||||
|
||||
ctx.HTML(http.StatusOK, tplRepoViewList)
|
||||
}
|
||||
|
||||
@@ -289,7 +279,9 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.Data["LastCommitLoaderURL"] = ctx.Repo.RepoLink + "/lastcommit/" + url.PathEscape(ctx.Repo.CommitID) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
// TODO: LAST-COMMIT-ASYNC-LOADING: search this keyword to see more details
|
||||
lastCommitLoaderURL := ctx.Repo.RepoLink + "/lastcommit/" + url.PathEscape(ctx.Repo.CommitID) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
ctx.Data["LastCommitLoaderURL"] = lastCommitLoaderURL + "?refSubUrl=" + url.QueryEscape(ctx.Repo.RefTypeNameSubURL())
|
||||
|
||||
// Get current entry user currently looking at.
|
||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
||||
@@ -322,6 +314,21 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
|
||||
ctx.ServerError("GetCommitsInfo", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
{
|
||||
if timeout != 0 && !setting.IsProd && !setting.IsInTesting {
|
||||
log.Debug("first call to get directory file commit info")
|
||||
clearFilesCommitInfo := func() {
|
||||
log.Warn("clear directory file commit info to force async loading on frontend")
|
||||
for i := range files {
|
||||
files[i].Commit = nil
|
||||
}
|
||||
}
|
||||
_ = clearFilesCommitInfo
|
||||
// clearFilesCommitInfo() // TODO: LAST-COMMIT-ASYNC-LOADING: debug the frontend async latest commit info loading, uncomment this line, and it needs more tests
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Files"] = files
|
||||
prepareDirectoryFileIcons(ctx, files)
|
||||
for _, f := range files {
|
||||
@@ -334,16 +341,6 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
|
||||
if !loadLatestCommitData(ctx, latestCommit) {
|
||||
return nil
|
||||
}
|
||||
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
|
||||
treeLink := branchLink
|
||||
|
||||
if len(ctx.Repo.TreePath) > 0 {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
|
||||
ctx.Data["TreeLink"] = treeLink
|
||||
|
||||
return allEntries
|
||||
}
|
||||
|
||||
|
||||
@@ -362,6 +362,32 @@ func redirectFollowSymlink(ctx *context.Context, treePathEntry *git.TreeEntry) b
|
||||
return false
|
||||
}
|
||||
|
||||
func prepareRepoViewContent(ctx *context.Context, refTypeNameSubURL string) {
|
||||
// for: home, file list, file view, blame
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show Upload File button or menu item
|
||||
|
||||
// prepare the tree path navigation
|
||||
var treeNames, paths []string
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + refTypeNameSubURL
|
||||
treeLink := branchLink
|
||||
if ctx.Repo.TreePath != "" {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
treeNames = strings.Split(ctx.Repo.TreePath, "/")
|
||||
for i := range treeNames {
|
||||
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
|
||||
}
|
||||
ctx.Data["HasParentPath"] = true
|
||||
if len(paths)-2 >= 0 {
|
||||
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
||||
}
|
||||
}
|
||||
ctx.Data["Paths"] = paths
|
||||
ctx.Data["TreeLink"] = treeLink
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["BranchLink"] = branchLink
|
||||
}
|
||||
|
||||
// Home render repository home page
|
||||
func Home(ctx *context.Context) {
|
||||
if handleRepoHomeFeed(ctx) {
|
||||
@@ -383,8 +409,7 @@ func Home(ctx *context.Context) {
|
||||
title += ": " + ctx.Repo.Repository.Description
|
||||
}
|
||||
ctx.Data["Title"] = title
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show New File / Upload File buttons
|
||||
prepareRepoViewContent(ctx, ctx.Repo.RefTypeNameSubURL())
|
||||
|
||||
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
||||
// empty or broken repositories need to be handled differently
|
||||
@@ -405,26 +430,6 @@ func Home(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// prepare the tree path
|
||||
var treeNames, paths []string
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
|
||||
treeLink := branchLink
|
||||
if ctx.Repo.TreePath != "" {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
treeNames = strings.Split(ctx.Repo.TreePath, "/")
|
||||
for i := range treeNames {
|
||||
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
|
||||
}
|
||||
ctx.Data["HasParentPath"] = true
|
||||
if len(paths)-2 >= 0 {
|
||||
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
||||
}
|
||||
}
|
||||
ctx.Data["Paths"] = paths
|
||||
ctx.Data["TreeLink"] = treeLink
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["BranchLink"] = branchLink
|
||||
|
||||
// some UI components are only shown when the tree path is root
|
||||
isTreePathRoot := ctx.Repo.TreePath == ""
|
||||
|
||||
@@ -455,7 +460,7 @@ func Home(ctx *context.Context) {
|
||||
|
||||
if isViewHomeOnlyContent(ctx) {
|
||||
ctx.HTML(http.StatusOK, tplRepoViewContent)
|
||||
} else if len(treeNames) != 0 {
|
||||
} else if ctx.Repo.TreePath != "" {
|
||||
ctx.HTML(http.StatusOK, tplRepoView)
|
||||
} else {
|
||||
ctx.HTML(http.StatusOK, tplRepoHome)
|
||||
|
||||
Reference in New Issue
Block a user