Skip to content

Commit 7d6861a

Browse files
brymutwxiaoguang
andauthored
Add "Go to file", "Delete Directory" to repo file list page (go-gitea#35911)
/claim go-gitea#35898 Resolves go-gitea#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>
1 parent b54af88 commit 7d6861a

33 files changed

+669
-487
lines changed

options/locale/locale_en-US.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,8 +1354,11 @@ editor.this_file_locked = File is locked
13541354
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.
13551355
editor.fork_before_edit = You must fork this repository to make or propose changes to this file.
13561356
editor.delete_this_file = Delete File
1357+
editor.delete_this_directory = Delete Directory
13571358
editor.must_have_write_access = You must have write access to make or propose changes to this file.
13581359
editor.file_delete_success = File "%s" has been deleted.
1360+
editor.directory_delete_success = Directory "%s" has been deleted.
1361+
editor.delete_directory = Delete directory '%s'
13591362
editor.name_your_file = Name your file…
13601363
editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field.
13611364
editor.or = or

routers/api/v1/repo/file.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -610,10 +610,6 @@ func handleChangeRepoFilesError(ctx *context.APIContext, err error) {
610610
ctx.APIError(http.StatusUnprocessableEntity, err)
611611
return
612612
}
613-
if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
614-
ctx.APIError(http.StatusNotFound, err)
615-
return
616-
}
617613
if errors.Is(err, util.ErrNotExist) {
618614
ctx.APIError(http.StatusNotFound, err)
619615
return

routers/web/repo/blame.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"net/url"
1111
"path"
1212
"strconv"
13-
"strings"
1413

1514
repo_model "code.gitea.io/gitea/models/repo"
1615
user_model "code.gitea.io/gitea/models/user"
@@ -42,8 +41,8 @@ type blameRow struct {
4241

4342
// RefBlame render blame page
4443
func RefBlame(ctx *context.Context) {
45-
ctx.Data["PageIsViewCode"] = true
4644
ctx.Data["IsBlame"] = true
45+
prepareRepoViewContent(ctx, ctx.Repo.RefTypeNameSubURL())
4746

4847
// Get current entry user currently looking at.
4948
if ctx.Repo.TreePath == "" {
@@ -56,17 +55,6 @@ func RefBlame(ctx *context.Context) {
5655
return
5756
}
5857

59-
treeNames := strings.Split(ctx.Repo.TreePath, "/")
60-
var paths []string
61-
for i := range treeNames {
62-
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
63-
}
64-
65-
ctx.Data["Paths"] = paths
66-
ctx.Data["TreeNames"] = treeNames
67-
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
68-
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
69-
7058
blob := entry.Blob()
7159
fileSize := blob.Size()
7260
ctx.Data["FileSize"] = fileSize

routers/web/repo/editor.go

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ const (
4141
editorCommitChoiceNewBranch string = "commit-to-new-branch"
4242
)
4343

44-
func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions {
44+
func prepareEditorPage(ctx *context.Context, editorAction string) *context.CommitFormOptions {
45+
prepareHomeTreeSideBarSwitch(ctx)
46+
return prepareEditorPageFormOptions(ctx, editorAction)
47+
}
48+
49+
func prepareEditorPageFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions {
4550
cleanedTreePath := files_service.CleanGitTreePath(ctx.Repo.TreePath)
4651
if cleanedTreePath != ctx.Repo.TreePath {
4752
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) {
283288
// on the "New File" page, we should add an empty path field to make end users could input a new name
284289
prepareTreePathFieldsAndPaths(ctx, util.Iif(isNewFile, ctx.Repo.TreePath+"/", ctx.Repo.TreePath))
285290

286-
prepareEditorCommitFormOptions(ctx, editorAction)
291+
prepareEditorPage(ctx, editorAction)
287292
if ctx.Written() {
288293
return
289294
}
@@ -376,33 +381,54 @@ func EditFilePost(ctx *context.Context) {
376381

377382
// DeleteFile render delete file page
378383
func DeleteFile(ctx *context.Context) {
379-
prepareEditorCommitFormOptions(ctx, "_delete")
384+
prepareEditorPage(ctx, "_delete")
380385
if ctx.Written() {
381386
return
382387
}
383388
ctx.Data["PageIsDelete"] = true
389+
prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath)
384390
ctx.HTML(http.StatusOK, tplDeleteFile)
385391
}
386392

387-
// DeleteFilePost response for deleting file
393+
// DeleteFilePost response for deleting file or directory
388394
func DeleteFilePost(ctx *context.Context) {
389395
parsed := prepareEditorCommitSubmittedForm[*forms.DeleteRepoFileForm](ctx)
390396
if ctx.Written() {
391397
return
392398
}
393399

394400
treePath := ctx.Repo.TreePath
395-
_, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
401+
if treePath == "" {
402+
ctx.JSONError("cannot delete root directory") // it should not happen unless someone is trying to be malicious
403+
return
404+
}
405+
406+
// Check if the path is a directory
407+
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
408+
if err != nil {
409+
ctx.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
410+
return
411+
}
412+
413+
var commitMessage string
414+
if entry.IsDir() {
415+
commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete_directory", treePath))
416+
} else {
417+
commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath))
418+
}
419+
420+
_, err = files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
396421
LastCommitID: parsed.form.LastCommit,
397422
OldBranch: parsed.OldBranchName,
398423
NewBranch: parsed.NewBranchName,
399424
Files: []*files_service.ChangeRepoFile{
400425
{
401-
Operation: "delete",
402-
TreePath: treePath,
426+
Operation: "delete",
427+
TreePath: treePath,
428+
DeleteRecursively: true,
403429
},
404430
},
405-
Message: parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath)),
431+
Message: commitMessage,
406432
Signoff: parsed.form.Signoff,
407433
Author: parsed.GitCommitter,
408434
Committer: parsed.GitCommitter,
@@ -412,15 +438,19 @@ func DeleteFilePost(ctx *context.Context) {
412438
return
413439
}
414440

415-
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
441+
if entry.IsDir() {
442+
ctx.Flash.Success(ctx.Tr("repo.editor.directory_delete_success", treePath))
443+
} else {
444+
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
445+
}
416446
redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath)
417447
redirectForCommitChoice(ctx, parsed, redirectTreePath)
418448
}
419449

420450
func UploadFile(ctx *context.Context) {
421451
ctx.Data["PageIsUpload"] = true
422452
prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath)
423-
opts := prepareEditorCommitFormOptions(ctx, "_upload")
453+
opts := prepareEditorPage(ctx, "_upload")
424454
if ctx.Written() {
425455
return
426456
}

routers/web/repo/editor_apply_patch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
)
1515

1616
func NewDiffPatch(ctx *context.Context) {
17-
prepareEditorCommitFormOptions(ctx, "_diffpatch")
17+
prepareEditorPage(ctx, "_diffpatch")
1818
if ctx.Written() {
1919
return
2020
}

routers/web/repo/editor_cherry_pick.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
)
1717

1818
func CherryPick(ctx *context.Context) {
19-
prepareEditorCommitFormOptions(ctx, "_cherrypick")
19+
prepareEditorPage(ctx, "_cherrypick")
2020
if ctx.Written() {
2121
return
2222
}

routers/web/repo/find.go

Lines changed: 0 additions & 24 deletions
This file was deleted.

routers/web/repo/view.go

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -245,27 +245,17 @@ func LastCommit(ctx *context.Context) {
245245
return
246246
}
247247

248+
// The "/lastcommit/" endpoint is used to render the embedded HTML content for the directory file listing with latest commit info
249+
// 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).
250+
// So we need to get the ref name from the query parameter "refSubUrl".
251+
// TODO: LAST-COMMIT-ASYNC-LOADING: it needs more tests to cover this
252+
refSubURL := path.Clean(ctx.FormString("refSubUrl"))
253+
prepareRepoViewContent(ctx, util.IfZero(refSubURL, ctx.Repo.RefTypeNameSubURL()))
248254
renderDirectoryFiles(ctx, 0)
249255
if ctx.Written() {
250256
return
251257
}
252258

253-
var treeNames []string
254-
paths := make([]string, 0, 5)
255-
if len(ctx.Repo.TreePath) > 0 {
256-
treeNames = strings.Split(ctx.Repo.TreePath, "/")
257-
for i := range treeNames {
258-
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
259-
}
260-
261-
ctx.Data["HasParentPath"] = true
262-
if len(paths)-2 >= 0 {
263-
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
264-
}
265-
}
266-
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
267-
ctx.Data["BranchLink"] = branchLink
268-
269259
ctx.HTML(http.StatusOK, tplRepoViewList)
270260
}
271261

@@ -289,7 +279,9 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
289279
return nil
290280
}
291281

