@@ -12,6 +12,7 @@ import (
12
12
"io"
13
13
"net/http"
14
14
"path"
15
+ "strings"
15
16
"time"
16
17
17
18
"code.gitea.io/gitea/models"
@@ -407,6 +408,96 @@ func canReadFiles(r *context.Repository) bool {
407
408
return r .Permission .CanRead (unit .TypeCode )
408
409
}
409
410
411
+ // ChangeFiles handles API call for creating or updating multiple files
412
+ func ChangeFiles (ctx * context.APIContext ) {
413
+ // swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
414
+ // ---
415
+ // summary: Create or update multiple files in a repository
416
+ // consumes:
417
+ // - application/json
418
+ // produces:
419
+ // - application/json
420
+ // parameters:
421
+ // - name: owner
422
+ // in: path
423
+ // description: owner of the repo
424
+ // type: string
425
+ // required: true
426
+ // - name: repo
427
+ // in: path
428
+ // description: name of the repo
429
+ // type: string
430
+ // required: true
431
+ // - name: body
432
+ // in: body
433
+ // required: true
434
+ // schema:
435
+ // "$ref": "#/definitions/ChangeFilesOptions"
436
+ // responses:
437
+ // "201":
438
+ // "$ref": "#/responses/FilesResponse"
439
+ // "403":
440
+ // "$ref": "#/responses/error"
441
+ // "404":
442
+ // "$ref": "#/responses/notFound"
443
+ // "422":
444
+ // "$ref": "#/responses/error"
445
+
446
+ apiOpts := web .GetForm (ctx ).(* api.ChangeFilesOptions )
447
+
448
+ if apiOpts .BranchName == "" {
449
+ apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
450
+ }
451
+
452
+ files := []* files_service.ChangeRepoFile {}
453
+ for _ , file := range apiOpts .Files {
454
+ changeRepoFile := & files_service.ChangeRepoFile {
455
+ Operation : file .Operation ,
456
+ TreePath : file .Path ,
457
+ FromTreePath : file .FromPath ,
458
+ Content : file .Content ,
459
+ SHA : file .SHA ,
460
+ }
461
+ files = append (files , changeRepoFile )
462
+ }
463
+
464
+ opts := & files_service.ChangeRepoFilesOptions {
465
+ Files : files ,
466
+ Message : apiOpts .Message ,
467
+ OldBranch : apiOpts .BranchName ,
468
+ NewBranch : apiOpts .NewBranchName ,
469
+ Committer : & files_service.IdentityOptions {
470
+ Name : apiOpts .Committer .Name ,
471
+ Email : apiOpts .Committer .Email ,
472
+ },
473
+ Author : & files_service.IdentityOptions {
474
+ Name : apiOpts .Author .Name ,
475
+ Email : apiOpts .Author .Email ,
476
+ },
477
+ Dates : & files_service.CommitDateOptions {
478
+ Author : apiOpts .Dates .Author ,
479
+ Committer : apiOpts .Dates .Committer ,
480
+ },
481
+ Signoff : apiOpts .Signoff ,
482
+ }
483
+ if opts .Dates .Author .IsZero () {
484
+ opts .Dates .Author = time .Now ()
485
+ }
486
+ if opts .Dates .Committer .IsZero () {
487
+ opts .Dates .Committer = time .Now ()
488
+ }
489
+
490
+ if opts .Message == "" {
491
+ opts .Message = changeFilesCommitMessage (ctx , files )
492
+ }
493
+
494
+ if filesResponse , err := createOrUpdateFiles (ctx , opts ); err != nil {
495
+ handleCreateOrUpdateFileError (ctx , err )
496
+ } else {
497
+ ctx .JSON (http .StatusCreated , filesResponse )
498
+ }
499
+ }
500
+
410
501
// CreateFile handles API call for creating a file
411
502
func CreateFile (ctx * context.APIContext ) {
412
503
// swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
@@ -453,11 +544,15 @@ func CreateFile(ctx *context.APIContext) {
453
544
apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
454
545
}
455
546
456
- opts := & files_service.UpdateRepoFileOptions {
457
- Content : apiOpts .Content ,
458
- IsNewFile : true ,
547
+ opts := & files_service.ChangeRepoFilesOptions {
548
+ Files : []* files_service.ChangeRepoFile {
549
+ {
550
+ Operation : "create" ,
551
+ TreePath : ctx .Params ("*" ),
552
+ Content : apiOpts .Content ,
553
+ },
554
+ },
459
555
Message : apiOpts .Message ,
460
- TreePath : ctx .Params ("*" ),
461
556
OldBranch : apiOpts .BranchName ,
462
557
NewBranch : apiOpts .NewBranchName ,
463
558
Committer : & files_service.IdentityOptions {
@@ -482,12 +577,13 @@ func CreateFile(ctx *context.APIContext) {
482
577
}
483
578
484
579
if opts .Message == "" {
485
- opts .Message = ctx . Tr ( "repo.editor.add" , opts .TreePath )
580
+ opts .Message = changeFilesCommitMessage ( ctx , opts .Files )
486
581
}
487
582
488
- if fileResponse , err := createOrUpdateFile (ctx , opts ); err != nil {
583
+ if filesResponse , err := createOrUpdateFiles (ctx , opts ); err != nil {
489
584
handleCreateOrUpdateFileError (ctx , err )
490
585
} else {
586
+ fileResponse := files_service .GetFileResponseFromFilesResponse (filesResponse , 0 )
491
587
ctx .JSON (http .StatusCreated , fileResponse )
492
588
}
493
589
}
@@ -540,15 +636,19 @@ func UpdateFile(ctx *context.APIContext) {
540
636
apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
541
637
}
542
638
543
- opts := & files_service.UpdateRepoFileOptions {
544
- Content : apiOpts .Content ,
545
- SHA : apiOpts .SHA ,
546
- IsNewFile : false ,
547
- Message : apiOpts .Message ,
548
- FromTreePath : apiOpts .FromPath ,
549
- TreePath : ctx .Params ("*" ),
550
- OldBranch : apiOpts .BranchName ,
551
- NewBranch : apiOpts .NewBranchName ,
639
+ opts := & files_service.ChangeRepoFilesOptions {
640
+ Files : []* files_service.ChangeRepoFile {
641
+ {
642
+ Operation : "update" ,
643
+ Content : apiOpts .Content ,
644
+ SHA : apiOpts .SHA ,
645
+ FromTreePath : apiOpts .FromPath ,
646
+ TreePath : ctx .Params ("*" ),
647
+ },
648
+ },
649
+ Message : apiOpts .Message ,
650
+ OldBranch : apiOpts .BranchName ,
651
+ NewBranch : apiOpts .NewBranchName ,
552
652
Committer : & files_service.IdentityOptions {
553
653
Name : apiOpts .Committer .Name ,
554
654
Email : apiOpts .Committer .Email ,
@@ -571,12 +671,13 @@ func UpdateFile(ctx *context.APIContext) {
571
671
}
572
672
573
673
if opts .Message == "" {
574
- opts .Message = ctx . Tr ( "repo.editor.update" , opts .TreePath )
674
+ opts .Message = changeFilesCommitMessage ( ctx , opts .Files )
575
675
}
576
676
577
- if fileResponse , err := createOrUpdateFile (ctx , opts ); err != nil {
677
+ if filesResponse , err := createOrUpdateFiles (ctx , opts ); err != nil {
578
678
handleCreateOrUpdateFileError (ctx , err )
579
679
} else {
680
+ fileResponse := files_service .GetFileResponseFromFilesResponse (filesResponse , 0 )
580
681
ctx .JSON (http .StatusOK , fileResponse )
581
682
}
582
683
}
@@ -600,21 +701,53 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
600
701
}
601
702
602
703
// Called from both CreateFile or UpdateFile to handle both
603
- func createOrUpdateFile (ctx * context.APIContext , opts * files_service.UpdateRepoFileOptions ) (* api.FileResponse , error ) {
704
+ func createOrUpdateFiles (ctx * context.APIContext , opts * files_service.ChangeRepoFilesOptions ) (* api.FilesResponse , error ) {
604
705
if ! canWriteFiles (ctx , opts .OldBranch ) {
605
706
return nil , repo_model.ErrUserDoesNotHaveAccessToRepo {
606
707
UserID : ctx .Doer .ID ,
607
708
RepoName : ctx .Repo .Repository .LowerName ,
608
709
}
609
710
}
610
711
611
- content , err := base64 .StdEncoding .DecodeString (opts .Content )
612
- if err != nil {
613
- return nil , err
712
+ for _ , file := range opts .Files {
713
+ content , err := base64 .StdEncoding .DecodeString (file .Content )
714
+ if err != nil {
715
+ return nil , err
716
+ }
717
+ file .Content = string (content )
614
718
}
615
- opts .Content = string (content )
616
719
617
- return files_service .CreateOrUpdateRepoFile (ctx , ctx .Repo .Repository , ctx .Doer , opts )
720
+ return files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , opts )
721
+ }
722
+
723
+ // format commit message if empty
724
+ func changeFilesCommitMessage (ctx * context.APIContext , files []* files_service.ChangeRepoFile ) string {
725
+ var (
726
+ createFiles []string
727
+ updateFiles []string
728
+ deleteFiles []string
729
+ )
730
+ for _ , file := range files {
731
+ switch file .Operation {
732
+ case "create" :
733
+ createFiles = append (createFiles , file .TreePath )
734
+ case "update" :
735
+ updateFiles = append (updateFiles , file .TreePath )
736
+ case "delete" :
737
+ deleteFiles = append (deleteFiles , file .TreePath )
738
+ }
739
+ }
740
+ message := ""
741
+ if len (createFiles ) != 0 {
742
+ message += ctx .Tr ("repo.editor.add" , strings .Join (createFiles , ", " )+ "\n " )
743
+ }
744
+ if len (updateFiles ) != 0 {
745
+ message += ctx .Tr ("repo.editor.update" , strings .Join (updateFiles , ", " )+ "\n " )
746
+ }
747
+ if len (deleteFiles ) != 0 {
748
+ message += ctx .Tr ("repo.editor.delete" , strings .Join (deleteFiles , ", " ))
749
+ }
750
+ return strings .Trim (message , "\n " )
618
751
}
619
752
620
753
// DeleteFile Delete a file in a repository
@@ -670,12 +803,17 @@ func DeleteFile(ctx *context.APIContext) {
670
803
apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
671
804
}
672
805
673
- opts := & files_service.DeleteRepoFileOptions {
806
+ opts := & files_service.ChangeRepoFilesOptions {
807
+ Files : []* files_service.ChangeRepoFile {
808
+ {
809
+ Operation : "delete" ,
810
+ SHA : apiOpts .SHA ,
811
+ TreePath : ctx .Params ("*" ),
812
+ },
813
+ },
674
814
Message : apiOpts .Message ,
675
815
OldBranch : apiOpts .BranchName ,
676
816
NewBranch : apiOpts .NewBranchName ,
677
- SHA : apiOpts .SHA ,
678
- TreePath : ctx .Params ("*" ),
679
817
Committer : & files_service.IdentityOptions {
680
818
Name : apiOpts .Committer .Name ,
681
819
Email : apiOpts .Committer .Email ,
@@ -698,10 +836,10 @@ func DeleteFile(ctx *context.APIContext) {
698
836
}
699
837
700
838
if opts .Message == "" {
701
- opts .Message = ctx . Tr ( "repo.editor.delete" , opts .TreePath )
839
+ opts .Message = changeFilesCommitMessage ( ctx , opts .Files )
702
840
}
703
841
704
- if fileResponse , err := files_service .DeleteRepoFile (ctx , ctx .Repo .Repository , ctx .Doer , opts ); err != nil {
842
+ if filesResponse , err := files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , opts ); err != nil {
705
843
if git .IsErrBranchNotExist (err ) || models .IsErrRepoFileDoesNotExist (err ) || git .IsErrNotExist (err ) {
706
844
ctx .Error (http .StatusNotFound , "DeleteFile" , err )
707
845
return
@@ -718,6 +856,7 @@ func DeleteFile(ctx *context.APIContext) {
718
856
}
719
857
ctx .Error (http .StatusInternalServerError , "DeleteFile" , err )
720
858
} else {
859
+ fileResponse := files_service .GetFileResponseFromFilesResponse (filesResponse , 0 )
721
860
ctx .JSON (http .StatusOK , fileResponse ) // FIXME on APIv2: return http.StatusNoContent
722
861
}
723
862
}
0 commit comments