diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 70bbcd9965..025d29f64c 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -145,10 +145,6 @@ func editFile(ctx *context.Context, isNewFile bool) { } blob := entry.Blob() - if blob.Size() >= setting.UI.MaxDisplayFileSize { - ctx.NotFound(err) - return - } buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob) if err != nil { @@ -303,6 +299,11 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b operation = "create" } + var contentReader io.ReadSeeker + if isNewFile && form.Content.Has() { + contentReader = strings.NewReader(strings.ReplaceAll(form.Content.Value(), "\r", "")) + } + if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ LastCommitID: form.LastCommit, OldBranch: ctx.Repo.BranchName, @@ -313,7 +314,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b Operation: operation, FromTreePath: ctx.Repo.TreePath, TreePath: form.TreePath, - ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")), + ContentReader: contentReader, }, }, Signoff: form.Signoff, diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index ca346b7e6c..3ffd8f89c4 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -99,7 +99,7 @@ func NewDiffPatchPost(ctx *context.Context) { OldBranch: ctx.Repo.BranchName, NewBranch: branchName, Message: message, - Content: strings.ReplaceAll(form.Content, "\r", ""), + Content: strings.ReplaceAll(form.Content.Value(), "\r", ""), Author: gitCommitter, Committer: gitCommitter, }) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index a2827e516a..d11ad0a54c 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -10,6 +10,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" project_model "code.gitea.io/gitea/models/project" + "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/context" @@ -689,7 +690,7 @@ func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.E // EditRepoFileForm form for changing repository file type EditRepoFileForm struct { TreePath string `binding:"Required;MaxSize(500)"` - Content string + Content optional.Option[string] CommitSummary string `binding:"MaxSize(100)"` CommitMessage string CommitChoice string `binding:"Required;MaxSize(50)"` diff --git a/services/repository/files/update.go b/services/repository/files/update.go index fbf59c40ed..6b1555fb9a 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -43,7 +43,7 @@ type ChangeRepoFile struct { Operation string TreePath string FromTreePath string - ContentReader io.ReadSeeker + ContentReader io.ReadSeeker // nil if the operation is a pure rename SHA string Options *RepoFileOptions } @@ -461,6 +461,11 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep // CreateOrUpdateFile handles creating or updating a file for ChangeRepoFiles func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile, contentStore *lfs.ContentStore, repoID int64, hasOldBranch bool) error { + // ContentReader is only allowed to be nil if the file is moving + if file.ContentReader == nil && file.TreePath == file.FromTreePath { + return nil + } + // Get the two paths (might be the same if not moving) from the index if they exist filesInIndex, err := t.LsFiles(ctx, file.TreePath, file.FromTreePath) if err != nil { @@ -488,26 +493,69 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file } } - treeObjectContentReader := file.ContentReader + var treeObjectContentReader io.Reader = file.ContentReader + var oldEntry *git.TreeEntry + // If nil, use the existing content + if file.ContentReader == nil { + lastCommit, _ := t.GetLastCommit(ctx) + commit, err := t.GetCommit(lastCommit) + if err != nil { + return err + } + + oldEntry, err = commit.GetTreeEntryByPath(file.Options.fromTreePath) + if err != nil { + return err + } + + treeObjectContentReader, err = oldEntry.Blob().DataAsync() + if err != nil { + return err + } + } + var lfsMetaObject *git_model.LFSMetaObject if setting.LFS.StartServer && hasOldBranch { // Check there is no way this can return multiple infos attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{ Attributes: []string{attribute.Filter}, - Filenames: []string{file.Options.treePath}, + Filenames: []string{file.Options.treePath, file.Options.fromTreePath}, }) if err != nil { return err } - if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" { - // OK so we are supposed to LFS this data! - pointer, err := lfs.GeneratePointer(treeObjectContentReader) + var pointer *lfs.Pointer + // Get existing lfs pointer if the old tree path is in lfs + if oldEntry != nil && attributesMap[file.Options.fromTreePath] != nil && attributesMap[file.Options.fromTreePath].Get(attribute.Filter).ToString().Value() == "lfs" { + pointerReader, err := oldEntry.Blob().DataAsync() if err != nil { return err } - lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repoID} + p, err := lfs.ReadPointer(pointerReader) + if err != nil { + return err + } + pointer = &p + } + + if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" { + // Only generate a new lfs pointer if the old tree path isn't in lfs or the object content is changed + if pointer == nil { + p, err := lfs.GeneratePointer(treeObjectContentReader) + if err != nil { + return err + } + pointer = &p + } + + lfsMetaObject = &git_model.LFSMetaObject{Pointer: *pointer, RepositoryID: repoID} treeObjectContentReader = strings.NewReader(pointer.StringContent()) + } else if pointer != nil { // old tree path was in lfs, new is not + treeObjectContentReader, err = lfs.ReadMetaObject(*pointer) + if err != nil { + return err + } } } @@ -539,11 +587,21 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file return err } if !exist { - _, err := file.ContentReader.Seek(0, io.SeekStart) - if err != nil { - return err + var lfsContentReader io.Reader + if file.ContentReader != nil { + _, err := file.ContentReader.Seek(0, io.SeekStart) + if err != nil { + return err + } + lfsContentReader = file.ContentReader + } else { + lfsContentReader, err = oldEntry.Blob().DataAsync() + if err != nil { + return err + } } - if err := contentStore.Put(lfsMetaObject.Pointer, file.ContentReader); err != nil { + + if err := contentStore.Put(lfsMetaObject.Pointer, lfsContentReader); err != nil { if _, err2 := git_model.RemoveLFSMetaObjectByOid(ctx, repoID, lfsMetaObject.Oid); err2 != nil { return fmt.Errorf("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)", lfsMetaObject.Oid, err2, err) }