292-
ctx.Data["LastCommitLoaderURL"] = ctx.Repo.RepoLink + "/lastcommit/" + url.PathEscape(ctx.Repo.CommitID) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
282+
// TODO: LAST-COMMIT-ASYNC-LOADING: search this keyword to see more details
283+
lastCommitLoaderURL := ctx.Repo.RepoLink + "/lastcommit/" + url.PathEscape(ctx.Repo.CommitID) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
284+
ctx.Data["LastCommitLoaderURL"] = lastCommitLoaderURL + "?refSubUrl=" + url.QueryEscape(ctx.Repo.RefTypeNameSubURL())
293285

294286
// Get current entry user currently looking at.
295287
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
@@ -322,6 +314,21 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
322314
ctx.ServerError("GetCommitsInfo", err)
323315
return nil
324316
}
317+
318+
{
319+
if timeout != 0 && !setting.IsProd && !setting.IsInTesting {
320+
log.Debug("first call to get directory file commit info")
321+
clearFilesCommitInfo := func() {
322+
log.Warn("clear directory file commit info to force async loading on frontend")
323+
for i := range files {
324+
files[i].Commit = nil
325+
}
326+
}
327+
_ = clearFilesCommitInfo
328+
// clearFilesCommitInfo() // TODO: LAST-COMMIT-ASYNC-LOADING: debug the frontend async latest commit info loading, uncomment this line, and it needs more tests
329+
}
330+
}
331+
325332
ctx.Data["Files"] = files
326333
prepareDirectoryFileIcons(ctx, files)
327334
for _, f := range files {
@@ -334,16 +341,6 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
334341
if !loadLatestCommitData(ctx, latestCommit) {
335342
return nil
336343
}
337-
338-
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
339-
treeLink := branchLink
340-
341-
if len(ctx.Repo.TreePath) > 0 {
342-
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
343-
}
344-
345-
ctx.Data["TreeLink"] = treeLink
346-
347344
return allEntries
348345
}
349346

