3838 subcmdHookPreReceive ,
3939 subcmdHookUpdate ,
4040 subcmdHookPostReceive ,
41+ subcmdHookProcReceive ,
4142 },
4243 }
4344
7475 },
7576 },
7677 }
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+ }
7790)
7891
7992type delayWriter struct {
@@ -205,6 +218,11 @@ Gitea or set your environment appropriately.`, "")
205218 }
206219 }
207220
221+ supportProcRecive := false
222+ if git .CheckGitVersionAtLeast ("2.29" ) == nil {
223+ supportProcRecive = true
224+ }
225+
208226 for scanner .Scan () {
209227 // TODO: support news feeds for wiki
210228 if isWiki {
@@ -223,7 +241,9 @@ Gitea or set your environment appropriately.`, "")
223241 lastline ++
224242
225243 // 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 ) {
227247 oldCommitIDs [count ] = oldCommitID
228248 newCommitIDs [count ] = newCommitID
229249 refFullNames [count ] = refFullName
@@ -463,3 +483,327 @@ func pushOptions() map[string]string {
463483 }
464484 return opts
465485}
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