|
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 {
|
@@ -460,3 +473,261 @@ func pushOptions() map[string]string {
|
460 | 473 | }
|
461 | 474 | return opts
|
462 | 475 | }
|
| 476 | + |
| 477 | +func runHookProcReceive(c *cli.Context) error { |
| 478 | + setup("hooks/proc-receive.log", c.Bool("debug")) |
| 479 | + |
| 480 | + if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
| 481 | + if setting.OnlyAllowPushIfGiteaEnvironmentSet { |
| 482 | + fail(`Rejecting changes as Gitea environment not set. |
| 483 | +If you are pushing over SSH you must push with a key managed by |
| 484 | +Gitea or set your environment appropriately.`, "") |
| 485 | + } else { |
| 486 | + return nil |
| 487 | + } |
| 488 | + } |
| 489 | + |
| 490 | + lf, err := os.OpenFile("test.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) |
| 491 | + if err != nil { |
| 492 | + fail("Internal Server Error", "open log file failed: %v", err) |
| 493 | + } |
| 494 | + defer lf.Close() |
| 495 | + |
| 496 | + if git.CheckGitVersionAtLeast("2.29") != nil { |
| 497 | + fail("Internal Server Error", "git not support proc-receive.") |
| 498 | + } |
| 499 | + |
| 500 | + reader := bufio.NewReader(os.Stdin) |
| 501 | + repoUser := os.Getenv(models.EnvRepoUsername) |
| 502 | + repoName := os.Getenv(models.EnvRepoName) |
| 503 | + pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) |
| 504 | + pusherName := os.Getenv(models.EnvPusherName) |
| 505 | + |
| 506 | + // 1. Version and features negotiation. |
| 507 | + // S: PKT-LINE(version=1\0push-options atomic...) |
| 508 | + // S: flush-pkt |
| 509 | + // H: PKT-LINE(version=1\0push-options...) |
| 510 | + // H: flush-pkt |
| 511 | + |
| 512 | + rs, err := readPktLine(reader) |
| 513 | + if err != nil { |
| 514 | + fail("Internal Server Error", "Pkt-Line format is wrong :%v", err) |
| 515 | + } |
| 516 | + if rs.Type != pktLineTypeData { |
| 517 | + fail("Internal Server Error", "Pkt-Line format is wrong. get %v", rs) |
| 518 | + } |
| 519 | + |
| 520 | + const VersionHead string = "version=1" |
| 521 | + |
| 522 | + if !strings.HasPrefix(rs.Data, VersionHead) { |
| 523 | + fail("Internal Server Error", "Pkt-Line format is wrong. get %v", rs) |
| 524 | + } |
| 525 | + |
| 526 | + hasPushOptions := false |
| 527 | + response := []byte(VersionHead) |
| 528 | + if strings.Contains(rs.Data, "push-options") { |
| 529 | + response = append(response, byte(0)) |
| 530 | + response = append(response, []byte("push-options")...) |
| 531 | + hasPushOptions = true |
| 532 | + } |
| 533 | + response = append(response, []byte("\n")...) |
| 534 | + |
| 535 | + rs, err = readPktLine(reader) |
| 536 | + if err != nil { |
| 537 | + fail("Internal Server Error", "Pkt-Line format is wrong :%v", err) |
| 538 | + } |
| 539 | + if rs.Type != pktLineTypeFlush { |
| 540 | + fail("Internal Server Error", "Pkt-Line format is wrong. get %v", rs) |
| 541 | + } |
| 542 | + |
| 543 | + err = writePktLine(os.Stdout, pktLineTypeData, response) |
| 544 | + if err != nil { |
| 545 | + fail("Internal Server Error", "Pkt-Line response failed: %v", err) |
| 546 | + } |
| 547 | + |
| 548 | + err = writePktLine(os.Stdout, pktLineTypeFlush, nil) |
| 549 | + if err != nil { |
| 550 | + fail("Internal Server Error", "Pkt-Line response failed: %v", err) |
| 551 | + } |
| 552 | + |
| 553 | + // 2. receive commands from server. |
| 554 | + // S: PKT-LINE(<old-oid> <new-oid> <ref>) |
| 555 | + // S: ... ... |
| 556 | + // S: flush-pkt |
| 557 | + // # receive push-options |
| 558 | + // S: PKT-LINE(push-option) |
| 559 | + // S: ... ... |
| 560 | + // S: flush-pkt |
| 561 | + hookOptions := private.HookOptions{ |
| 562 | + UserName: pusherName, |
| 563 | + UserID: pusherID, |
| 564 | + } |
| 565 | + hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize) |
| 566 | + hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize) |
| 567 | + hookOptions.RefFullNames = make([]string, 0, hookBatchSize) |
| 568 | + |
| 569 | + for { |
| 570 | + rs, err = readPktLine(reader) |
| 571 | + if err != nil { |
| 572 | + fail("Internal Server Error", "Pkt-Line format is wrong :%v", err) |
| 573 | + } |
| 574 | + if rs.Type == pktLineTypeFlush { |
| 575 | + break |
| 576 | + } |
| 577 | + t := strings.SplitN(rs.Data, " ", 3) |
| 578 | + if len(t) != 3 { |
| 579 | + continue |
| 580 | + } |
| 581 | + hookOptions.OldCommitIDs = append(hookOptions.OldCommitIDs, t[0]) |
| 582 | + hookOptions.NewCommitIDs = append(hookOptions.NewCommitIDs, t[1]) |
| 583 | + hookOptions.RefFullNames = append(hookOptions.RefFullNames, t[2]) |
| 584 | + } |
| 585 | + |
| 586 | + hookOptions.GitPushOptions = make(map[string]string) |
| 587 | + |
| 588 | + if hasPushOptions { |
| 589 | + for { |
| 590 | + rs, err = readPktLine(reader) |
| 591 | + if err != nil { |
| 592 | + fail("Internal Server Error", "Pkt-Line format is wrong :%v", err) |
| 593 | + } |
| 594 | + if rs.Type == pktLineTypeFlush { |
| 595 | + break |
| 596 | + } |
| 597 | + |
| 598 | + kv := strings.SplitN(rs.Data, "=", 2) |
| 599 | + if len(kv) == 2 { |
| 600 | + hookOptions.GitPushOptions[kv[0]] = kv[1] |
| 601 | + } |
| 602 | + } |
| 603 | + } |
| 604 | + |
| 605 | + // run hook |
| 606 | + resp, err := private.HookProcReceive(repoUser, repoName, hookOptions) |
| 607 | + if err != nil { |
| 608 | + fail("Internal Server Error", "run proc-receive hook failed :%v", err) |
| 609 | + } |
| 610 | + |
| 611 | + // 3 response result to service. |
| 612 | + // # OK, but has an alternate reference. The alternate reference name |
| 613 | + // # and other status can be given in option directives. |
| 614 | + // H: PKT-LINE(ok <ref>) |
| 615 | + // H: PKT-LINE(option refname <refname>) |
| 616 | + // H: PKT-LINE(option old-oid <old-oid>) |
| 617 | + // H: PKT-LINE(option new-oid <new-oid>) |
| 618 | + // H: PKT-LINE(option forced-update) |
| 619 | + // H: ... ... |
| 620 | + // H: flush-pkt |
| 621 | + for _, rs := range resp.Results { |
| 622 | + err = writePktLine(os.Stdout, pktLineTypeData, []byte("ok "+rs.OrignRef)) |
| 623 | + if err != nil { |
| 624 | + fail("Internal Server Error", "Pkt-Line response failed: %v", err) |
| 625 | + } |
| 626 | + err = writePktLine(os.Stdout, pktLineTypeData, []byte("option refname "+rs.Ref)) |
| 627 | + if err != nil { |
| 628 | + fail("Internal Server Error", "Pkt-Line response failed: %v", err) |
| 629 | + } |
| 630 | + err = writePktLine(os.Stdout, pktLineTypeData, []byte("option old-oid "+rs.OldOID)) |
| 631 | + if err != nil { |
| 632 | + fail("Internal Server Error", "Pkt-Line response failed: %v", err) |
| 633 | + } |
| 634 | + err = writePktLine(os.Stdout, pktLineTypeData, []byte("option new-oid "+rs.NewOID)) |
| 635 | + if err != nil { |
| 636 | + fail("Internal Server Error", "Pkt-Line response failed: %v", err) |
| 637 | + } |
| 638 | + } |
| 639 | + err = writePktLine(os.Stdout, pktLineTypeFlush, nil) |
| 640 | + if err != nil { |
| 641 | + fail("Internal Server Error", "Pkt-Line response failed: %v", err) |
| 642 | + } |
| 643 | + return nil |
| 644 | +} |
| 645 | + |
| 646 | +// git PKT-Line api |
| 647 | +// pktLineType message type of pkt-line |
| 648 | +type pktLineType int64 |
| 649 | + |
| 650 | +const ( |
| 651 | + // UnKnow type |
| 652 | + pktLineTypeUnknow pktLineType = 0 |
| 653 | + // flush-pkt "0000" |
| 654 | + pktLineTypeFlush pktLineType = iota |
| 655 | + // data line |
| 656 | + pktLineTypeData |
| 657 | +) |
| 658 | + |
| 659 | +// gitPktLine pkt-line api |
| 660 | +type gitPktLine struct { |
| 661 | + Type pktLineType |
| 662 | + Length int64 |
| 663 | + Data string |
| 664 | +} |
| 665 | + |
| 666 | +func readPktLine(in *bufio.Reader) (r *gitPktLine, err error) { |
| 667 | + // read prefix |
| 668 | + lengthBytes := make([]byte, 4) |
| 669 | + for i := 0; i < 4; i++ { |
| 670 | + lengthBytes[i], err = in.ReadByte() |
| 671 | + if err != nil { |
| 672 | + return nil, err |
| 673 | + } |
| 674 | + } |
| 675 | + r = new(gitPktLine) |
| 676 | + r.Length, err = strconv.ParseInt(string(lengthBytes), 16, 64) |
| 677 | + if err != nil { |
| 678 | + return nil, err |
| 679 | + } |
| 680 | + |
| 681 | + if r.Length == 0 { |
| 682 | + r.Type = pktLineTypeFlush |
| 683 | + return r, nil |
| 684 | + } |
| 685 | + |
| 686 | + if r.Length <= 4 || r.Length > 65520 { |
| 687 | + r.Type = pktLineTypeUnknow |
| 688 | + return r, nil |
| 689 | + } |
| 690 | + |
| 691 | + tmp := make([]byte, r.Length-4) |
| 692 | + for i := range tmp { |
| 693 | + tmp[i], err = in.ReadByte() |
| 694 | + if err != nil { |
| 695 | + return nil, err |
| 696 | + } |
| 697 | + } |
| 698 | + |
| 699 | + r.Type = pktLineTypeData |
| 700 | + r.Data = string(tmp) |
| 701 | + |
| 702 | + return r, nil |
| 703 | +} |
| 704 | + |
| 705 | +func writePktLine(out io.Writer, typ pktLineType, data []byte) error { |
| 706 | + if typ == pktLineTypeFlush { |
| 707 | + l, err := out.Write([]byte("0000")) |
| 708 | + if err != nil { |
| 709 | + return err |
| 710 | + } |
| 711 | + if l != 4 { |
| 712 | + return fmt.Errorf("real write length is different with request, want %v, real %v", 4, l) |
| 713 | + } |
| 714 | + } |
| 715 | + |
| 716 | + if typ != pktLineTypeData { |
| 717 | + return nil |
| 718 | + } |
| 719 | + |
| 720 | + l := len(data) + 4 |
| 721 | + tmp := []byte(fmt.Sprintf("%04x", l)) |
| 722 | + tmp = append(tmp, data...) |
| 723 | + |
| 724 | + lr, err := out.Write(tmp) |
| 725 | + if err != nil { |
| 726 | + return err |
| 727 | + } |
| 728 | + if l != lr { |
| 729 | + return fmt.Errorf("real write length is different with request, want %v, real %v", l, lr) |
| 730 | + } |
| 731 | + |
| 732 | + return nil |
| 733 | +} |
0 commit comments