You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
4.0 KiB

Refactor editor upload, update and delete to use git plumbing and add LFS support (#5702) * Use git plumbing for upload: #5621 repo_editor.go: UploadRepoFile * Use git plumbing for upload: #5621 repo_editor.go: GetDiffPreview * Use git plumbing for upload: #5621 repo_editor.go: DeleteRepoFile * Use git plumbing for upload: #5621 repo_editor.go: UploadRepoFiles * Move branch checkout functions out of repo_editor.go as they are no longer used there * BUGFIX: The default permissions should be 100644 This is a change from the previous code but is more in keeping with the default behaviour of git. Signed-off-by: Andrew Thornton <art27@cantab.net> * Standardise cleanUploadFilename to more closely match git See verify_path in: https://github.com/git/git/blob/7f4e64169352e03476b0ea64e7e2973669e491a2/read-cache.c#L951 Signed-off-by: Andrew Thornton <art27@cantab.net> * Redirect on bad paths Signed-off-by: Andrew Thornton <art27@cantab.net> * Refactor to move the uploading functions out to a module Signed-off-by: Andrew Thornton <art27@cantab.net> * Add LFS support Signed-off-by: Andrew Thornton <art27@cantab.net> * Update upload.go attribution header Upload.go is essentially the remnants of repo_editor.go. The remaining code is essentially unchanged from the Gogs code, hence the Gogs attribution. * Delete upload files after session committed * Ensure that GIT_AUTHOR_NAME etc. are valid for git see #5774 Signed-off-by: Andrew Thornton <art27@cantab.net> * Add in test cases per @lafriks comment * Add space between gitea and github imports Signed-off-by: Andrew Thornton <art27@cantab.net> * more examples in TestCleanUploadName Signed-off-by: Andrew Thornton <art27@cantab.net> * fix formatting Signed-off-by: Andrew Thornton <art27@cantab.net> * Set the SSH_ORIGINAL_COMMAND to ensure hooks are run Signed-off-by: Andrew Thornton <art27@cantab.net> * Switch off SSH_ORIGINAL_COMMAND Signed-off-by: Andrew Thornton <art27@cantab.net>
5 years ago
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package uploader
  5. import (
  6. "fmt"
  7. "strings"
  8. "code.gitea.io/git"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/lfs"
  11. "code.gitea.io/gitea/modules/setting"
  12. )
  13. // UpdateRepoFileOptions holds the repository file update options
  14. type UpdateRepoFileOptions struct {
  15. LastCommitID string
  16. OldBranch string
  17. NewBranch string
  18. OldTreeName string
  19. NewTreeName string
  20. Message string
  21. Content string
  22. IsNewFile bool
  23. }
  24. // UpdateRepoFile adds or updates a file in the given repository
  25. func UpdateRepoFile(repo *models.Repository, doer *models.User, opts *UpdateRepoFileOptions) error {
  26. t, err := NewTemporaryUploadRepository(repo)
  27. defer t.Close()
  28. if err != nil {
  29. return err
  30. }
  31. if err := t.Clone(opts.OldBranch); err != nil {
  32. return err
  33. }
  34. if err := t.SetDefaultIndex(); err != nil {
  35. return err
  36. }
  37. filesInIndex, err := t.LsFiles(opts.NewTreeName, opts.OldTreeName)
  38. if err != nil {
  39. return fmt.Errorf("UpdateRepoFile: %v", err)
  40. }
  41. if opts.IsNewFile {
  42. for _, file := range filesInIndex {
  43. if file == opts.NewTreeName {
  44. return models.ErrRepoFileAlreadyExist{FileName: opts.NewTreeName}
  45. }
  46. }
  47. }
  48. //var stdout string
  49. if opts.OldTreeName != opts.NewTreeName && len(filesInIndex) > 0 {
  50. for _, file := range filesInIndex {
  51. if file == opts.OldTreeName {
  52. if err := t.RemoveFilesFromIndex(opts.OldTreeName); err != nil {
  53. return err
  54. }
  55. }
  56. }
  57. }
  58. // Check there is no way this can return multiple infos
  59. filename2attribute2info, err := t.CheckAttribute("filter", opts.NewTreeName)
  60. if err != nil {
  61. return err
  62. }
  63. content := opts.Content
  64. var lfsMetaObject *models.LFSMetaObject
  65. if filename2attribute2info[opts.NewTreeName] != nil && filename2attribute2info[opts.NewTreeName]["filter"] == "lfs" {
  66. // OK so we are supposed to LFS this data!
  67. oid, err := models.GenerateLFSOid(strings.NewReader(opts.Content))
  68. if err != nil {
  69. return err
  70. }
  71. lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: int64(len(opts.Content)), RepositoryID: repo.ID}
  72. content = lfsMetaObject.Pointer()
  73. }
  74. // Add the object to the database
  75. objectHash, err := t.HashObject(strings.NewReader(content))
  76. if err != nil {
  77. return err
  78. }
  79. // Add the object to the index
  80. if err := t.AddObjectToIndex("100644", objectHash, opts.NewTreeName); err != nil {
  81. return err
  82. }
  83. // Now write the tree
  84. treeHash, err := t.WriteTree()
  85. if err != nil {
  86. return err
  87. }
  88. // Now commit the tree
  89. commitHash, err := t.CommitTree(doer, treeHash, opts.Message)
  90. if err != nil {
  91. return err
  92. }
  93. if lfsMetaObject != nil {
  94. // We have an LFS object - create it
  95. lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
  96. if err != nil {
  97. return err
  98. }
  99. contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath}
  100. if !contentStore.Exists(lfsMetaObject) {
  101. if err := contentStore.Put(lfsMetaObject, strings.NewReader(opts.Content)); err != nil {
  102. if err2 := repo.RemoveLFSMetaObjectByOid(lfsMetaObject.Oid); err2 != nil {
  103. return fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err)
  104. }
  105. return err
  106. }
  107. }
  108. }
  109. // Then push this tree to NewBranch
  110. if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
  111. return err
  112. }
  113. // Simulate push event.
  114. oldCommitID := opts.LastCommitID
  115. if opts.NewBranch != opts.OldBranch {
  116. oldCommitID = git.EmptySHA
  117. }
  118. if err = repo.GetOwner(); err != nil {
  119. return fmt.Errorf("GetOwner: %v", err)
  120. }
  121. err = models.PushUpdate(
  122. opts.NewBranch,
  123. models.PushUpdateOptions{
  124. PusherID: doer.ID,
  125. PusherName: doer.Name,
  126. RepoUserName: repo.Owner.Name,
  127. RepoName: repo.Name,
  128. RefFullName: git.BranchPrefix + opts.NewBranch,
  129. OldCommitID: oldCommitID,
  130. NewCommitID: commitHash,
  131. },
  132. )
  133. if err != nil {
  134. return fmt.Errorf("PushUpdate: %v", err)
  135. }
  136. models.UpdateRepoIndexer(repo)
  137. return nil
  138. }