@@ -246,7 +246,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
246246 contentStore := lfs .NewContentStore ()
247247 for _ , file := range opts .Files {
248248 switch file .Operation {
249- case "create" , "update" :
249+ case "create" , "update" , "rename" :
250250 if err := CreateOrUpdateFile (ctx , t , file , contentStore , repo .ID , hasOldBranch ); err != nil {
251251 return nil , err
252252 }
@@ -488,31 +488,32 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
488488 }
489489 }
490490
491- treeObjectContentReader := file .ContentReader
492- var lfsMetaObject * git_model.LFSMetaObject
493- if setting .LFS .StartServer && hasOldBranch {
494- // Check there is no way this can return multiple infos
495- attributesMap , err := attribute .CheckAttributes (ctx , t .gitRepo , "" /* use temp repo's working dir */ , attribute.CheckAttributeOpts {
496- Attributes : []string {attribute .Filter },
497- Filenames : []string {file .Options .treePath },
498- })
491+ var oldEntry * git.TreeEntry
492+ // Assume that the file.ContentReader of a pure rename operation is invalid. Use the file content how it's present in
493+ // git instead
494+ if file .Operation == "rename" {
495+ lastCommitID , err := t .GetLastCommit (ctx )
496+ if err != nil {
497+ return err
498+ }
499+ commit , err := t .GetCommit (lastCommitID )
499500 if err != nil {
500501 return err
501502 }
502503
503- if attributesMap [file .Options .treePath ] != nil && attributesMap [file .Options .treePath ].Get (attribute .Filter ).ToString ().Value () == "lfs" {
504- // OK so we are supposed to LFS this data!
505- pointer , err := lfs .GeneratePointer (treeObjectContentReader )
506- if err != nil {
507- return err
508- }
509- lfsMetaObject = & git_model.LFSMetaObject {Pointer : pointer , RepositoryID : repoID }
510- treeObjectContentReader = strings .NewReader (pointer .StringContent ())
504+ if oldEntry , err = commit .GetTreeEntryByPath (file .Options .fromTreePath ); err != nil {
505+ return err
511506 }
512507 }
513508
514- // Add the object to the database
515- objectHash , err := t .HashObject (ctx , treeObjectContentReader )
509+ var objectHash string
510+ var lfsPointer * lfs.Pointer
511+ switch file .Operation {
512+ case "create" , "update" :
513+ objectHash , lfsPointer , err = createOrUpdateFileHash (ctx , t , file , hasOldBranch )
514+ case "rename" :
515+ objectHash , lfsPointer , err = renameFileHash (ctx , t , oldEntry , file )
516+ }
516517 if err != nil {
517518 return err
518519 }
@@ -528,9 +529,9 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
528529 }
529530 }
530531
531- if lfsMetaObject != nil {
532+ if lfsPointer != nil {
532533 // We have an LFS object - create it
533- lfsMetaObject , err = git_model .NewLFSMetaObject (ctx , lfsMetaObject . RepositoryID , lfsMetaObject . Pointer )
534+ lfsMetaObject , err : = git_model .NewLFSMetaObject (ctx , repoID , * lfsPointer )
534535 if err != nil {
535536 return err
536537 }
@@ -539,11 +540,20 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
539540 return err
540541 }
541542 if ! exist {
542- _ , err := file .ContentReader .Seek (0 , io .SeekStart )
543- if err != nil {
544- return err
543+ var lfsContentReader io.Reader
544+ if file .Operation != "rename" {
545+ if _ , err := file .ContentReader .Seek (0 , io .SeekStart ); err != nil {
546+ return err
547+ }
548+ lfsContentReader = file .ContentReader
549+ } else {
550+ if lfsContentReader , err = oldEntry .Blob ().DataAsync (); err != nil {
551+ return err
552+ }
553+ defer lfsContentReader .(io.ReadCloser ).Close ()
545554 }
546- if err := contentStore .Put (lfsMetaObject .Pointer , file .ContentReader ); err != nil {
555+
556+ if err := contentStore .Put (lfsMetaObject .Pointer , lfsContentReader ); err != nil {
547557 if _ , err2 := git_model .RemoveLFSMetaObjectByOid (ctx , repoID , lfsMetaObject .Oid ); err2 != nil {
548558 return fmt .Errorf ("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)" , lfsMetaObject .Oid , err2 , err )
549559 }
@@ -555,6 +565,99 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
555565 return nil
556566}
557567
568+ func createOrUpdateFileHash (ctx context.Context , t * TemporaryUploadRepository , file * ChangeRepoFile , hasOldBranch bool ) (string , * lfs.Pointer , error ) {
569+ treeObjectContentReader := file .ContentReader
570+ var lfsPointer * lfs.Pointer
571+ if setting .LFS .StartServer && hasOldBranch {
572+ // Check there is no way this can return multiple infos
573+ attributesMap , err := attribute .CheckAttributes (ctx , t .gitRepo , "" /* use temp repo's working dir */ , attribute.CheckAttributeOpts {
574+ Attributes : []string {attribute .Filter },
575+ Filenames : []string {file .Options .treePath },
576+ })
577+ if err != nil {
578+ return "" , nil , err
579+ }
580+
581+ if attributesMap [file .Options .treePath ] != nil && attributesMap [file .Options .treePath ].Get (attribute .Filter ).ToString ().Value () == "lfs" {
582+ // OK so we are supposed to LFS this data!
583+ pointer , err := lfs .GeneratePointer (treeObjectContentReader )
584+ if err != nil {
585+ return "" , nil , err
586+ }
587+ lfsPointer = & pointer
588+ treeObjectContentReader = strings .NewReader (pointer .StringContent ())
589+ }
590+ }
591+
592+ // Add the object to the database
593+ objectHash , err := t .HashObject (ctx , treeObjectContentReader )
594+ if err != nil {
595+ return "" , nil , err
596+ }
597+
598+ return objectHash , lfsPointer , nil
599+ }
600+
601+ func renameFileHash (ctx context.Context , t * TemporaryUploadRepository , oldEntry * git.TreeEntry , file * ChangeRepoFile ) (string , * lfs.Pointer , error ) {
602+ if setting .LFS .StartServer {
603+ attributesMap , err := attribute .CheckAttributes (ctx , t .gitRepo , "" /* use temp repo's working dir */ , attribute.CheckAttributeOpts {
604+ Attributes : []string {attribute .Filter },
605+ Filenames : []string {file .Options .treePath , file .Options .fromTreePath },
606+ })
607+ if err != nil {
608+ return "" , nil , err
609+ }
610+
611+ oldIsLfs := attributesMap [file .Options .fromTreePath ] != nil && attributesMap [file .Options .fromTreePath ].Get (attribute .Filter ).ToString ().Value () == "lfs"
612+ newIsLfs := attributesMap [file .Options .treePath ] != nil && attributesMap [file .Options .treePath ].Get (attribute .Filter ).ToString ().Value () == "lfs"
613+
614+ // 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
615+ // as the object doesn't change
616+ if oldIsLfs == newIsLfs {
617+ return oldEntry .ID .String (), nil , nil
618+ }
619+
620+ oldEntryReader , err := oldEntry .Blob ().DataAsync ()
621+ if err != nil {
622+ return "" , nil , err
623+ }
624+ defer oldEntryReader .Close ()
625+
626+ var treeObjectContentReader io.Reader
627+ var lfsPointer * lfs.Pointer
628+ // If the old path is in lfs but the new isn't, read the content from lfs and add it as normal git object
629+ // If the new path is in lfs but the old isn't, read the content from the git object and generate a lfs
630+ // pointer of it
631+ if oldIsLfs {
632+ pointer , err := lfs .ReadPointer (oldEntryReader )
633+ if err != nil {
634+ return "" , nil , err
635+ }
636+ treeObjectContentReader , err = lfs .ReadMetaObject (pointer )
637+ if err != nil {
638+ return "" , nil , err
639+ }
640+ defer treeObjectContentReader .(io.ReadCloser ).Close ()
641+ } else {
642+ pointer , err := lfs .GeneratePointer (oldEntryReader )
643+ if err != nil {
644+ return "" , nil , err
645+ }
646+ treeObjectContentReader = strings .NewReader (pointer .StringContent ())
647+ lfsPointer = & pointer
648+ }
649+
650+ // Add the object to the database
651+ objectID , err := t .HashObject (ctx , treeObjectContentReader )
652+ if err != nil {
653+ return "" , nil , err
654+ }
655+ return objectID , lfsPointer , nil
656+ }
657+
658+ return oldEntry .ID .String (), nil , nil
659+ }
660+
558661// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
559662func VerifyBranchProtection (ctx context.Context , repo * repo_model.Repository , doer * user_model.User , branchName string , treePaths []string ) error {
560663 protectedBranch , err := git_model .GetFirstMatchProtectedBranchRule (ctx , repo .ID , branchName )
0 commit comments