Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add agit flow support in gitea #14295

Merged
merged 63 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
36c77da
feature: add agit flow support
a1012112796 Feb 7, 2021
b8784f9
Merge branch 'master' into git_simple_pr
a1012112796 Feb 7, 2021
a757ed5
fix lint
a1012112796 Feb 7, 2021
f96e9e7
simplify code add fix some nits
a1012112796 Feb 8, 2021
501f850
Merge branch 'master' into git_simple_pr
a1012112796 Feb 8, 2021
7294bfa
update merge help message
a1012112796 Feb 8, 2021
c1291fa
Merge branch 'master' into git_simple_pr
a1012112796 Feb 9, 2021
0d9442e
Apply suggestions from code review. Thanks @jiangxin
a1012112796 Feb 9, 2021
86acbe1
add forced-update message
a1012112796 Feb 9, 2021
95ca26a
fix lint
a1012112796 Feb 9, 2021
f68899a
splite writePktLine
a1012112796 Feb 9, 2021
9eeaf3d
Merge branch 'master' into git_simple_pr
a1012112796 Feb 12, 2021
5c67694
add refs/for/<target-branch>/<topic-branch> support also
a1012112796 Feb 12, 2021
95eebb1
Add test code add fix api
a1012112796 Feb 13, 2021
434704c
fix lint
a1012112796 Feb 13, 2021
5be1bde
Merge branch 'master' into git_simple_pr
a1012112796 Feb 13, 2021
3c01930
fix test
a1012112796 Feb 13, 2021
300765d
skip test if git version < 2.29
a1012112796 Feb 13, 2021
5140fd7
Merge branch 'master' into git_simple_pr
a1012112796 Feb 13, 2021
e1119e8
try test with git 2.30.1
a1012112796 Feb 14, 2021
9f95719
fix permission check bug
a1012112796 Feb 14, 2021
96d2072
Merge branch 'master' into git_simple_pr
a1012112796 Feb 14, 2021
5fd466f
fix some nit
a1012112796 Feb 15, 2021
dc13bfe
logic implify and test code update
a1012112796 Feb 15, 2021
60d3c32
Merge branch 'master' into git_simple_pr
a1012112796 Feb 15, 2021
0d85ba7
fix bug
a1012112796 Feb 16, 2021
10a45c5
Merge branch 'master' into git_simple_pr
a1012112796 Feb 26, 2021
166ce7b
Merge branch 'master' into git_simple_pr
a1012112796 Mar 2, 2021
beb0a2e
Merge branch 'master' into git_simple_pr
a1012112796 Mar 12, 2021
6e0f5d5
Merge branch 'main' into git_simple_pr
a1012112796 May 7, 2021
aee62fe
Merge branch 'main' into git_simple_pr
a1012112796 May 10, 2021
b740e9b
apply suggestions from code review
a1012112796 May 10, 2021
5547e9a
Merge branch 'main' into git_simple_pr
a1012112796 Jun 14, 2021
4a8e7a7
Merge branch 'main' into git_simple_pr
a1012112796 Jun 14, 2021
fb9a0ac
prepare for merge
zeripath Jun 23, 2021
e33d0aa
Merge remote-tracking branch 'origin/main' into git_simple_pr
zeripath Jun 23, 2021
a6481fa
Merge branch 'main' into git_simple_pr
a1012112796 Jun 24, 2021
a409d22
Merge branch 'main' into git_simple_pr
a1012112796 Jun 26, 2021
5ae5b84
fix permission check bug
a1012112796 Jun 26, 2021
e9c84e1
fix bug when target branch isn't exist
a1012112796 Jun 26, 2021
ce04bd2
Merge branch 'main' into git_simple_pr
6543 Jun 26, 2021
d833ea1
Merge branch 'main' into git_simple_pr
a1012112796 Jun 27, 2021
638e8f7
prevent some special push and fix some nits
a1012112796 Jun 27, 2021
02e49eb
Merge branch 'master' into git_simple_pr
6543 Jun 29, 2021
91d1c11
Merge branch 'main' into git_simple_pr
6543 Jul 10, 2021
fbfb003
Merge branch 'main' into git_simple_pr
a1012112796 Jul 17, 2021
3d03249
fix lint
a1012112796 Jul 17, 2021
cd01c08
try splite
a1012112796 Jul 18, 2021
1a8096e
Merge branch 'main' into git_simple_pr
6543 Jul 18, 2021
ab351e5
Merge branch 'main' into git_simple_pr
a1012112796 Jul 19, 2021
b096315
Apply suggestions from code review
a1012112796 Jul 20, 2021
59a8552
Merge branch 'main' into git_simple_pr
a1012112796 Jul 20, 2021
22fd300
fix version negotiation
a1012112796 Jul 20, 2021
d322b82
Merge branch 'main' into git_simple_pr
a1012112796 Jul 21, 2021
4d4aa45
Merge branch 'main' into git_simple_pr
6543 Jul 21, 2021
19edf9a
remane
a1012112796 Jul 21, 2021
fe505de
fix template
a1012112796 Jul 21, 2021
5a840ac
Merge branch 'main' into git_simple_pr
6543 Jul 23, 2021
a86ee55
Merge branch 'main' into git_simple_pr
a1012112796 Jul 25, 2021
25c4b5c
handle empty repo
a1012112796 Jul 25, 2021
b42ebdd
ui: fix branch link under the title
a1012112796 Jul 25, 2021
3b6594a
Merge branch 'main' into git_simple_pr
lunny Jul 28, 2021
f351805
fix nits
a1012112796 Jul 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 271 additions & 0 deletions cmd/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var (
subcmdHookPreReceive,
subcmdHookUpdate,
subcmdHookPostReceive,
subcmdHookProcReceive,
},
}

