gitea源码

update.go 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package files
  4. import (
  5. "context"
  6. "fmt"
  7. "io"
  8. "path"
  9. "slices"
  10. "strings"
  11. "time"
  12. git_model "code.gitea.io/gitea/models/git"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/git/attribute"
  17. "code.gitea.io/gitea/modules/gitrepo"
  18. "code.gitea.io/gitea/modules/lfs"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/structs"
  22. "code.gitea.io/gitea/modules/util"
  23. "code.gitea.io/gitea/routers/api/v1/utils"
  24. asymkey_service "code.gitea.io/gitea/services/asymkey"
  25. pull_service "code.gitea.io/gitea/services/pull"
  26. )
  27. // IdentityOptions for a person's identity like an author or committer
  28. type IdentityOptions struct {
  29. GitUserName string // to match "git config user.name"
  30. GitUserEmail string // to match "git config user.email"
  31. }
  32. // CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
  33. type CommitDateOptions struct {
  34. Author time.Time
  35. Committer time.Time
  36. }
  37. type ChangeRepoFile struct {
  38. Operation string
  39. TreePath string
  40. FromTreePath string
  41. ContentReader io.ReadSeeker
  42. SHA string
  43. Options *RepoFileOptions
  44. }
  45. // ChangeRepoFilesOptions holds the repository files update options
  46. type ChangeRepoFilesOptions struct {
  47. LastCommitID string
  48. OldBranch string
  49. NewBranch string
  50. Message string
  51. Files []*ChangeRepoFile
  52. Author *IdentityOptions
  53. Committer *IdentityOptions
  54. Dates *CommitDateOptions
  55. Signoff bool
  56. }
  57. type RepoFileOptions struct {
  58. treePath string
  59. fromTreePath string
  60. executable bool
  61. }
  62. // ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error.
  63. type ErrRepoFileDoesNotExist struct {
  64. Path string
  65. Name string
  66. }
  67. // IsErrRepoFileDoesNotExist checks if an error is a ErrRepoDoesNotExist.
  68. func IsErrRepoFileDoesNotExist(err error) bool {
  69. _, ok := err.(ErrRepoFileDoesNotExist)
  70. return ok
  71. }
  72. func (err ErrRepoFileDoesNotExist) Error() string {
  73. return fmt.Sprintf("repository file does not exist [path: %s]", err.Path)
  74. }
  75. func (err ErrRepoFileDoesNotExist) Unwrap() error {
  76. return util.ErrNotExist
  77. }
  78. type LazyReadSeeker interface {
  79. io.ReadSeeker
  80. io.Closer
  81. OpenLazyReader() error
  82. }
  83. // ChangeRepoFiles adds, updates or removes multiple files in the given repository
  84. func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *ChangeRepoFilesOptions) (_ *structs.FilesResponse, errRet error) {
  85. var addedLfsPointers []lfs.Pointer
  86. defer func() {
  87. if errRet != nil {
  88. for _, lfsPointer := range addedLfsPointers {
  89. _, err := git_model.RemoveLFSMetaObjectByOid(ctx, repo.ID, lfsPointer.Oid)
  90. if err != nil {
  91. log.Error("ChangeRepoFiles: RemoveLFSMetaObjectByOid failed: %v", err)
  92. }
  93. }
  94. }
  95. }()
  96. err := repo.MustNotBeArchived()
  97. if err != nil {
  98. return nil, err
  99. }
  100. // If no branch name is set, assume the default branch
  101. if opts.OldBranch == "" {
  102. opts.OldBranch = repo.DefaultBranch
  103. }
  104. if opts.NewBranch == "" {
  105. opts.NewBranch = opts.OldBranch
  106. }
  107. gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
  108. if err != nil {
  109. return nil, err
  110. }
  111. defer closer.Close()
  112. // oldBranch must exist for this operation
  113. if exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.OldBranch); err != nil {
  114. return nil, err
  115. } else if !exist && !repo.IsEmpty {
  116. return nil, git_model.ErrBranchNotExist{
  117. RepoID: repo.ID,
  118. BranchName: opts.OldBranch,
  119. }
  120. }
  121. var treePaths []string
  122. for _, file := range opts.Files {
  123. // If FromTreePath is not set, set it to the opts.TreePath
  124. if file.TreePath != "" && file.FromTreePath == "" {
  125. file.FromTreePath = file.TreePath
  126. }
  127. // Check that the path given in opts.treePath is valid (not a git path)
  128. treePath := CleanGitTreePath(file.TreePath)
  129. if treePath == "" {
  130. return nil, ErrFilenameInvalid{
  131. Path: file.TreePath,
  132. }
  133. }
  134. // If there is a fromTreePath (we are copying it), also clean it up
  135. fromTreePath := CleanGitTreePath(file.FromTreePath)
  136. if fromTreePath == "" && file.FromTreePath != "" {
  137. return nil, ErrFilenameInvalid{
  138. Path: file.FromTreePath,
  139. }
  140. }
  141. file.Options = &RepoFileOptions{
  142. treePath: treePath,
  143. fromTreePath: fromTreePath,
  144. executable: false,
  145. }
  146. treePaths = append(treePaths, treePath)
  147. }
  148. // A NewBranch can be specified for the file to be created/updated in a new branch.
  149. // Check to make sure the branch does not already exist, otherwise we can't proceed.
  150. // If we aren't branching to a new branch, make sure user can commit to the given branch
  151. if opts.NewBranch != opts.OldBranch {
  152. exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.NewBranch)
  153. if err != nil {
  154. return nil, err
  155. }
  156. if exist {
  157. return nil, git_model.ErrBranchAlreadyExists{
  158. BranchName: opts.NewBranch,
  159. }
  160. }
  161. } else if err := VerifyBranchProtection(ctx, repo, doer, opts.OldBranch, treePaths); err != nil {
  162. return nil, err
  163. }
  164. message := strings.TrimSpace(opts.Message)
  165. t, err := NewTemporaryUploadRepository(repo)
  166. if err != nil {
  167. log.Error("NewTemporaryUploadRepository failed: %v", err)
  168. }
  169. defer t.Close()
  170. hasOldBranch := true
  171. if err := t.Clone(ctx, opts.OldBranch, true); err != nil {
  172. for _, file := range opts.Files {
  173. if file.Operation == "delete" {
  174. return nil, err
  175. }
  176. }
  177. if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
  178. return nil, err
  179. }
  180. if err := t.Init(ctx, repo.ObjectFormatName); err != nil {
  181. return nil, err
  182. }
  183. hasOldBranch = false
  184. opts.LastCommitID = ""
  185. }
  186. if hasOldBranch {
  187. if err := t.SetDefaultIndex(ctx); err != nil {
  188. return nil, err
  189. }
  190. }
  191. for _, file := range opts.Files {
  192. if file.Operation == "delete" {
  193. // Get the files in the index
  194. filesInIndex, err := t.LsFiles(ctx, file.TreePath)
  195. if err != nil {
  196. return nil, fmt.Errorf("DeleteRepoFile: %w", err)
  197. }
  198. // Find the file we want to delete in the index
  199. inFilelist := slices.Contains(filesInIndex, file.TreePath)
  200. if !inFilelist {
  201. return nil, ErrRepoFileDoesNotExist{
  202. Path: file.TreePath,
  203. }
  204. }
  205. }
  206. }
  207. if hasOldBranch {
  208. // Get the commit of the original branch
  209. commit, err := t.GetBranchCommit(opts.OldBranch)
  210. if err != nil {
  211. return nil, err // Couldn't get a commit for the branch
  212. }
  213. // Assigned LastCommitID in "opts" if it hasn't been set
  214. if opts.LastCommitID == "" {
  215. opts.LastCommitID = commit.ID.String()
  216. } else {
  217. lastCommitID, err := t.gitRepo.ConvertToGitID(opts.LastCommitID)
  218. if err != nil {
  219. return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %w", err)
  220. }
  221. opts.LastCommitID = lastCommitID.String()
  222. }
  223. for _, file := range opts.Files {
  224. if err = handleCheckErrors(file, commit, opts); err != nil {
  225. return nil, err
  226. }
  227. }
  228. }
  229. lfsContentStore := lfs.NewContentStore()
  230. for _, file := range opts.Files {
  231. switch file.Operation {
  232. case "create", "update", "rename", "upload":
  233. addedLfsPointer, err := modifyFile(ctx, t, file, lfsContentStore, repo.ID)
  234. if err != nil {
  235. return nil, err
  236. }
  237. if addedLfsPointer != nil {
  238. addedLfsPointers = append(addedLfsPointers, *addedLfsPointer)
  239. }
  240. case "delete":
  241. if err = t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil {
  242. return nil, err
  243. }
  244. default:
  245. return nil, fmt.Errorf("invalid file operation: %s %s, supported operations are create, update, delete", file.Operation, file.Options.treePath)
  246. }
  247. }
  248. // Now write the tree
  249. treeHash, err := t.WriteTree(ctx)
  250. if err != nil {
  251. return nil, err
  252. }
  253. // Now commit the tree
  254. commitOpts := &CommitTreeUserOptions{
  255. ParentCommitID: opts.LastCommitID,
  256. TreeHash: treeHash,
  257. CommitMessage: message,
  258. SignOff: opts.Signoff,
  259. DoerUser: doer,
  260. AuthorIdentity: opts.Author,
  261. AuthorTime: nil,
  262. CommitterIdentity: opts.Committer,
  263. CommitterTime: nil,
  264. }
  265. if opts.Dates != nil {
  266. commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
  267. }
  268. commitHash, err := t.CommitTree(ctx, commitOpts)
  269. if err != nil {
  270. return nil, err
  271. }
  272. // Then push this tree to NewBranch
  273. if err := t.Push(ctx, doer, commitHash, opts.NewBranch); err != nil {
  274. log.Error("%T %v", err, err)
  275. return nil, err
  276. }
  277. commit, err := t.GetCommit(commitHash)
  278. if err != nil {
  279. return nil, err
  280. }
  281. // FIXME: this call seems not right, why it needs to read the file content again
  282. // FIXME: why it uses the NewBranch as "ref", it should use the commit ID because the response is only for this commit
  283. filesResponse, err := GetFilesResponseFromCommit(ctx, repo, gitRepo, utils.NewRefCommit(git.RefNameFromBranch(opts.NewBranch), commit), treePaths)
  284. if err != nil {
  285. return nil, err
  286. }
  287. if repo.IsEmpty {
  288. if isEmpty, err := gitRepo.IsEmpty(); err == nil && !isEmpty {
  289. _ = repo_model.UpdateRepositoryColsWithAutoTime(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
  290. }
  291. }
  292. return filesResponse, nil
  293. }
  294. // ErrRepoFileAlreadyExists represents a "RepoFileAlreadyExist" kind of error.
  295. type ErrRepoFileAlreadyExists struct {
  296. Path string
  297. }
  298. // IsErrRepoFileAlreadyExists checks if an error is a ErrRepoFileAlreadyExists.
  299. func IsErrRepoFileAlreadyExists(err error) bool {
  300. _, ok := err.(ErrRepoFileAlreadyExists)
  301. return ok
  302. }
  303. func (err ErrRepoFileAlreadyExists) Error() string {
  304. return fmt.Sprintf("repository file already exists [path: %s]", err.Path)
  305. }
  306. func (err ErrRepoFileAlreadyExists) Unwrap() error {
  307. return util.ErrAlreadyExist
  308. }
  309. // ErrFilePathInvalid represents a "FilePathInvalid" kind of error.
  310. type ErrFilePathInvalid struct {
  311. Message string
  312. Path string
  313. Name string
  314. Type git.EntryMode
  315. }
  316. // IsErrFilePathInvalid checks if an error is an ErrFilePathInvalid.
  317. func IsErrFilePathInvalid(err error) bool {
  318. _, ok := err.(ErrFilePathInvalid)
  319. return ok
  320. }
  321. func (err ErrFilePathInvalid) Error() string {
  322. if err.Message != "" {
  323. return err.Message
  324. }
  325. return fmt.Sprintf("path is invalid [path: %s]", err.Path)
  326. }
  327. func (err ErrFilePathInvalid) Unwrap() error {
  328. return util.ErrInvalidArgument
  329. }
  330. // ErrSHAOrCommitIDNotProvided represents a "SHAOrCommitIDNotProvided" kind of error.
  331. type ErrSHAOrCommitIDNotProvided struct{}
  332. // IsErrSHAOrCommitIDNotProvided checks if an error is a ErrSHAOrCommitIDNotProvided.
  333. func IsErrSHAOrCommitIDNotProvided(err error) bool {
  334. _, ok := err.(ErrSHAOrCommitIDNotProvided)
  335. return ok
  336. }
  337. func (err ErrSHAOrCommitIDNotProvided) Error() string {
  338. return "a SHA or commit ID must be proved when updating a file"
  339. }
  340. // handles the check for various issues for ChangeRepoFiles
  341. func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions) error {
  342. // check old entry (fromTreePath/fromEntry)
  343. if file.Operation == "update" || file.Operation == "upload" || file.Operation == "delete" || file.Operation == "rename" {
  344. var fromEntryIDString string
  345. {
  346. fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath)
  347. if file.Operation == "upload" && git.IsErrNotExist(err) {
  348. fromEntry = nil
  349. } else if err != nil {
  350. return err
  351. }
  352. if fromEntry != nil {
  353. fromEntryIDString = fromEntry.ID.String()
  354. file.Options.executable = fromEntry.IsExecutable() // FIXME: legacy hacky approach, it shouldn't prepare the "Options" in the "check" function
  355. }
  356. }
  357. if file.SHA != "" {
  358. // If the SHA given doesn't match the SHA of the fromTreePath, throw error
  359. if file.SHA != fromEntryIDString {
  360. return pull_service.ErrSHADoesNotMatch{
  361. Path: file.Options.treePath,
  362. GivenSHA: file.SHA,
  363. CurrentSHA: fromEntryIDString,
  364. }
  365. }
  366. } else if opts.LastCommitID != "" {
  367. // If a lastCommitID given doesn't match the branch head's commitID throw
  368. // an error, but only if we aren't creating a new branch.
  369. if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
  370. if changed, err := commit.FileChangedSinceCommit(file.Options.treePath, opts.LastCommitID); err != nil {
  371. return err
  372. } else if changed {
  373. return ErrCommitIDDoesNotMatch{
  374. GivenCommitID: opts.LastCommitID,
  375. CurrentCommitID: opts.LastCommitID,
  376. }
  377. }
  378. // The file wasn't modified, so we are good to delete it
  379. }
  380. } else {
  381. // When updating a file, a lastCommitID or SHA needs to be given to make sure other commits
  382. // haven't been made. We throw an error if one wasn't provided.
  383. return ErrSHAOrCommitIDNotProvided{}
  384. }
  385. }
  386. // check new entry (treePath/treeEntry)
  387. if file.Operation == "create" || file.Operation == "update" || file.Operation == "upload" || file.Operation == "rename" {
  388. // For operation's target path, we need to make sure no parts of the path are existing files or links
  389. // except for the last item in the path (which is the file name).
  390. // And that shouldn't exist IF it is a new file OR is being moved to a new path.
  391. treePathParts := strings.Split(file.Options.treePath, "/")
  392. subTreePath := ""
  393. for index, part := range treePathParts {
  394. subTreePath = path.Join(subTreePath, part)
  395. entry, err := commit.GetTreeEntryByPath(subTreePath)
  396. if err != nil {
  397. if git.IsErrNotExist(err) {
  398. // Means there is no item with that name, so we're good
  399. break
  400. }
  401. return err
  402. }
  403. if index < len(treePathParts)-1 {
  404. if !entry.IsDir() {
  405. return ErrFilePathInvalid{
  406. Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
  407. Path: subTreePath,
  408. Name: part,
  409. Type: git.EntryModeBlob,
  410. }
  411. }
  412. } else if entry.IsLink() {
  413. return ErrFilePathInvalid{
  414. Message: fmt.Sprintf("a symbolic link exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
  415. Path: subTreePath,
  416. Name: part,
  417. Type: git.EntryModeSymlink,
  418. }
  419. } else if entry.IsDir() {
  420. return ErrFilePathInvalid{
  421. Message: fmt.Sprintf("a directory exists where you’re trying to create a file [path: %s]", subTreePath),
  422. Path: subTreePath,
  423. Name: part,
  424. Type: git.EntryModeTree,
  425. }
  426. } else if file.Options.fromTreePath != file.Options.treePath || file.Operation == "create" {
  427. // The entry shouldn't exist if we are creating the new file or moving to a new path
  428. return ErrRepoFileAlreadyExists{
  429. Path: file.Options.treePath,
  430. }
  431. }
  432. }
  433. }
  434. return nil
  435. }
  436. func modifyFile(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile, contentStore *lfs.ContentStore, repoID int64) (addedLfsPointer *lfs.Pointer, _ error) {
  437. if rd, ok := file.ContentReader.(LazyReadSeeker); ok {
  438. if err := rd.OpenLazyReader(); err != nil {
  439. return nil, fmt.Errorf("OpenLazyReader: %w", err)
  440. }
  441. defer rd.Close()
  442. }
  443. // Get the two paths (might be the same if not moving) from the index if they exist
  444. filesInIndex, err := t.LsFiles(ctx, file.TreePath, file.FromTreePath)
  445. if err != nil {
  446. return nil, fmt.Errorf("LsFiles: %w", err)
  447. }
  448. // If is a new file (not updating) then the given path shouldn't exist
  449. if file.Operation == "create" {
  450. if slices.Contains(filesInIndex, file.TreePath) {
  451. return nil, ErrRepoFileAlreadyExists{Path: file.TreePath}
  452. }
  453. }
  454. // Remove the old path from the tree
  455. if file.Options.fromTreePath != file.Options.treePath && len(filesInIndex) > 0 {
  456. for _, indexFile := range filesInIndex {
  457. if indexFile == file.Options.fromTreePath {
  458. if err = t.RemoveFilesFromIndex(ctx, file.FromTreePath); err != nil {
  459. return nil, err
  460. }
  461. }
  462. }
  463. }
  464. var writeObjectRet *writeRepoObjectRet
  465. switch file.Operation {
  466. case "create", "update", "upload":
  467. writeObjectRet, err = writeRepoObjectForModify(ctx, t, file)
  468. case "rename":
  469. writeObjectRet, err = writeRepoObjectForRename(ctx, t, file)
  470. default:
  471. return nil, util.NewInvalidArgumentErrorf("unknown file modification operation: '%s'", file.Operation)
  472. }
  473. if err != nil {
  474. return nil, err
  475. }
  476. // Add the object to the index, the "file.Options.executable" is set in handleCheckErrors by the caller (legacy hacky approach)
  477. if err = t.AddObjectToIndex(ctx, util.Iif(file.Options.executable, "100755", "100644"), writeObjectRet.ObjectHash, file.Options.treePath); err != nil {
  478. return nil, err
  479. }
  480. if writeObjectRet.LfsContent == nil {
  481. return nil, nil // No LFS pointer, so nothing to do
  482. }
  483. defer writeObjectRet.LfsContent.Close()
  484. // Now we must store the content into an LFS object
  485. lfsMetaObject, err := git_model.NewLFSMetaObject(ctx, repoID, writeObjectRet.LfsPointer)
  486. if err != nil {
  487. return nil, err
  488. }
  489. exist, err := contentStore.Exists(lfsMetaObject.Pointer)
  490. if err != nil {
  491. return nil, err
  492. }
  493. if !exist {
  494. err = contentStore.Put(lfsMetaObject.Pointer, writeObjectRet.LfsContent)
  495. if err != nil {
  496. if _, errRemove := git_model.RemoveLFSMetaObjectByOid(ctx, repoID, lfsMetaObject.Oid); errRemove != nil {
  497. return nil, fmt.Errorf("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)", lfsMetaObject.Oid, errRemove, err)
  498. }
  499. return nil, err
  500. }
  501. }
  502. return &lfsMetaObject.Pointer, nil
  503. }
  504. func checkIsLfsFileInGitAttributes(ctx context.Context, t *TemporaryUploadRepository, paths []string) (ret []bool, err error) {
  505. attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
  506. Attributes: []string{attribute.Filter},
  507. Filenames: paths,
  508. })
  509. if err != nil {
  510. return nil, err
  511. }
  512. for _, p := range paths {
  513. isLFSFile := attributesMap[p] != nil && attributesMap[p].Get(attribute.Filter).ToString().Value() == "lfs"
  514. ret = append(ret, isLFSFile)
  515. }
  516. return ret, nil
  517. }
  518. type writeRepoObjectRet struct {
  519. ObjectHash string
  520. LfsContent io.ReadCloser // if not nil, then the caller should store its content in LfsPointer, then close it
  521. LfsPointer lfs.Pointer
  522. }
  523. // writeRepoObjectForModify hashes the git object for create or update operations
  524. func writeRepoObjectForModify(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile) (ret *writeRepoObjectRet, err error) {
  525. ret = &writeRepoObjectRet{}
  526. treeObjectContentReader := file.ContentReader
  527. if setting.LFS.StartServer {
  528. checkIsLfsFiles, err := checkIsLfsFileInGitAttributes(ctx, t, []string{file.Options.treePath})
  529. if err != nil {
  530. return nil, err
  531. }
  532. if checkIsLfsFiles[0] {
  533. // OK, so we are supposed to LFS this data!
  534. ret.LfsPointer, err = lfs.GeneratePointer(file.ContentReader)
  535. if err != nil {
  536. return nil, err
  537. }
  538. if _, err = file.ContentReader.Seek(0, io.SeekStart); err != nil {
  539. return nil, err
  540. }
  541. ret.LfsContent = io.NopCloser(file.ContentReader)
  542. treeObjectContentReader = strings.NewReader(ret.LfsPointer.StringContent())
  543. }
  544. }
  545. ret.ObjectHash, err = t.HashObjectAndWrite(ctx, treeObjectContentReader)
  546. if err != nil {
  547. return nil, err
  548. }
  549. return ret, nil
  550. }
  551. // writeRepoObjectForRename the same as writeRepoObjectForModify buf for "rename"
  552. func writeRepoObjectForRename(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile) (ret *writeRepoObjectRet, err error) {
  553. lastCommitID, err := t.GetLastCommit(ctx)
  554. if err != nil {
  555. return nil, err
  556. }
  557. commit, err := t.GetCommit(lastCommitID)
  558. if err != nil {
  559. return nil, err
  560. }
  561. oldEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath)
  562. if err != nil {
  563. return nil, err
  564. }
  565. ret = &writeRepoObjectRet{ObjectHash: oldEntry.ID.String()}
  566. if !setting.LFS.StartServer {
  567. return ret, nil
  568. }
  569. checkIsLfsFiles, err := checkIsLfsFileInGitAttributes(ctx, t, []string{file.Options.fromTreePath, file.Options.treePath})
  570. if err != nil {
  571. return nil, err
  572. }
  573. oldIsLfs, newIsLfs := checkIsLfsFiles[0], checkIsLfsFiles[1]
  574. // If the old and new paths are both in lfs or both not in lfs, the object hash of the old file can be used directly
  575. // as the object doesn't change
  576. if oldIsLfs == newIsLfs {
  577. return ret, nil
  578. }
  579. oldEntryBlobPointerBy := func(f func(r io.Reader) (lfs.Pointer, error)) (lfsPointer lfs.Pointer, err error) {
  580. r, err := oldEntry.Blob().DataAsync()
  581. if err != nil {
  582. return lfsPointer, err
  583. }
  584. defer r.Close()
  585. return f(r)
  586. }
  587. var treeObjectContentReader io.ReadCloser
  588. if oldIsLfs {
  589. // If the old is in lfs but the new isn't, read the content from lfs and add it as a normal git object
  590. pointer, err := oldEntryBlobPointerBy(lfs.ReadPointer)
  591. if err != nil {
  592. return nil, err
  593. }
  594. treeObjectContentReader, err = lfs.ReadMetaObject(pointer)
  595. if err != nil {
  596. return nil, err
  597. }
  598. defer treeObjectContentReader.Close()
  599. } else {
  600. // If the new is in lfs but the old isn't, read the content from the git object and generate a lfs pointer of it
  601. ret.LfsPointer, err = oldEntryBlobPointerBy(lfs.GeneratePointer)
  602. if err != nil {
  603. return nil, err
  604. }
  605. ret.LfsContent, err = oldEntry.Blob().DataAsync()
  606. if err != nil {
  607. return nil, err
  608. }
  609. treeObjectContentReader = io.NopCloser(strings.NewReader(ret.LfsPointer.StringContent()))
  610. }
  611. ret.ObjectHash, err = t.HashObjectAndWrite(ctx, treeObjectContentReader)
  612. if err != nil {
  613. return nil, err
  614. }
  615. return ret, nil
  616. }
  617. // VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
  618. func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName string, treePaths []string) error {
  619. protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
  620. if err != nil {
  621. return err
  622. }
  623. if protectedBranch != nil {
  624. protectedBranch.Repo = repo
  625. globUnprotected := protectedBranch.GetUnprotectedFilePatterns()
  626. globProtected := protectedBranch.GetProtectedFilePatterns()
  627. canUserPush := protectedBranch.CanUserPush(ctx, doer)
  628. for _, treePath := range treePaths {
  629. isUnprotectedFile := false
  630. if len(globUnprotected) != 0 {
  631. isUnprotectedFile = protectedBranch.IsUnprotectedFile(globUnprotected, treePath)
  632. }
  633. if !canUserPush && !isUnprotectedFile {
  634. return ErrUserCannotCommit{
  635. UserName: doer.LowerName,
  636. }
  637. }
  638. if protectedBranch.IsProtectedFile(globProtected, treePath) {
  639. return pull_service.ErrFilePathProtected{
  640. Path: treePath,
  641. }
  642. }
  643. }
  644. if protectedBranch.RequireSignedCommits {
  645. _, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), branchName)
  646. if err != nil {
  647. if !asymkey_service.IsErrWontSign(err) {
  648. return err
  649. }
  650. return ErrUserCannotCommit{
  651. UserName: doer.LowerName,
  652. }
  653. }
  654. }
  655. }
  656. return nil
  657. }