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 all commits
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
346 changes: 345 additions & 1 deletion 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 @@ -205,6 +218,11 @@ Gitea or set your environment appropriately.`, "")
}
}

supportProcRecive := false
if git.CheckGitVersionAtLeast("2.29") == nil {
supportProcRecive = true
}

for scanner.Scan() {
// TODO: support news feeds for wiki
if isWiki {
Expand All @@ -223,7 +241,9 @@ Gitea or set your environment appropriately.`, "")
lastline++

// If the ref is a branch or tag, check if it's protected
if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
// if supportProcRecive all ref should be checked because
// permission check was delayed
if supportProcRecive || strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
oldCommitIDs[count] = oldCommitID
newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName
Expand Down Expand Up @@ -463,3 +483,327 @@ 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 {
return 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.`, "")
}
return nil
}

ctx, cancel := installSignals()
defer cancel()

if git.CheckGitVersionAtLeast("2.29") != nil {
return 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...) / PKT-LINE(version=1\n)
// S: flush-pkt
// H: PKT-LINE(version=1\0push-options...)
// H: flush-pkt

rs, err := readPktLine(reader, pktLineTypeData)
if err != nil {
return err
}

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

var (
hasPushOptions bool
response = []byte(VersionHead)
requestOptions []string
)

index := bytes.IndexByte(rs.Data, byte(0))
if index >= len(rs.Data) {
return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
}

if index < 0 {
if len(rs.Data) == 10 && rs.Data[9] == '\n' {
index = 9
} else {
return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
}
}

if string(rs.Data[0:index]) != VersionHead {
return fail("Internal Server Error", "Received unsupported version: %s", string(rs.Data[0:index]))
}
requestOptions = strings.Split(string(rs.Data[index+1:]), " ")

for _, option := range requestOptions {
if strings.HasPrefix(option, "push-options") {
response = append(response, byte(0))
response = append(response, []byte("push-options")...)
hasPushOptions = true
}
}
response = append(response, '\n')

_, err = readPktLine(reader, pktLineTypeFlush)
if err != nil {
return err
}

err = writeDataPktLine(os.Stdout, response)
if err != nil {
return err
}

err = writeFlushPktLine(os.Stdout)
if err != nil {
return 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 {
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
rs, err = readPktLine(reader, pktLineTypeUnknow)
if err != nil {
return err
}

if rs.Type == pktLineTypeFlush {
break
}
t := strings.SplitN(string(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, pktLineTypeUnknow)
if err != nil {
return err
}

if rs.Type == pktLineTypeFlush {
break
}

kv := strings.SplitN(string(rs.Data), "=", 2)
if len(kv) == 2 {
hookOptions.GitPushOptions[kv[0]] = kv[1]
}
}
}

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

// 4. response result to service
// # a. 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
// # b. NO, I reject it.
// H: PKT-LINE(ng <ref> <reason>)
// # c. Fall through, let 'receive-pack' to execute it.
// H: PKT-LINE(ok <ref>)
// H: PKT-LINE(option fall-through)

for _, rs := range resp.Results {
a1012112796 marked this conversation as resolved.
Show resolved Hide resolved
if len(rs.Err) > 0 {
err = writeDataPktLine(os.Stdout, []byte("ng "+rs.OriginalRef+" "+rs.Err))
if err != nil {
return err
}
continue
}

if rs.IsNotMatched {
err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
if err != nil {
return err
}
err = writeDataPktLine(os.Stdout, []byte("option fall-through"))
if err != nil {
return err
}
continue
}

err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
if err != nil {
return err
}
err = writeDataPktLine(os.Stdout, []byte("option refname "+rs.Ref))
if err != nil {
return err
}
if rs.OldOID != git.EmptySHA {
err = writeDataPktLine(os.Stdout, []byte("option old-oid "+rs.OldOID))
if err != nil {
return err
}
}
err = writeDataPktLine(os.Stdout, []byte("option new-oid "+rs.NewOID))
if err != nil {
return err
}
if rs.IsForcePush {
err = writeDataPktLine(os.Stdout, []byte("option forced-update"))
if err != nil {
return err
}
}
}
err = writeFlushPktLine(os.Stdout)

return err
}

// 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 uint64
Data []byte
}

func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
var (
err error
r *gitPktLine
)

// read prefix
lengthBytes := make([]byte, 4)
for i := 0; i < 4; i++ {
lengthBytes[i], err = in.ReadByte()
if err != nil {
return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
}
}

r = new(gitPktLine)
r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
if err != nil {
return nil, fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
}

if r.Length == 0 {
if requestType == pktLineTypeData {
return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
}
r.Type = pktLineTypeFlush
return r, nil
}

if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush {
return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
}

r.Data = make([]byte, r.Length-4)
for i := range r.Data {
r.Data[i], err = in.ReadByte()
if err != nil {
return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
}
}

r.Type = pktLineTypeData

return r, nil
}

func writeFlushPktLine(out io.Writer) error {
l, err := out.Write([]byte("0000"))
if err != nil {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
if l != 4 {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}

return nil
}

func writeDataPktLine(out io.Writer, data []byte) error {
hexchar := []byte("0123456789abcdef")
hex := func(n uint64) byte {
return hexchar[(n)&15]
}

length := uint64(len(data) + 4)
tmp := make([]byte, 4)
tmp[0] = hex(length >> 12)
tmp[1] = hex(length >> 8)
tmp[2] = hex(length >> 4)
tmp[3] = hex(length)

lr, err := out.Write(tmp)
if err != nil {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
if 4 != lr {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}

lr, err = out.Write(data)
if err != nil {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
if int(length-4) != lr {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}

return nil
}
Loading