1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-19 16:58:37 +00:00

Allow renaming/moving binary/LFS files in the UI (#34350)

Adds the ability to rename/move binary files like binary blobs or images
and files that are too large in the web ui.
This was purposed in #24722, along with the ability edit images via an
upload of a new image, which I didn't implement here (could be done in a
separate PR).

Binary file content:

![binary](https://github.com/user-attachments/assets/61d9ff71-25d3-4832-9288-452cdefc7283)

File too large:

![toolarge](https://github.com/user-attachments/assets/3b42dbd0-e76a-4c3c-92d2-52ebffedea64)

GitHub does the same (I've copied the text from there):

![gh](https://github.com/user-attachments/assets/e1499813-fb71-4544-9d58-086046a5f13e)
This commit is contained in:
bytedream
2025-06-17 02:15:07 +02:00
committed by GitHub
parent 24ce2058e8
commit 3a37d63d61
12 changed files with 417 additions and 115 deletions

View File

@@ -58,6 +58,50 @@ func getUpdateRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang
}
}
func getUpdateRepoFilesRenameOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
// move normally
{
Operation: "rename",
FromTreePath: "README.md",
TreePath: "README.txt",
SHA: "",
ContentReader: nil,
},
// move from in lfs
{
Operation: "rename",
FromTreePath: "crypt.bin",
TreePath: "crypt1.bin",
SHA: "",
ContentReader: nil,
},
// move from lfs to normal
{
Operation: "rename",
FromTreePath: "jpeg.jpg",
TreePath: "jpeg.jpeg",
SHA: "",
ContentReader: nil,
},
// move from normal to lfs
{
Operation: "rename",
FromTreePath: "CONTRIBUTING.md",
TreePath: "CONTRIBUTING.md.bin",
SHA: "",
ContentReader: nil,
},
},
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
Message: "Rename files",
Author: nil,
Committer: nil,
}
}
func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
@@ -248,6 +292,109 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA
}
}
func getExpectedFileResponseForRepoFilesUpdateRename(commitID, lastCommitSHA string, lastCommitterWhen, lastAuthorWhen time.Time) *api.FilesResponse {
details := []map[string]any{
{
"filename": "README.txt",
"sha": "8276d2a29779af982c0afa976bdb793b52d442a8",
"size": 22,
"content": "IyBBbiBMRlMtZW5hYmxlZCByZXBvCg==",
},
{
"filename": "crypt1.bin",
"sha": "d4a41a0d4db4949e129bd22f871171ea988103ef",
"size": 129,
"content": "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6MmVjY2RiNDM4MjVkMmE0OWQ5OWQ1NDJkYWEyMDA3NWNmZjFkOTdkOWQyMzQ5YTg5NzdlZmU5YzAzNjYxNzM3YwpzaXplIDIwNDgK",
},
{
"filename": "jpeg.jpeg",
"sha": "71911bf48766c7181518c1070911019fbb00b1fc",
"size": 107,
"content": "/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=",
},
{
"filename": "CONTRIBUTING.md.bin",
"sha": "2b6c6c4eaefa24b22f2092c3d54b263ff26feb58",
"size": 127,
"content": "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6N2I2YjJjODhkYmE5Zjc2MGExYTU4NDY5YjY3ZmVlMmI2OThlZjdlOTM5OWM0Y2E0ZjM0YTE0Y2NiZTM5ZjYyMwpzaXplIDI3Cg==",
},
}
var responses []*api.ContentsResponse
for _, detail := range details {
encoding := "base64"
content := detail["content"].(string)
selfURL := setting.AppURL + "api/v1/repos/user2/lfs/contents/" + detail["filename"].(string) + "?ref=master"
htmlURL := setting.AppURL + "user2/lfs/src/branch/master/" + detail["filename"].(string)
gitURL := setting.AppURL + "api/v1/repos/user2/lfs/git/blobs/" + detail["sha"].(string)
downloadURL := setting.AppURL + "user2/lfs/raw/branch/master/" + detail["filename"].(string)
responses = append(responses, &api.ContentsResponse{
Name: detail["filename"].(string),
Path: detail["filename"].(string),
SHA: detail["sha"].(string),
LastCommitSHA: lastCommitSHA,
LastCommitterDate: lastCommitterWhen,
LastAuthorDate: lastAuthorWhen,
Type: "file",
Size: int64(detail["size"].(int)),
Encoding: &encoding,
Content: &content,
URL: &selfURL,
HTMLURL: &htmlURL,
GitURL: &gitURL,
DownloadURL: &downloadURL,
Links: &api.FileLinksResponse{
Self: &selfURL,
GitURL: &gitURL,
HTMLURL: &htmlURL,
},
})
}
return &api.FilesResponse{
Files: responses,
Commit: &api.FileCommitResponse{
CommitMeta: api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/lfs/git/commits/" + commitID,
SHA: commitID,
},
HTMLURL: setting.AppURL + "user2/lfs/commit/" + commitID,
Author: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Parents: []*api.CommitMeta{
{
URL: setting.AppURL + "api/v1/repos/user2/lfs/git/commits/73cf03db6ece34e12bf91e8853dc58f678f2f82d",
SHA: "73cf03db6ece34e12bf91e8853dc58f678f2f82d",
},
},
Message: "Rename files\n",
Tree: &api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/lfs/git/trees/5307376dc3a5557dc1c403c29a8984668ca9ecb5",
SHA: "5307376dc3a5557dc1c403c29a8984668ca9ecb5",
},
},
Verification: &api.PayloadCommitVerification{
Verified: false,
Reason: "gpg.error.not_signed_commit",
Signature: "",
Payload: "",
},
}
}
func TestChangeRepoFilesForCreate(t *testing.T) {
// setup
onGiteaRun(t, func(t *testing.T, u *url.URL) {
@@ -369,6 +516,35 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) {
})
}
func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/lfs")
ctx.SetPathParam("id", "54")
contexttest.LoadRepo(t, ctx, 54)
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 := getUpdateRepoFilesRenameOptions(repo)
// test
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
// asserts
assert.NoError(t, err)
gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch)
lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When)
assert.Equal(t, expectedFileResponse, filesResponse)
})
}
// Test opts with branch names removed, should get same results as above test
func TestChangeRepoFilesWithoutBranchNames(t *testing.T) {
// setup