-
-
+ {{if not .NotEditableReason}}
+
+
-
- {{ctx.Locale.Tr "loading"}}
+
+
+
+ {{ctx.Locale.Tr "loading"}}
+
+
-
+ {{else}}
+
+
+
{{.NotEditableReason}}
+
{{ctx.Locale.Tr "repo.editor.file_not_editable_hint"}}
-
+ {{end}}
{{template "repo/editor/commit_form" .}}
diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go
index ce55a2f94347a..4678e52a9c5e1 100644
--- a/tests/integration/repofiles_change_test.go
+++ b/tests/integration/repofiles_change_test.go
@@ -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
diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts
index 0f77508f70d8a..acf4127399513 100644
--- a/web_src/js/features/repo-editor.ts
+++ b/web_src/js/features/repo-editor.ts
@@ -141,38 +141,39 @@ export function initRepoEditor() {
}
});
+ const elForm = document.querySelector
('.repository.editor .edit.form');
+
+ // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
+ // to enable or disable the commit button
+ const commitButton = document.querySelector('#commit-button');
+ const dirtyFileClass = 'dirty-file';
+
+ // Enabling the button at the start if the page has posted
+ if (document.querySelector('input[name="page_has_posted"]')?.value === 'true') {
+ commitButton.disabled = false;
+ }
+
+ // Registering a custom listener for the file path and the file content
+ // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
+ applyAreYouSure(elForm, {
+ silent: true,
+ dirtyClass: dirtyFileClass,
+ fieldSelector: ':input:not(.commit-form-wrapper :input)',
+ change($form: any) {
+ const dirty = $form[0]?.classList.contains(dirtyFileClass);
+ commitButton.disabled = !dirty;
+ },
+ });
+
// on the upload page, there is no editor(textarea)
const editArea = document.querySelector('.page-content.repository.editor textarea#edit_area');
if (!editArea) return;
- const elForm = document.querySelector('.repository.editor .edit.form');
initEditPreviewTab(elForm);
(async () => {
const editor = await createCodeEditor(editArea, filenameInput);
- // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
- // to enable or disable the commit button
- const commitButton = document.querySelector('#commit-button');
- const dirtyFileClass = 'dirty-file';
-
- // Disabling the button at the start
- if (document.querySelector('input[name="page_has_posted"]').value !== 'true') {
- commitButton.disabled = true;
- }
-
- // Registering a custom listener for the file path and the file content
- // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
- applyAreYouSure(elForm, {
- silent: true,
- dirtyClass: dirtyFileClass,
- fieldSelector: ':input:not(.commit-form-wrapper :input)',
- change($form: any) {
- const dirty = $form[0]?.classList.contains(dirtyFileClass);
- commitButton.disabled = !dirty;
- },
- });
-
// Update the editor from query params, if available,
// only after the dirtyFileClass initialization
const params = new URLSearchParams(window.location.search);