@@ -407,6 +407,96 @@ func canReadFiles(r *context.Repository) bool {
407
407
return r .Permission .CanRead (unit .TypeCode )
408
408
}
409
409
410
+ // ChangeFiles handles API call for creating or updating multiple files
411
+ func ChangeFiles (ctx * context.APIContext ) {
412
+ // swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
413
+ // ---
414
+ // summary: Create or update multiple files in a repository
415
+ // consumes:
416
+ // - application/json
417
+ // produces:
418
+ // - application/json
419
+ // parameters:
420
+ // - name: owner
421
+ // in: path
422
+ // description: owner of the repo
423
+ // type: string
424
+ // required: true
425
+ // - name: repo
426
+ // in: path
427
+ // description: name of the repo
428
+ // type: string
429
+ // required: true
430
+ // - name: body
431
+ // in: body
432
+ // required: true
433
+ // schema:
434
+ // "$ref": "#/definitions/ChangeFilesOptions"
435
+ // responses:
436
+ // "201":
437
+ // "$ref": "#/responses/FileListResponse"
438
+ // "403":
439
+ // "$ref": "#/responses/error"
440
+ // "404":
441
+ // "$ref": "#/responses/notFound"
442
+ // "422":
443
+ // "$ref": "#/responses/error"
444
+
445
+ apiOpts := web .GetForm (ctx ).(* api.ChangeFilesOptions )
446
+
447
+ if apiOpts .BranchName == "" {
448
+ apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
449
+ }
450
+
451
+ files := []* files_service.ChangeRepoFile {}
452
+ for _ , file := range apiOpts .Files {
453
+ changeRepoFile := & files_service.ChangeRepoFile {
454
+ Operation : file .Operation ,
455
+ TreePath : file .Path ,
456
+ FromTreePath : file .FromPath ,
457
+ Content : file .Content ,
458
+ SHA : file .SHA ,
459
+ }
460
+ files = append (files , changeRepoFile )
461
+ }
462
+
463
+ opts := & files_service.ChangeRepoFilesOptions {
464
+ Files : files ,
465
+ Message : apiOpts .Message ,
466
+ OldBranch : apiOpts .BranchName ,
467
+ NewBranch : apiOpts .NewBranchName ,
468
+ Committer : & files_service.IdentityOptions {
469
+ Name : apiOpts .Committer .Name ,
470
+ Email : apiOpts .Committer .Email ,
471
+ },
472
+ Author : & files_service.IdentityOptions {
473
+ Name : apiOpts .Author .Name ,
474
+ Email : apiOpts .Author .Email ,
475
+ },
476
+ Dates : & files_service.CommitDateOptions {
477
+ Author : apiOpts .Dates .Author ,
478
+ Committer : apiOpts .Dates .Committer ,
479
+ },
480
+ Signoff : apiOpts .Signoff ,
481
+ }
482
+ if opts .Dates .Author .IsZero () {
483
+ opts .Dates .Author = time .Now ()
484
+ }
485
+ if opts .Dates .Committer .IsZero () {
486
+ opts .Dates .Committer = time .Now ()
487
+ }
488
+
489
+ if opts .Message == "" {
490
+ opts .Message = "Upload files over API"
491
+ }
492
+
493
+ if filesResponse , err := createOrUpdateFiles (ctx , opts ); err != nil {
494
+ handleCreateOrUpdateFileError (ctx , err )
495
+ } else {
496
+ ctx .JSON (http .StatusCreated , filesResponse )
497
+ }
498
+ }
499
+
410
500
// CreateFile handles API call for creating a file
411
501
func CreateFile (ctx * context.APIContext ) {
412
502
// swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
@@ -453,11 +543,15 @@ func CreateFile(ctx *context.APIContext) {
453
543
apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
454
544
}
455
545
456
- opts := & files_service.UpdateRepoFileOptions {
457
- Content : apiOpts .Content ,
458
- IsNewFile : true ,
546
+ opts := & files_service.ChangeRepoFilesOptions {
547
+ Files : []* files_service.ChangeRepoFile {
548
+ {
549
+ Operation : "create" ,
550
+ TreePath : ctx .Params ("*" ),
551
+ Content : apiOpts .Content ,
552
+ },
553
+ },
459
554
Message : apiOpts .Message ,
460
- TreePath : ctx .Params ("*" ),
461
555
OldBranch : apiOpts .BranchName ,
462
556
NewBranch : apiOpts .NewBranchName ,
463
557
Committer : & files_service.IdentityOptions {
@@ -482,13 +576,13 @@ func CreateFile(ctx *context.APIContext) {
482
576
}
483
577
484
578
if opts .Message == "" {
485
- opts .Message = ctx .Tr ("repo.editor.add" , opts .TreePath )
579
+ opts .Message = ctx .Tr ("repo.editor.add" , opts .Files [ 0 ]. TreePath )
486
580
}
487
581
488
- if fileResponse , err := createOrUpdateFile (ctx , opts ); err != nil {
582
+ if filesResponse , err := createOrUpdateFiles (ctx , opts ); err != nil {
489
583
handleCreateOrUpdateFileError (ctx , err )
490
584
} else {
491
- ctx .JSON (http .StatusCreated , fileResponse )
585
+ ctx .JSON (http .StatusCreated , filesResponse [ 0 ] )
492
586
}
493
587
}
494
588
@@ -540,15 +634,19 @@ func UpdateFile(ctx *context.APIContext) {
540
634
apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
541
635
}
542
636
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 ,
637
+ opts := & files_service.ChangeRepoFilesOptions {
638
+ Files : []* files_service.ChangeRepoFile {
639
+ {
640
+ Operation : "update" ,
641
+ Content : apiOpts .Content ,
642
+ SHA : apiOpts .SHA ,
643
+ FromTreePath : apiOpts .FromPath ,
644
+ TreePath : ctx .Params ("*" ),
645
+ },
646
+ },
647
+ Message : apiOpts .Message ,
648
+ OldBranch : apiOpts .BranchName ,
649
+ NewBranch : apiOpts .NewBranchName ,
552
650
Committer : & files_service.IdentityOptions {
553
651
Name : apiOpts .Committer .Name ,
554
652
Email : apiOpts .Committer .Email ,
@@ -571,13 +669,13 @@ func UpdateFile(ctx *context.APIContext) {
571
669
}
572
670
573
671
if opts .Message == "" {
574
- opts .Message = ctx .Tr ("repo.editor.update" , opts .TreePath )
672
+ opts .Message = ctx .Tr ("repo.editor.update" , opts .Files [ 0 ]. TreePath )
575
673
}
576
674
577
- if fileResponse , err := createOrUpdateFile (ctx , opts ); err != nil {
675
+ if filesResponse , err := createOrUpdateFiles (ctx , opts ); err != nil {
578
676
handleCreateOrUpdateFileError (ctx , err )
579
677
} else {
580
- ctx .JSON (http .StatusOK , fileResponse )
678
+ ctx .JSON (http .StatusOK , filesResponse [ 0 ] )
581
679
}
582
680
}
583
681
@@ -600,21 +698,23 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
600
698
}
601
699
602
700
// Called from both CreateFile or UpdateFile to handle both
603
- func createOrUpdateFile (ctx * context.APIContext , opts * files_service.UpdateRepoFileOptions ) (* api.FileResponse , error ) {
701
+ func createOrUpdateFiles (ctx * context.APIContext , opts * files_service.ChangeRepoFilesOptions ) ([] * api.FileResponse , error ) {
604
702
if ! canWriteFiles (ctx , opts .OldBranch ) {
605
703
return nil , repo_model.ErrUserDoesNotHaveAccessToRepo {
606
704
UserID : ctx .Doer .ID ,
607
705
RepoName : ctx .Repo .Repository .LowerName ,
608
706
}
609
707
}
610
708
611
- content , err := base64 .StdEncoding .DecodeString (opts .Content )
612
- if err != nil {
613
- return nil , err
709
+ for _ , file := range opts .Files {
710
+ content , err := base64 .StdEncoding .DecodeString (file .Content )
711
+ if err != nil {
712
+ return nil , err
713
+ }
714
+ file .Content = string (content )
614
715
}
615
- opts .Content = string (content )
616
716
617
- return files_service .CreateOrUpdateRepoFile (ctx , ctx .Repo .Repository , ctx .Doer , opts )
717
+ return files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , opts )
618
718
}
619
719
620
720
// DeleteFile Delete a file in a repository
@@ -670,12 +770,17 @@ func DeleteFile(ctx *context.APIContext) {
670
770
apiOpts .BranchName = ctx .Repo .Repository .DefaultBranch
671
771
}
672
772
673
- opts := & files_service.DeleteRepoFileOptions {
773
+ opts := & files_service.ChangeRepoFilesOptions {
774
+ Files : []* files_service.ChangeRepoFile {
775
+ {
776
+ Operation : "delete" ,
777
+ SHA : apiOpts .SHA ,
778
+ TreePath : ctx .Params ("*" ),
779
+ },
780
+ },
674
781
Message : apiOpts .Message ,
675
782
OldBranch : apiOpts .BranchName ,
676
783
NewBranch : apiOpts .NewBranchName ,
677
- SHA : apiOpts .SHA ,
678
- TreePath : ctx .Params ("*" ),
679
784
Committer : & files_service.IdentityOptions {
680
785
Name : apiOpts .Committer .Name ,
681
786
Email : apiOpts .Committer .Email ,
@@ -698,10 +803,10 @@ func DeleteFile(ctx *context.APIContext) {
698
803
}
699
804
700
805
if opts .Message == "" {
701
- opts .Message = ctx .Tr ("repo.editor.delete" , opts .TreePath )
806
+ opts .Message = ctx .Tr ("repo.editor.delete" , opts .Files [ 0 ]. TreePath )
702
807
}
703
808
704
- if fileResponse , err := files_service .DeleteRepoFile (ctx , ctx .Repo .Repository , ctx .Doer , opts ); err != nil {
809
+ if filesResponse , err := files_service .ChangeRepoFiles (ctx , ctx .Repo .Repository , ctx .Doer , opts ); err != nil {
705
810
if git .IsErrBranchNotExist (err ) || models .IsErrRepoFileDoesNotExist (err ) || git .IsErrNotExist (err ) {
706
811
ctx .Error (http .StatusNotFound , "DeleteFile" , err )
707
812
return
@@ -718,7 +823,7 @@ func DeleteFile(ctx *context.APIContext) {
718
823
}
719
824
ctx .Error (http .StatusInternalServerError , "DeleteFile" , err )
720
825
} else {
721
- ctx .JSON (http .StatusOK , fileResponse ) // FIXME on APIv2: return http.StatusNoContent
826
+ ctx .JSON (http .StatusOK , filesResponse [ 0 ] ) // FIXME on APIv2: return http.StatusNoContent
722
827
}
723
828
}
724
829
0 commit comments