Expand Down Expand Up @@ -74,6 +75,18 @@ var (
},
},
}
// Note: new hook since git 2.29
subcmdHookProcReceive = cli.Command{
Name: "proc-receive",
Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookProcReceive,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
}
)

type delayWriter struct {
Expand Down Expand Up @@ -460,3 +473,261 @@ func pushOptions() map[string]string {
}
return opts
}

func runHookProcReceive(c *cli.Context) error {
setup("hooks/proc-receive.log", c.Bool("debug"))

if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
fail(`Rejecting changes as Gitea environment not set.
If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "")
} else {
return nil
}
}

lf, err := os.OpenFile("test.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil {
fail("Internal Server Error", "open log file failed: %v", err)
}
defer lf.Close()

if git.CheckGitVersionAtLeast("2.29") != nil {
fail("Internal Server Error", "git not support proc-receive.")
}

reader := bufio.NewReader(os.Stdin)
repoUser := os.Getenv(models.EnvRepoUsername)
repoName := os.Getenv(models.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
pusherName := os.Getenv(models.EnvPusherName)

// 1. Version and features negotiation.
// S: PKT-LINE(version=1\0push-options atomic...)
// S: flush-pkt
// H: PKT-LINE(version=1\0push-options...)
// H: flush-pkt

rs, err := readPktLine(reader)
if err != nil {
fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
}
if rs.Type != pktLineTypeData {
fail("Internal Server Error", "Pkt-Line format is wrong. get %v", rs)
}

a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
const VersionHead string = "version=1"

if !strings.HasPrefix(rs.Data, VersionHead) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should split rs.Data by the NUL character ('\0'). Part one is version, and part two is capabilities.

fail("Internal Server Error", "Pkt-Line format is wrong. get %v", rs)
}

hasPushOptions := false
response := []byte(VersionHead)
if strings.Contains(rs.Data, "push-options") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should split capabilities using space character, and try to match each capability.
If there is a new capability called "push-options-v2", using string.Contains is wrong.

response = append(response, byte(0))
response = append(response, []byte("push-options")...)
hasPushOptions = true
}
response = append(response, []byte("\n")...)
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved

rs, err = readPktLine(reader)
if err != nil {
fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
}
if rs.Type != pktLineTypeFlush {
fail("Internal Server Error", "Pkt-Line format is wrong. get %v", rs)
}

err = writePktLine(os.Stdout, pktLineTypeData, response)
if err != nil {
fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}

err = writePktLine(os.Stdout, pktLineTypeFlush, nil)
if err != nil {
fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}

// 2. receive commands from server.
// S: PKT-LINE(<old-oid> <new-oid> <ref>)
// S: ... ...
// S: flush-pkt
// # receive push-options
// S: PKT-LINE(push-option)
// S: ... ...
// S: flush-pkt
hookOptions := private.HookOptions{
UserName: pusherName,
UserID: pusherID,
}
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.RefFullNames = make([]string, 0, hookBatchSize)

for {
rs, err = readPktLine(reader)
if err != nil {
fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
}
if rs.Type == pktLineTypeFlush {
break
}
t := strings.SplitN(rs.Data, " ", 3)
if len(t) != 3 {
continue
}
hookOptions.OldCommitIDs = append(hookOptions.OldCommitIDs, t[0])
hookOptions.NewCommitIDs = append(hookOptions.NewCommitIDs, t[1])
hookOptions.RefFullNames = append(hookOptions.RefFullNames, t[2])
zeripath marked this conversation as resolved.
Show resolved Hide resolved
}

hookOptions.GitPushOptions = make(map[string]string)

if hasPushOptions {
for {
rs, err = readPktLine(reader)
if err != nil {
fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
}
if rs.Type == pktLineTypeFlush {
break
}

kv := strings.SplitN(rs.Data, "=", 2)
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
if len(kv) == 2 {
hookOptions.GitPushOptions[kv[0]] = kv[1]
}
}
}

// run hook
resp, err := private.HookProcReceive(repoUser, repoName, hookOptions)
if err != nil {
fail("Internal Server Error", "run proc-receive hook failed :%v", err)
}

// 3 response result to service.
// # OK, but has an alternate reference. The alternate reference name
// # and other status can be given in option directives.
// H: PKT-LINE(ok <ref>)
// H: PKT-LINE(option refname <refname>)
// H: PKT-LINE(option old-oid <old-oid>)
// H: PKT-LINE(option new-oid <new-oid>)
// H: PKT-LINE(option forced-update)
// H: ... ...
// H: flush-pkt
for _, rs := range resp.Results {
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
err = writePktLine(os.Stdout, pktLineTypeData, []byte("ok "+rs.OrignRef))
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
err = writePktLine(os.Stdout, pktLineTypeData, []byte("option refname "+rs.Ref))
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
err = writePktLine(os.Stdout, pktLineTypeData, []byte("option old-oid "+rs.OldOID))
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
err = writePktLine(os.Stdout, pktLineTypeData, []byte("option new-oid "+rs.NewOID))
if err != nil {
fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
}
err = writePktLine(os.Stdout, pktLineTypeFlush, nil)
if err != nil {
fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
return nil
}

// git PKT-Line api
// pktLineType message type of pkt-line
type pktLineType int64

const (
// UnKnow type
pktLineTypeUnknow pktLineType = 0
// flush-pkt "0000"
pktLineTypeFlush pktLineType = iota
// data line
pktLineTypeData
)

// gitPktLine pkt-line api
type gitPktLine struct {
Type pktLineType
Length int64
6543 marked this conversation as resolved.
Show resolved Hide resolved
Data string
}

func readPktLine(in *bufio.Reader) (r *gitPktLine, err error) {
// read prefix
lengthBytes := make([]byte, 4)
for i := 0; i < 4; i++ {
lengthBytes[i], err = in.ReadByte()
if err != nil {
return nil, err
}
}
r = new(gitPktLine)
r.Length, err = strconv.ParseInt(string(lengthBytes), 16, 64)
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

if r.Length == 0 {
r.Type = pktLineTypeFlush
return r, nil
}

if r.Length <= 4 || r.Length > 65520 {
r.Type = pktLineTypeUnknow
return r, nil
}

tmp := make([]byte, r.Length-4)
for i := range tmp {
tmp[i], err = in.ReadByte()
6543 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
}

r.Type = pktLineTypeData
r.Data = string(tmp)
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved

return r, nil
}

func writePktLine(out io.Writer, typ pktLineType, data []byte) error {
if typ == pktLineTypeFlush {
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
l, err := out.Write([]byte("0000"))
if err != nil {
return err
}
if l != 4 {
return fmt.Errorf("real write length is different with request, want %v, real %v", 4, l)
}
}

if typ != pktLineTypeData {
return nil
}

l := len(data) + 4
tmp := []byte(fmt.Sprintf("%04x", l))
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
tmp = append(tmp, data...)
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved

lr, err := out.Write(tmp)
if err != nil {
return err
}
if l != lr {
return fmt.Errorf("real write length is different with request, want %v, real %v", l, lr)
}

return nil
}
41 changes: 41 additions & 0 deletions cmd/hook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package cmd

import (
"bufio"
"bytes"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestPktLine(t *testing.T) {
// test read
s := strings.NewReader("0000")
r := bufio.NewReader(s)
result, err := readPktLine(r)
assert.NoError(t, err)
assert.Equal(t, pktLineTypeFlush, result.Type)

s = strings.NewReader("0006a\n")
r = bufio.NewReader(s)
result, err = readPktLine(r)
assert.NoError(t, err)
assert.Equal(t, pktLineTypeData, result.Type)
assert.Equal(t, "a\n", result.Data)

// test write
w := bytes.NewBuffer([]byte{})
err = writePktLine(w, pktLineTypeFlush, nil)
assert.NoError(t, err)
assert.Equal(t, []byte("0000"), w.Bytes())

w.Reset()
err = writePktLine(w, pktLineTypeData, []byte("a\nb"))
assert.NoError(t, err)
assert.Equal(t, []byte("0007a\nb"), w.Bytes())
}
13 changes: 13 additions & 0 deletions cmd/serv.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/pprof"
Expand Down Expand Up @@ -135,6 +136,13 @@ func runServ(c *cli.Context) error {
}

if len(words) < 2 {
if git.CheckGitVersionAtLeast("2.29") == nil {
// for AGit Flow
if cmd == "ssh_info" {
fmt.Print(`{"type":"gitea","version":1}`)
return nil
}
}
fail("Too few arguments", "Too few arguments in cmd: %s", cmd)
}

Expand Down Expand Up @@ -203,6 +211,11 @@ func runServ(c *cli.Context) error {
}
}

// Because of special ref "refs/for" .. , need delay write permission check
if git.CheckGitVersionAtLeast("2.29") == nil {
requestedMode = models.AccessModeRead
}

results, err := private.ServCommand(keyID, username, reponame, requestedMode, verb, lfsVerb)
if err != nil {
if private.IsErrServCommand(err) {
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ var migrations = []Migration{
NewMigration("Add user redirect", addUserRedirect),
// v168 -> v169
NewMigration("Recreate user table to fix default values", recreateUserTableToFixDefaultValues),
// v169 -> v170
NewMigration("Add agit style pull request support", addAgitStylePullRequest),
}

// GetCurrentDBVersion returns the current db version
Expand Down
25 changes: 25 additions & 0 deletions models/migrations/v169.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"fmt"

"xorm.io/xorm"
)

func addAgitStylePullRequest(x *xorm.Engine) error {
type PullRequestStyle int

type PullRequest struct {
TopicBranch string
Style PullRequestStyle
}

if err := x.Sync2(new(PullRequest)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}
Loading