38
38
subcmdHookPreReceive ,
39
39
subcmdHookUpdate ,
40
40
subcmdHookPostReceive ,
41
+ subcmdHookProcReceive ,
41
42
},
42
43
}
43
44
74
75
},
75
76
},
76
77
}
78
+ // Note: new hook since git 2.29
79
+ subcmdHookProcReceive = cli.Command {
80
+ Name : "proc-receive" ,
81
+ Usage : "Delegate proc-receive Git hook" ,
82
+ Description : "This command should only be called by Git" ,
83
+ Action : runHookProcReceive ,
84
+ Flags : []cli.Flag {
85
+ cli.BoolFlag {
86
+ Name : "debug" ,
87
+ },
88
+ },
89
+ }
77
90
)
78
91
79
92
type delayWriter struct {
@@ -205,6 +218,11 @@ Gitea or set your environment appropriately.`, "")
205
218
}
206
219
}
207
220
221
+ supportProcRecive := false
222
+ if git .CheckGitVersionAtLeast ("2.29" ) == nil {
223
+ supportProcRecive = true
224
+ }
225
+
208
226
for scanner .Scan () {
209
227
// TODO: support news feeds for wiki
210
228
if isWiki {
@@ -223,7 +241,9 @@ Gitea or set your environment appropriately.`, "")
223
241
lastline ++
224
242
225
243
// If the ref is a branch or tag, check if it's protected
226
- if strings .HasPrefix (refFullName , git .BranchPrefix ) || strings .HasPrefix (refFullName , git .TagPrefix ) {
244
+ // if supportProcRecive all ref should be checked because
245
+ // permission check was delayed
246
+ if supportProcRecive || strings .HasPrefix (refFullName , git .BranchPrefix ) || strings .HasPrefix (refFullName , git .TagPrefix ) {
227
247
oldCommitIDs [count ] = oldCommitID
228
248
newCommitIDs [count ] = newCommitID
229
249
refFullNames [count ] = refFullName
@@ -463,3 +483,327 @@ func pushOptions() map[string]string {
463
483
}
464
484
return opts
465
485
}
486
+
487
+ func runHookProcReceive (c * cli.Context ) error {
488
+ setup ("hooks/proc-receive.log" , c .Bool ("debug" ))
489
+
490
+ if len (os .Getenv ("SSH_ORIGINAL_COMMAND" )) == 0 {
491
+ if setting .OnlyAllowPushIfGiteaEnvironmentSet {
492
+ return fail (`Rejecting changes as Gitea environment not set.
493
+ If you are pushing over SSH you must push with a key managed by
494
+ Gitea or set your environment appropriately.` , "" )
495
+ }
496
+ return nil
497
+ }
498
+
499
+ ctx , cancel := installSignals ()
500
+ defer cancel ()
501
+
502
+ if git .CheckGitVersionAtLeast ("2.29" ) != nil {
503
+ return fail ("Internal Server Error" , "git not support proc-receive." )
504
+ }
505
+
506
+ reader := bufio .NewReader (os .Stdin )
507
+ repoUser := os .Getenv (models .EnvRepoUsername )
508
+ repoName := os .Getenv (models .EnvRepoName )
509
+ pusherID , _ := strconv .ParseInt (os .Getenv (models .EnvPusherID ), 10 , 64 )
510
+ pusherName := os .Getenv (models .EnvPusherName )
511
+
512
+ // 1. Version and features negotiation.
513
+ // S: PKT-LINE(version=1\0push-options atomic...) / PKT-LINE(version=1\n)
514
+ // S: flush-pkt
515
+ // H: PKT-LINE(version=1\0push-options...)
516
+ // H: flush-pkt
517
+
518
+ rs , err := readPktLine (reader , pktLineTypeData )
519
+ if err != nil {
520
+ return err
521
+ }
522
+
523
+ const VersionHead string = "version=1"
524
+
525
+ var (
526
+ hasPushOptions bool
527
+ response = []byte (VersionHead )
528
+ requestOptions []string
529
+ )
530
+
531
+ index := bytes .IndexByte (rs .Data , byte (0 ))
532
+ if index >= len (rs .Data ) {
533
+ return fail ("Internal Server Error" , "pkt-line: format error " + fmt .Sprint (rs .Data ))
534
+ }
535
+
536
+ if index < 0 {
537
+ if len (rs .Data ) == 10 && rs .Data [9 ] == '\n' {
538
+ index = 9
539
+ } else {
540
+ return fail ("Internal Server Error" , "pkt-line: format error " + fmt .Sprint (rs .Data ))
541
+ }
542
+ }
543
+
544
+ if string (rs .Data [0 :index ]) != VersionHead {
545
+ return fail ("Internal Server Error" , "Received unsupported version: %s" , string (rs .Data [0 :index ]))
546
+ }
547
+ requestOptions = strings .Split (string (rs .Data [index + 1 :]), " " )
548
+
549
+ for _ , option := range requestOptions {
550
+ if strings .HasPrefix (option , "push-options" ) {
551
+ response = append (response , byte (0 ))
552
+ response = append (response , []byte ("push-options" )... )
553
+ hasPushOptions = true
554
+ }
555
+ }
556
+ response = append (response , '\n' )
557
+
558
+ _ , err = readPktLine (reader , pktLineTypeFlush )
559
+ if err != nil {
560
+ return err
561
+ }
562
+
563
+ err = writeDataPktLine (os .Stdout , response )
564
+ if err != nil {
565
+ return err
566
+ }
567
+
568
+ err = writeFlushPktLine (os .Stdout )
569
+ if err != nil {
570
+ return err
571
+ }
572
+
573
+ // 2. receive commands from server.
574
+ // S: PKT-LINE(<old-oid> <new-oid> <ref>)
575
+ // S: ... ...
576
+ // S: flush-pkt
577
+ // # [receive push-options]
578
+ // S: PKT-LINE(push-option)
579
+ // S: ... ...
580
+ // S: flush-pkt
581
+ hookOptions := private.HookOptions {
582
+ UserName : pusherName ,
583
+ UserID : pusherID ,
584
+ }
585
+ hookOptions .OldCommitIDs = make ([]string , 0 , hookBatchSize )
586
+ hookOptions .NewCommitIDs = make ([]string , 0 , hookBatchSize )
587
+ hookOptions .RefFullNames = make ([]string , 0 , hookBatchSize )
588
+
589
+ for {
590
+ // note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
591
+ rs , err = readPktLine (reader , pktLineTypeUnknow )
592
+ if err != nil {
593
+ return err
594
+ }
595
+
596
+ if rs .Type == pktLineTypeFlush {
597
+ break
598
+ }
599
+ t := strings .SplitN (string (rs .Data ), " " , 3 )
600
+ if len (t ) != 3 {
601
+ continue
602
+ }
603
+ hookOptions .OldCommitIDs = append (hookOptions .OldCommitIDs , t [0 ])
604
+ hookOptions .NewCommitIDs = append (hookOptions .NewCommitIDs , t [1 ])
605
+ hookOptions .RefFullNames = append (hookOptions .RefFullNames , t [2 ])
606
+ }
607
+
608
+ hookOptions .GitPushOptions = make (map [string ]string )
609
+
610
+ if hasPushOptions {
611
+ for {
612
+ rs , err = readPktLine (reader , pktLineTypeUnknow )
613
+ if err != nil {
614
+ return err
615
+ }
616
+
617
+ if rs .Type == pktLineTypeFlush {
618
+ break
619
+ }
620
+
621
+ kv := strings .SplitN (string (rs .Data ), "=" , 2 )
622
+ if len (kv ) == 2 {
623
+ hookOptions .GitPushOptions [kv [0 ]] = kv [1 ]
624
+ }
625
+ }
626
+ }
627
+
628
+ // 3. run hook
629
+ resp , err := private .HookProcReceive (ctx , repoUser , repoName , hookOptions )
630
+ if err != nil {
631
+ return fail ("Internal Server Error" , "run proc-receive hook failed :%v" , err )
632
+ }
633
+
634
+ // 4. response result to service
635
+ // # a. OK, but has an alternate reference. The alternate reference name
636
+ // # and other status can be given in option directives.
637
+ // H: PKT-LINE(ok <ref>)
638
+ // H: PKT-LINE(option refname <refname>)
639
+ // H: PKT-LINE(option old-oid <old-oid>)
640
+ // H: PKT-LINE(option new-oid <new-oid>)
641
+ // H: PKT-LINE(option forced-update)
642
+ // H: ... ...
643
+ // H: flush-pkt
644
+ // # b. NO, I reject it.
645
+ // H: PKT-LINE(ng <ref> <reason>)
646
+ // # c. Fall through, let 'receive-pack' to execute it.
647
+ // H: PKT-LINE(ok <ref>)
648
+ // H: PKT-LINE(option fall-through)
649
+
650
+ for _ , rs := range resp .Results {
651
+ if len (rs .Err ) > 0 {
652
+ err = writeDataPktLine (os .Stdout , []byte ("ng " + rs .OriginalRef + " " + rs .Err ))
653
+ if err != nil {
654
+ return err
655
+ }
656
+ continue
657
+ }
658
+
659
+ if rs .IsNotMatched {
660
+ err = writeDataPktLine (os .Stdout , []byte ("ok " + rs .OriginalRef ))
661
+ if err != nil {
662
+ return err
663
+ }
664
+ err = writeDataPktLine (os .Stdout , []byte ("option fall-through" ))
665
+ if err != nil {
666
+ return err
667
+ }
668
+ continue
669
+ }
670
+
671
+ err = writeDataPktLine (os .Stdout , []byte ("ok " + rs .OriginalRef ))
672
+ if err != nil {
673
+ return err
674
+ }
675
+ err = writeDataPktLine (os .Stdout , []byte ("option refname " + rs .Ref ))
676
+ if err != nil {
677
+ return err
678
+ }
679
+ if rs .OldOID != git .EmptySHA {
680
+ err = writeDataPktLine (os .Stdout , []byte ("option old-oid " + rs .OldOID ))
681
+ if err != nil {
682
+ return err
683
+ }
684
+ }
685
+ err = writeDataPktLine (os .Stdout , []byte ("option new-oid " + rs .NewOID ))
686
+ if err != nil {
687
+ return err
688
+ }
689
+ if rs .IsForcePush {
690
+ err = writeDataPktLine (os .Stdout , []byte ("option forced-update" ))
691
+ if err != nil {
692
+ return err
693
+ }
694
+ }
695
+ }
696
+ err = writeFlushPktLine (os .Stdout )
697
+
698
+ return err
699
+ }
700
+
701
+ // git PKT-Line api
702
+ // pktLineType message type of pkt-line
703
+ type pktLineType int64
704
+
705
+ const (
706
+ // UnKnow type
707
+ pktLineTypeUnknow pktLineType = 0
708
+ // flush-pkt "0000"
709
+ pktLineTypeFlush pktLineType = iota
710
+ // data line
711
+ pktLineTypeData
712
+ )
713
+
714
+ // gitPktLine pkt-line api
715
+ type gitPktLine struct {
716
+ Type pktLineType
717
+ Length uint64
718
+ Data []byte
719
+ }
720
+
721
+ func readPktLine (in * bufio.Reader , requestType pktLineType ) (* gitPktLine , error ) {
722
+ var (
723
+ err error
724
+ r * gitPktLine
725
+ )
726
+
727
+ // read prefix
728
+ lengthBytes := make ([]byte , 4 )
729
+ for i := 0 ; i < 4 ; i ++ {
730
+ lengthBytes [i ], err = in .ReadByte ()
731
+ if err != nil {
732
+ return nil , fail ("Internal Server Error" , "Pkt-Line: read stdin failed : %v" , err )
733
+ }
734
+ }
735
+
736
+ r = new (gitPktLine )
737
+ r .Length , err = strconv .ParseUint (string (lengthBytes ), 16 , 32 )
738
+ if err != nil {
739
+ return nil , fail ("Internal Server Error" , "Pkt-Line format is wrong :%v" , err )
740
+ }
741
+
742
+ if r .Length == 0 {
743
+ if requestType == pktLineTypeData {
744
+ return nil , fail ("Internal Server Error" , "Pkt-Line format is wrong" )
745
+ }
746
+ r .Type = pktLineTypeFlush
747
+ return r , nil
748
+ }
749
+
750
+ if r .Length <= 4 || r .Length > 65520 || requestType == pktLineTypeFlush {
751
+ return nil , fail ("Internal Server Error" , "Pkt-Line format is wrong" )
752
+ }
753
+
754
+ r .Data = make ([]byte , r .Length - 4 )
755
+ for i := range r .Data {
756
+ r .Data [i ], err = in .ReadByte ()
757
+ if err != nil {
758
+ return nil , fail ("Internal Server Error" , "Pkt-Line: read stdin failed : %v" , err )
759
+ }
760
+ }
761
+
762
+ r .Type = pktLineTypeData
763
+
764
+ return r , nil
765
+ }
766
+
767
+ func writeFlushPktLine (out io.Writer ) error {
768
+ l , err := out .Write ([]byte ("0000" ))
769
+ if err != nil {
770
+ return fail ("Internal Server Error" , "Pkt-Line response failed: %v" , err )
771
+ }
772
+ if l != 4 {
773
+ return fail ("Internal Server Error" , "Pkt-Line response failed: %v" , err )
774
+ }
775
+
776
+ return nil
777
+ }
778
+
779
+ func writeDataPktLine (out io.Writer , data []byte ) error {
780
+ hexchar := []byte ("0123456789abcdef" )
781
+ hex := func (n uint64 ) byte {
782
+ return hexchar [(n )& 15 ]
783
+ }
784
+
785
+ length := uint64 (len (data ) + 4 )
786
+ tmp := make ([]byte , 4 )
787
+ tmp [0 ] = hex (length >> 12 )
788
+ tmp [1 ] = hex (length >> 8 )
789
+ tmp [2 ] = hex (length >> 4 )
790
+ tmp [3 ] = hex (length )
791
+
792
+ lr , err := out .Write (tmp )
793
+ if err != nil {
794
+ return fail ("Internal Server Error" , "Pkt-Line response failed: %v" , err )
795
+ }
796
+ if 4 != lr {
797
+ return fail ("Internal Server Error" , "Pkt-Line response failed: %v" , err )
798
+ }
799
+
800
+ lr , err = out .Write (data )
801
+ if err != nil {
802
+ return fail ("Internal Server Error" , "Pkt-Line response failed: %v" , err )
803
+ }
804
+ if int (length - 4 ) != lr {
805
+ return fail ("Internal Server Error" , "Pkt-Line response failed: %v" , err )
806
+ }
807
+
808
+ return nil
809
+ }
0 commit comments