routers/web/repo/view_home.go

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,32 @@ func redirectFollowSymlink(ctx *context.Context, treePathEntry *git.TreeEntry) b
362362
return false
363363
}
364364

365+
func prepareRepoViewContent(ctx *context.Context, refTypeNameSubURL string) {
366+
// for: home, file list, file view, blame
367+
ctx.Data["PageIsViewCode"] = true
368+
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show Upload File button or menu item
369+
370+
// prepare the tree path navigation
371+
var treeNames, paths []string
372+
branchLink := ctx.Repo.RepoLink + "/src/" + refTypeNameSubURL
373+
treeLink := branchLink
374+
if ctx.Repo.TreePath != "" {
375+
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
376+
treeNames = strings.Split(ctx.Repo.TreePath, "/")
377+
for i := range treeNames {
378+
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
379+
}
380+
ctx.Data["HasParentPath"] = true
381+
if len(paths)-2 >= 0 {
382+
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
383+
}
384+
}
385+
ctx.Data["Paths"] = paths
386+
ctx.Data["TreeLink"] = treeLink
387+
ctx.Data["TreeNames"] = treeNames
388+
ctx.Data["BranchLink"] = branchLink
389+
}
390+
365391
// Home render repository home page
366392
func Home(ctx *context.Context) {
367393
if handleRepoHomeFeed(ctx) {
@@ -383,8 +409,7 @@ func Home(ctx *context.Context) {
383409
title += ": " + ctx.Repo.Repository.Description
384410
}
385411
ctx.Data["Title"] = title
386-
ctx.Data["PageIsViewCode"] = true
387-
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show New File / Upload File buttons
412+
prepareRepoViewContent(ctx, ctx.Repo.RefTypeNameSubURL())
388413

389414
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
390415
// empty or broken repositories need to be handled differently
@@ -405,26 +430,6 @@ func Home(ctx *context.Context) {
405430
return
406431
}
407432

408-
// prepare the tree path
409-
var treeNames, paths []string
410-
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
411-
treeLink := branchLink
412-
if ctx.Repo.TreePath != "" {
413-
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
414-
treeNames = strings.Split(ctx.Repo.TreePath, "/")
415-
for i := range treeNames {
416-
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
417-
}
418-
ctx.Data["HasParentPath"] = true
419-
if len(paths)-2 >= 0 {
420-
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
421-
}
422-
}
423-
ctx.Data["Paths"] = paths
424-
ctx.Data["TreeLink"] = treeLink
425-
ctx.Data["TreeNames"] = treeNames
426-
ctx.Data["BranchLink"] = branchLink
427-
428433
// some UI components are only shown when the tree path is root
429434
isTreePathRoot := ctx.Repo.TreePath == ""
430435

@@ -455,7 +460,7 @@ func Home(ctx *context.Context) {
455460

456461
if isViewHomeOnlyContent(ctx) {
457462
ctx.HTML(http.StatusOK, tplRepoViewContent)
458-
} else if len(treeNames) != 0 {
463+
} else if ctx.Repo.TreePath != "" {
459464
ctx.HTML(http.StatusOK, tplRepoView)
460465
} else {
461466
ctx.HTML(http.StatusOK, tplRepoHome)

routers/web/web.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,6 @@ func registerWebRoutes(m *web.Router) {
11841184
m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup)
11851185

11861186
m.Group("/{username}/{reponame}", func() {
1187-
m.Get("/find/*", repo.FindFiles)
11881187
m.Group("/tree-list", func() {
11891188
m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.TreeList)
11901189
m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeList)

0 commit comments

Comments
 (0)