1
1
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:
Bryan Mutai
2025-11-30 06:58:15 +03:00
committed by GitHub
parent b54af8811e
commit 7d6861ac54
33 changed files with 671 additions and 489 deletions

View File

@@ -5,6 +5,7 @@ package integration
import (
"context"
"errors"
"strings"
"testing"
@@ -72,7 +73,7 @@ func deleteFileInBranch(user *user_model.User, repo *repo_model.Repository, tree
func createOrReplaceFileInBranch(user *user_model.User, repo *repo_model.Repository, treePath, branchName, content string) error {
_, err := deleteFileInBranch(user, repo, treePath, branchName)
if err != nil && !files_service.IsErrRepoFileDoesNotExist(err) {
if err != nil && !errors.Is(err, util.ErrNotExist) {
return err
}

View File

@@ -5,6 +5,7 @@ package integration
import (
"fmt"
"net/http"
"net/url"
"path"
"strings"
@@ -12,7 +13,6 @@ import (
"time"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
@@ -22,6 +22,7 @@ import (
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func getCreateRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
@@ -93,55 +94,6 @@ func getUpdateRepoFilesRenameOptions(repo *repo_model.Repository) *files_service
}
}
func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "README.md",
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
},
},
LastCommitID: "",
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Deletes README.md",
Author: &files_service.IdentityOptions{
GitUserName: "Bob Smith",
GitUserEmail: "bob@smith.com",
},
Committer: nil,
}
}
func getExpectedFileResponseForRepoFilesDelete() *api.FileResponse {
// Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined
return &api.FileResponse{
Content: nil,
Commit: &api.FileCommitResponse{
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Bob Smith",
Email: "bob@smith.com",
},
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "Bob Smith",
Email: "bob@smith.com",
},
},
Message: "Deletes README.md\n",
},
Verification: &api.PayloadCommitVerification{
Verified: false,
Reason: "gpg.error.not_signed_commit",
Signature: "",
Payload: "",
},
}
}
func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.Commit) *api.FileResponse {
treePath := "new/file.txt"
encoding := "base64"
@@ -578,75 +530,88 @@ func TestChangeRepoFilesWithoutBranchNames(t *testing.T) {
}
func TestChangeRepoFilesForDelete(t *testing.T) {
onGiteaRun(t, testDeleteRepoFiles)
}
onGiteaRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
func testDeleteRepoFiles(t *testing.T, u *url.URL) {
// setup
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getDeleteRepoFilesOptions(repo)
t.Run("Delete README.md by commit", func(t *testing.T) {
urlRaw := "/user2/repo1/raw/branch/branch2/README.md"
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK)
opts := &files_service.ChangeRepoFilesOptions{
OldBranch: "branch2",
LastCommitID: "985f0301dba5e7b34be866819cd15ad3d8f508ee",
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "README.md",
},
},
Message: "test message",
}
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
require.NoError(t, err)
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, "test message\n", filesResponse.Commit.Message)
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound)
})
t.Run("Delete README.md file", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
assert.NoError(t, err)
expectedFileResponse := getExpectedFileResponseForRepoFilesDelete()
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity)
assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity)
assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification)
})
t.Run("Delete README.md with options", func(t *testing.T) {
urlRaw := "/user2/repo1/raw/branch/master/README.md"
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK)
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "README.md",
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
},
},
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Message for deleting README.md",
Author: &files_service.IdentityOptions{GitUserName: "Bob Smith", GitUserEmail: "bob@smith.com"},
}
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
require.NoError(t, err)
require.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, "Message for deleting README.md\n", filesResponse.Commit.Message)
assert.Equal(t, api.Identity{Name: "Bob Smith", Email: "bob@smith.com"}, filesResponse.Commit.Author.Identity)
assert.Equal(t, api.Identity{Name: "Bob Smith", Email: "bob@smith.com"}, filesResponse.Commit.Committer.Identity)
assert.Equal(t, &api.PayloadCommitVerification{Reason: "gpg.error.not_signed_commit"}, filesResponse.Verification)
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound)
})
t.Run("Verify README.md has been deleted", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
assert.Nil(t, filesResponse)
expectedError := "repository file does not exist [path: " + opts.Files[0].TreePath + "]"
assert.EqualError(t, err, expectedError)
})
}
// Test opts with branch names removed, same results
func TestChangeRepoFilesForDeleteWithoutBranchNames(t *testing.T) {
onGiteaRun(t, testDeleteRepoFilesWithoutBranchNames)
}
func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) {
// setup
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getDeleteRepoFilesOptions(repo)
opts.OldBranch = ""
opts.NewBranch = ""
t.Run("Delete README.md without Branch Name", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
assert.NoError(t, err)
expectedFileResponse := getExpectedFileResponseForRepoFilesDelete()
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity)
assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity)
assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification)
t.Run("Delete directory", func(t *testing.T) {
urlRaw := "/user2/repo1/raw/branch/sub-home-md-img-check/docs/README.md"
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusOK)
opts := &files_service.ChangeRepoFilesOptions{
OldBranch: "sub-home-md-img-check",
LastCommitID: "4649299398e4d39a5c09eb4f534df6f1e1eb87cc",
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
TreePath: "docs",
DeleteRecursively: true,
},
},
Message: "test message",
}
filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts)
require.NoError(t, err)
assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0])
assert.Equal(t, "test message\n", filesResponse.Commit.Message)
MakeRequest(t, NewRequest(t, "GET", urlRaw), http.StatusNotFound)
})
})
}