2017-02-23 11:40:44 +08:00
|
|
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
2022-11-28 02:20:29 +08:00
|
|
|
// SPDX-License-Identifier: MIT
|
2017-02-23 11:40:44 +08:00
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
2017-02-25 22:54:40 +08:00
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2017-02-23 11:40:44 +08:00
|
|
|
"fmt"
|
2020-01-12 16:46:03 +08:00
|
|
|
"io"
|
2019-06-01 23:00:21 +08:00
|
|
|
"net/http"
|
2017-02-23 11:40:44 +08:00
|
|
|
"os"
|
2017-02-25 22:54:40 +08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2020-01-12 16:46:03 +08:00
|
|
|
"time"
|
2017-02-23 11:40:44 +08:00
|
|
|
|
2019-03-27 17:33:00 +08:00
|
|
|
"code.gitea.io/gitea/modules/git"
|
2017-05-04 13:42:02 +08:00
|
|
|
"code.gitea.io/gitea/modules/private"
|
2022-05-09 00:46:32 +08:00
|
|
|
repo_module "code.gitea.io/gitea/modules/repository"
|
2019-11-15 06:39:48 +08:00
|
|
|
"code.gitea.io/gitea/modules/setting"
|
2020-05-08 23:46:05 +08:00
|
|
|
"code.gitea.io/gitea/modules/util"
|
2017-02-23 11:40:44 +08:00
|
|
|
|
|
|
|
"github.com/urfave/cli"
|
|
|
|
)
|
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
const (
|
|
|
|
hookBatchSize = 30
|
|
|
|
)
|
|
|
|
|
2017-02-23 11:40:44 +08:00
|
|
|
var (
|
|
|
|
// CmdHook represents the available hooks sub-command.
|
|
|
|
CmdHook = cli.Command{
|
|
|
|
Name: "hook",
|
|
|
|
Usage: "Delegate commands to corresponding Git hooks",
|
|
|
|
Description: "This should only be called by Git",
|
|
|
|
Subcommands: []cli.Command{
|
|
|
|
subcmdHookPreReceive,
|
2018-01-13 06:16:49 +08:00
|
|
|
subcmdHookUpdate,
|
2017-02-23 11:40:44 +08:00
|
|
|
subcmdHookPostReceive,
|
2021-07-28 17:42:56 +08:00
|
|
|
subcmdHookProcReceive,
|
2017-02-23 11:40:44 +08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
subcmdHookPreReceive = cli.Command{
|
|
|
|
Name: "pre-receive",
|
|
|
|
Usage: "Delegate pre-receive Git hook",
|
|
|
|
Description: "This command should only be called by Git",
|
|
|
|
Action: runHookPreReceive,
|
2020-05-29 11:04:44 +08:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "debug",
|
|
|
|
},
|
|
|
|
},
|
2017-02-23 11:40:44 +08:00
|
|
|
}
|
2018-01-13 06:16:49 +08:00
|
|
|
subcmdHookUpdate = cli.Command{
|
2017-02-23 11:40:44 +08:00
|
|
|
Name: "update",
|
|
|
|
Usage: "Delegate update Git hook",
|
|
|
|
Description: "This command should only be called by Git",
|
|
|
|
Action: runHookUpdate,
|
2020-05-29 11:04:44 +08:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "debug",
|
|
|
|
},
|
|
|
|
},
|
2017-02-23 11:40:44 +08:00
|
|
|
}
|
|
|
|
subcmdHookPostReceive = cli.Command{
|
|
|
|
Name: "post-receive",
|
|
|
|
Usage: "Delegate post-receive Git hook",
|
|
|
|
Description: "This command should only be called by Git",
|
|
|
|
Action: runHookPostReceive,
|
2020-05-29 11:04:44 +08:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "debug",
|
|
|
|
},
|
|
|
|
},
|
2017-02-23 11:40:44 +08:00
|
|
|
}
|
2021-07-28 17:42:56 +08:00
|
|
|
// 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",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-02-23 11:40:44 +08:00
|
|
|
)
|
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
type delayWriter struct {
|
|
|
|
internal io.Writer
|
|
|
|
buf *bytes.Buffer
|
|
|
|
timer *time.Timer
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDelayWriter(internal io.Writer, delay time.Duration) *delayWriter {
|
|
|
|
timer := time.NewTimer(delay)
|
|
|
|
return &delayWriter{
|
|
|
|
internal: internal,
|
|
|
|
buf: &bytes.Buffer{},
|
|
|
|
timer: timer,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *delayWriter) Write(p []byte) (n int, err error) {
|
|
|
|
if d.buf != nil {
|
|
|
|
select {
|
|
|
|
case <-d.timer.C:
|
|
|
|
_, err := d.internal.Write(d.buf.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
d.buf = nil
|
|
|
|
return d.internal.Write(p)
|
|
|
|
default:
|
|
|
|
return d.buf.Write(p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return d.internal.Write(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *delayWriter) WriteString(s string) (n int, err error) {
|
|
|
|
if d.buf != nil {
|
|
|
|
select {
|
|
|
|
case <-d.timer.C:
|
|
|
|
_, err := d.internal.Write(d.buf.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
d.buf = nil
|
|
|
|
return d.internal.Write([]byte(s))
|
|
|
|
default:
|
|
|
|
return d.buf.WriteString(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return d.internal.Write([]byte(s))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *delayWriter) Close() error {
|
|
|
|
if d == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-08 23:46:05 +08:00
|
|
|
stopped := util.StopTimer(d.timer)
|
|
|
|
if stopped || d.buf == nil {
|
2020-01-12 16:46:03 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
_, err := d.internal.Write(d.buf.Bytes())
|
|
|
|
d.buf = nil
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
type nilWriter struct{}
|
|
|
|
|
|
|
|
func (n *nilWriter) Write(p []byte) (int, error) {
|
|
|
|
return len(p), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *nilWriter) WriteString(s string) (int, error) {
|
|
|
|
return len(s), nil
|
|
|
|
}
|
|
|
|
|
2017-02-23 11:40:44 +08:00
|
|
|
func runHookPreReceive(c *cli.Context) error {
|
2022-05-09 00:46:32 +08:00
|
|
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
2019-12-28 05:15:04 +08:00
|
|
|
return nil
|
|
|
|
}
|
2021-07-14 22:43:13 +08:00
|
|
|
ctx, cancel := installSignals()
|
|
|
|
defer cancel()
|
2019-12-28 05:15:04 +08:00
|
|
|
|
2020-05-29 11:04:44 +08:00
|
|
|
setup("hooks/pre-receive.log", c.Bool("debug"))
|
2019-12-28 05:15:04 +08:00
|
|
|
|
2017-02-23 11:40:44 +08:00
|
|
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
2019-11-15 06:39:48 +08:00
|
|
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail(`Rejecting changes as Gitea environment not set.
|
2019-11-15 06:39:48 +08:00
|
|
|
If you are pushing over SSH you must push with a key managed by
|
|
|
|
Gitea or set your environment appropriately.`, "")
|
|
|
|
}
|
2021-07-14 22:43:13 +08:00
|
|
|
return nil
|
2017-02-23 11:40:44 +08:00
|
|
|
}
|
2017-02-25 22:54:40 +08:00
|
|
|
|
2021-07-08 19:38:13 +08:00
|
|
|
// the environment is set by serv command
|
2022-05-09 00:46:32 +08:00
|
|
|
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
|
|
|
username := os.Getenv(repo_module.EnvRepoUsername)
|
|
|
|
reponame := os.Getenv(repo_module.EnvRepoName)
|
|
|
|
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
|
|
|
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
|
|
|
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
|
Implement actions (#21937)
Close #13539.
Co-authored by: @lunny @appleboy @fuxiaohei and others.
Related projects:
- https://gitea.com/gitea/actions-proto-def
- https://gitea.com/gitea/actions-proto-go
- https://gitea.com/gitea/act
- https://gitea.com/gitea/act_runner
### Summary
The target of this PR is to bring a basic implementation of "Actions",
an internal CI/CD system of Gitea. That means even though it has been
merged, the state of the feature is **EXPERIMENTAL**, and please note
that:
- It is disabled by default;
- It shouldn't be used in a production environment currently;
- It shouldn't be used in a public Gitea instance currently;
- Breaking changes may be made before it's stable.
**Please comment on #13539 if you have any different product design
ideas**, all decisions reached there will be adopted here. But in this
PR, we don't talk about **naming, feature-creep or alternatives**.
### ⚠️ Breaking
`gitea-actions` will become a reserved user name. If a user with the
name already exists in the database, it is recommended to rename it.
### Some important reviews
- What is `DEFAULT_ACTIONS_URL` in `app.ini` for?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1055954954
- Why the api for runners is not under the normal `/api/v1` prefix?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061173592
- Why DBFS?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061301178
- Why ignore events triggered by `gitea-actions` bot?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1063254103
- Why there's no permission control for actions?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1090229868
### What it looks like
<details>
#### Manage runners
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205870657-c72f590e-2e08-4cd4-be7f-2e0abb299bbf.png">
#### List runs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872794-50fde990-2b45-48c1-a178-908e4ec5b627.png">
#### View logs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872501-9b7b9000-9542-4991-8f55-18ccdada77c3.png">
</details>
### How to try it
<details>
#### 1. Start Gitea
Clone this branch and [install from
source](https://docs.gitea.io/en-us/install-from-source).
Add additional configurations in `app.ini` to enable Actions:
```ini
[actions]
ENABLED = true
```
Start it.
If all is well, you'll see the management page of runners:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205877365-8e30a780-9b10-4154-b3e8-ee6c3cb35a59.png">
#### 2. Start runner
Clone the [act_runner](https://gitea.com/gitea/act_runner), and follow
the
[README](https://gitea.com/gitea/act_runner/src/branch/main/README.md)
to start it.
If all is well, you'll see a new runner has been added:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205878000-216f5937-e696-470d-b66c-8473987d91c3.png">
#### 3. Enable actions for a repo
Create a new repo or open an existing one, check the `Actions` checkbox
in settings and submit.
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879705-53e09208-73c0-4b3e-a123-2dcf9aba4b9c.png">
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879383-23f3d08f-1a85-41dd-a8b3-54e2ee6453e8.png">
If all is well, you'll see a new tab "Actions":
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205881648-a8072d8c-5803-4d76-b8a8-9b2fb49516c1.png">
#### 4. Upload workflow files
Upload some workflow files to `.gitea/workflows/xxx.yaml`, you can
follow the [quickstart](https://docs.github.com/en/actions/quickstart)
of GitHub Actions. Yes, Gitea Actions is compatible with GitHub Actions
in most cases, you can use the same demo:
```yaml
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
```
If all is well, you'll see a new run in `Actions` tab:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884473-79a874bc-171b-4aaf-acd5-0241a45c3b53.png">
#### 5. Check the logs of jobs
Click a run and you'll see the logs:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884800-994b0374-67f7-48ff-be9a-4c53f3141547.png">
#### 6. Go on
You can try more examples in [the
documents](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
of GitHub Actions, then you might find a lot of bugs.
Come on, PRs are welcome.
</details>
See also: [Feature Preview: Gitea
Actions](https://blog.gitea.io/2022/12/feature-preview-gitea-actions/)
---------
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2023-01-31 09:45:19 +08:00
|
|
|
actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64)
|
2017-02-25 22:54:40 +08:00
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
hookOptions := private.HookOptions{
|
|
|
|
UserID: userID,
|
|
|
|
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
|
|
|
|
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
|
|
|
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
2020-08-24 00:02:35 +08:00
|
|
|
GitPushOptions: pushOptions(),
|
2021-06-24 03:38:19 +08:00
|
|
|
PullRequestID: prID,
|
2022-03-22 17:29:07 +08:00
|
|
|
DeployKeyID: deployKeyID,
|
Implement actions (#21937)
Close #13539.
Co-authored by: @lunny @appleboy @fuxiaohei and others.
Related projects:
- https://gitea.com/gitea/actions-proto-def
- https://gitea.com/gitea/actions-proto-go
- https://gitea.com/gitea/act
- https://gitea.com/gitea/act_runner
### Summary
The target of this PR is to bring a basic implementation of "Actions",
an internal CI/CD system of Gitea. That means even though it has been
merged, the state of the feature is **EXPERIMENTAL**, and please note
that:
- It is disabled by default;
- It shouldn't be used in a production environment currently;
- It shouldn't be used in a public Gitea instance currently;
- Breaking changes may be made before it's stable.
**Please comment on #13539 if you have any different product design
ideas**, all decisions reached there will be adopted here. But in this
PR, we don't talk about **naming, feature-creep or alternatives**.
### ⚠️ Breaking
`gitea-actions` will become a reserved user name. If a user with the
name already exists in the database, it is recommended to rename it.
### Some important reviews
- What is `DEFAULT_ACTIONS_URL` in `app.ini` for?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1055954954
- Why the api for runners is not under the normal `/api/v1` prefix?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061173592
- Why DBFS?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061301178
- Why ignore events triggered by `gitea-actions` bot?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1063254103
- Why there's no permission control for actions?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1090229868
### What it looks like
<details>
#### Manage runners
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205870657-c72f590e-2e08-4cd4-be7f-2e0abb299bbf.png">
#### List runs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872794-50fde990-2b45-48c1-a178-908e4ec5b627.png">
#### View logs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872501-9b7b9000-9542-4991-8f55-18ccdada77c3.png">
</details>
### How to try it
<details>
#### 1. Start Gitea
Clone this branch and [install from
source](https://docs.gitea.io/en-us/install-from-source).
Add additional configurations in `app.ini` to enable Actions:
```ini
[actions]
ENABLED = true
```
Start it.
If all is well, you'll see the management page of runners:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205877365-8e30a780-9b10-4154-b3e8-ee6c3cb35a59.png">
#### 2. Start runner
Clone the [act_runner](https://gitea.com/gitea/act_runner), and follow
the
[README](https://gitea.com/gitea/act_runner/src/branch/main/README.md)
to start it.
If all is well, you'll see a new runner has been added:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205878000-216f5937-e696-470d-b66c-8473987d91c3.png">
#### 3. Enable actions for a repo
Create a new repo or open an existing one, check the `Actions` checkbox
in settings and submit.
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879705-53e09208-73c0-4b3e-a123-2dcf9aba4b9c.png">
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879383-23f3d08f-1a85-41dd-a8b3-54e2ee6453e8.png">
If all is well, you'll see a new tab "Actions":
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205881648-a8072d8c-5803-4d76-b8a8-9b2fb49516c1.png">
#### 4. Upload workflow files
Upload some workflow files to `.gitea/workflows/xxx.yaml`, you can
follow the [quickstart](https://docs.github.com/en/actions/quickstart)
of GitHub Actions. Yes, Gitea Actions is compatible with GitHub Actions
in most cases, you can use the same demo:
```yaml
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
```
If all is well, you'll see a new run in `Actions` tab:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884473-79a874bc-171b-4aaf-acd5-0241a45c3b53.png">
#### 5. Check the logs of jobs
Click a run and you'll see the logs:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884800-994b0374-67f7-48ff-be9a-4c53f3141547.png">
#### 6. Go on
You can try more examples in [the
documents](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
of GitHub Actions, then you might find a lot of bugs.
Come on, PRs are welcome.
</details>
See also: [Feature Preview: Gitea
Actions](https://blog.gitea.io/2022/12/feature-preview-gitea-actions/)
---------
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2023-01-31 09:45:19 +08:00
|
|
|
ActionPerm: int(actionPerm),
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
|
2017-02-25 22:54:40 +08:00
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
oldCommitIDs := make([]string, hookBatchSize)
|
|
|
|
newCommitIDs := make([]string, hookBatchSize)
|
|
|
|
refFullNames := make([]string, hookBatchSize)
|
|
|
|
count := 0
|
|
|
|
total := 0
|
|
|
|
lastline := 0
|
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
var out io.Writer
|
|
|
|
out = &nilWriter{}
|
|
|
|
if setting.Git.VerbosePush {
|
|
|
|
if setting.Git.VerbosePushDelay > 0 {
|
|
|
|
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
|
|
|
|
defer dWriter.Close()
|
|
|
|
out = dWriter
|
|
|
|
} else {
|
|
|
|
out = os.Stdout
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-27 00:21:54 +08:00
|
|
|
supportProcReceive := false
|
2021-07-28 17:42:56 +08:00
|
|
|
if git.CheckGitVersionAtLeast("2.29") == nil {
|
2022-11-27 00:21:54 +08:00
|
|
|
supportProcReceive = true
|
2021-07-28 17:42:56 +08:00
|
|
|
}
|
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
for scanner.Scan() {
|
2017-02-25 22:54:40 +08:00
|
|
|
// TODO: support news feeds for wiki
|
|
|
|
if isWiki {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
fields := bytes.Fields(scanner.Bytes())
|
|
|
|
if len(fields) != 3 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-09-14 16:16:22 +08:00
|
|
|
oldCommitID := string(fields[0])
|
2017-02-25 22:54:40 +08:00
|
|
|
newCommitID := string(fields[1])
|
|
|
|
refFullName := string(fields[2])
|
2019-12-26 19:29:45 +08:00
|
|
|
total++
|
|
|
|
lastline++
|
2017-02-25 22:54:40 +08:00
|
|
|
|
2021-06-25 22:28:55 +08:00
|
|
|
// If the ref is a branch or tag, check if it's protected
|
2022-11-27 00:21:54 +08:00
|
|
|
// if supportProcReceive all ref should be checked because
|
2021-07-28 17:42:56 +08:00
|
|
|
// permission check was delayed
|
2022-11-27 00:21:54 +08:00
|
|
|
if supportProcReceive || strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
|
2019-12-26 19:29:45 +08:00
|
|
|
oldCommitIDs[count] = oldCommitID
|
|
|
|
newCommitIDs[count] = newCommitID
|
|
|
|
refFullNames[count] = refFullName
|
|
|
|
count++
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, "*")
|
2019-12-26 19:29:45 +08:00
|
|
|
|
|
|
|
if count >= hookBatchSize {
|
2021-06-25 22:28:55 +08:00
|
|
|
fmt.Fprintf(out, " Checking %d references\n", count)
|
2019-12-26 19:29:45 +08:00
|
|
|
|
|
|
|
hookOptions.OldCommitIDs = oldCommitIDs
|
|
|
|
hookOptions.NewCommitIDs = newCommitIDs
|
|
|
|
hookOptions.RefFullNames = refFullNames
|
2021-07-14 22:43:13 +08:00
|
|
|
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
2019-12-26 19:29:45 +08:00
|
|
|
switch statusCode {
|
|
|
|
case http.StatusOK:
|
|
|
|
// no-op
|
|
|
|
case http.StatusInternalServerError:
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail("Internal Server Error", msg)
|
2019-12-26 19:29:45 +08:00
|
|
|
default:
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail(msg, "")
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
count = 0
|
|
|
|
lastline = 0
|
2017-03-01 23:01:03 +08:00
|
|
|
}
|
2019-12-26 19:29:45 +08:00
|
|
|
} else {
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, ".")
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
if lastline >= hookBatchSize {
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, "\n")
|
2019-12-26 19:29:45 +08:00
|
|
|
lastline = 0
|
2017-02-25 22:54:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
if count > 0 {
|
|
|
|
hookOptions.OldCommitIDs = oldCommitIDs[:count]
|
|
|
|
hookOptions.NewCommitIDs = newCommitIDs[:count]
|
|
|
|
hookOptions.RefFullNames = refFullNames[:count]
|
|
|
|
|
2021-06-25 22:28:55 +08:00
|
|
|
fmt.Fprintf(out, " Checking %d references\n", count)
|
2019-12-26 19:29:45 +08:00
|
|
|
|
2021-07-14 22:43:13 +08:00
|
|
|
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
2019-12-26 19:29:45 +08:00
|
|
|
switch statusCode {
|
|
|
|
case http.StatusInternalServerError:
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail("Internal Server Error", msg)
|
2019-12-26 19:29:45 +08:00
|
|
|
case http.StatusForbidden:
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail(msg, "")
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
} else if lastline > 0 {
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, "\n")
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
2017-02-23 11:40:44 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func runHookUpdate(c *cli.Context) error {
|
2019-12-27 23:21:33 +08:00
|
|
|
// Update is empty and is kept only for backwards compatibility
|
2017-02-23 11:40:44 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func runHookPostReceive(c *cli.Context) error {
|
2021-07-14 22:43:13 +08:00
|
|
|
ctx, cancel := installSignals()
|
|
|
|
defer cancel()
|
|
|
|
|
2022-06-16 23:47:44 +08:00
|
|
|
setup("hooks/post-receive.log", c.Bool("debug"))
|
|
|
|
|
2020-10-14 00:24:06 +08:00
|
|
|
// First of all run update-server-info no matter what
|
2022-04-01 10:55:30 +08:00
|
|
|
if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
|
2022-10-25 03:29:17 +08:00
|
|
|
return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
|
2020-10-14 00:24:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now if we're an internal don't do anything else
|
2022-05-09 00:46:32 +08:00
|
|
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
2019-12-28 05:15:04 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-02-23 11:40:44 +08:00
|
|
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
2019-11-15 06:39:48 +08:00
|
|
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail(`Rejecting changes as Gitea environment not set.
|
2019-11-15 06:39:48 +08:00
|
|
|
If you are pushing over SSH you must push with a key managed by
|
|
|
|
Gitea or set your environment appropriately.`, "")
|
|
|
|
}
|
2021-07-14 22:43:13 +08:00
|
|
|
return nil
|
2017-02-23 11:40:44 +08:00
|
|
|
}
|
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
var out io.Writer
|
|
|
|
var dWriter *delayWriter
|
|
|
|
out = &nilWriter{}
|
|
|
|
if setting.Git.VerbosePush {
|
|
|
|
if setting.Git.VerbosePushDelay > 0 {
|
|
|
|
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
|
|
|
|
defer dWriter.Close()
|
|
|
|
out = dWriter
|
|
|
|
} else {
|
|
|
|
out = os.Stdout
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-08 19:38:13 +08:00
|
|
|
// the environment is set by serv command
|
2022-05-09 00:46:32 +08:00
|
|
|
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
|
|
|
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
|
|
|
repoName := os.Getenv(repo_module.EnvRepoName)
|
|
|
|
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
|
|
|
pusherName := os.Getenv(repo_module.EnvPusherName)
|
2017-02-25 22:54:40 +08:00
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
hookOptions := private.HookOptions{
|
|
|
|
UserName: pusherName,
|
|
|
|
UserID: pusherID,
|
|
|
|
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
|
|
|
|
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
|
|
|
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
2020-08-24 00:02:35 +08:00
|
|
|
GitPushOptions: pushOptions(),
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
oldCommitIDs := make([]string, hookBatchSize)
|
|
|
|
newCommitIDs := make([]string, hookBatchSize)
|
|
|
|
refFullNames := make([]string, hookBatchSize)
|
|
|
|
count := 0
|
|
|
|
total := 0
|
|
|
|
wasEmpty := false
|
|
|
|
masterPushed := false
|
|
|
|
results := make([]private.HookPostReceiveBranchResult, 0)
|
|
|
|
|
2017-02-25 22:54:40 +08:00
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
for scanner.Scan() {
|
|
|
|
// TODO: support news feeds for wiki
|
|
|
|
if isWiki {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
fields := bytes.Fields(scanner.Bytes())
|
|
|
|
if len(fields) != 3 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, ".")
|
2019-12-26 19:29:45 +08:00
|
|
|
oldCommitIDs[count] = string(fields[0])
|
|
|
|
newCommitIDs[count] = string(fields[1])
|
|
|
|
refFullNames[count] = string(fields[2])
|
|
|
|
if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
|
|
|
|
masterPushed = true
|
|
|
|
}
|
|
|
|
count++
|
|
|
|
total++
|
|
|
|
|
|
|
|
if count >= hookBatchSize {
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, " Processing %d references\n", count)
|
2019-12-26 19:29:45 +08:00
|
|
|
hookOptions.OldCommitIDs = oldCommitIDs
|
|
|
|
hookOptions.NewCommitIDs = newCommitIDs
|
|
|
|
hookOptions.RefFullNames = refFullNames
|
2021-07-14 22:43:13 +08:00
|
|
|
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
2019-12-26 19:29:45 +08:00
|
|
|
if resp == nil {
|
2020-01-12 16:46:03 +08:00
|
|
|
_ = dWriter.Close()
|
2019-12-26 19:29:45 +08:00
|
|
|
hookPrintResults(results)
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail("Internal Server Error", err)
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
wasEmpty = wasEmpty || resp.RepoWasEmpty
|
|
|
|
results = append(results, resp.Results...)
|
|
|
|
count = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if count == 0 {
|
|
|
|
if wasEmpty && masterPushed {
|
|
|
|
// We need to tell the repo to reset the default branch to master
|
2021-07-14 22:43:13 +08:00
|
|
|
err := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
2019-12-26 19:29:45 +08:00
|
|
|
if err != nil {
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
}
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
2019-12-26 19:29:45 +08:00
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
_ = dWriter.Close()
|
2019-12-26 19:29:45 +08:00
|
|
|
hookPrintResults(results)
|
|
|
|
return nil
|
|
|
|
}
|
2017-02-25 22:54:40 +08:00
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
hookOptions.OldCommitIDs = oldCommitIDs[:count]
|
|
|
|
hookOptions.NewCommitIDs = newCommitIDs[:count]
|
|
|
|
hookOptions.RefFullNames = refFullNames[:count]
|
2018-10-20 14:59:06 +08:00
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, " Processing %d references\n", count)
|
2019-12-26 19:29:45 +08:00
|
|
|
|
2021-07-14 22:43:13 +08:00
|
|
|
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
2019-12-26 19:29:45 +08:00
|
|
|
if resp == nil {
|
2020-01-12 16:46:03 +08:00
|
|
|
_ = dWriter.Close()
|
2019-12-26 19:29:45 +08:00
|
|
|
hookPrintResults(results)
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail("Internal Server Error", err)
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
|
|
|
wasEmpty = wasEmpty || resp.RepoWasEmpty
|
|
|
|
results = append(results, resp.Results...)
|
|
|
|
|
2020-01-12 16:46:03 +08:00
|
|
|
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
2019-12-26 19:29:45 +08:00
|
|
|
|
|
|
|
if wasEmpty && masterPushed {
|
|
|
|
// We need to tell the repo to reset the default branch to master
|
2021-07-14 22:43:13 +08:00
|
|
|
err := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
2019-12-26 19:29:45 +08:00
|
|
|
if err != nil {
|
2021-07-14 22:43:13 +08:00
|
|
|
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
|
2019-06-01 23:00:21 +08:00
|
|
|
}
|
2019-12-26 19:29:45 +08:00
|
|
|
}
|
2020-01-12 16:46:03 +08:00
|
|
|
_ = dWriter.Close()
|
2019-12-26 19:29:45 +08:00
|
|
|
hookPrintResults(results)
|
2018-10-20 14:59:06 +08:00
|
|
|
|
2019-12-26 19:29:45 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func hookPrintResults(results []private.HookPostReceiveBranchResult) {
|
|
|
|
for _, res := range results {
|
|
|
|
if !res.Message {
|
2019-06-01 23:00:21 +08:00
|
|
|
continue
|
2018-10-20 14:59:06 +08:00
|
|
|
}
|
|
|
|
|
2019-06-01 23:00:21 +08:00
|
|
|
fmt.Fprintln(os.Stderr, "")
|
2019-12-26 19:29:45 +08:00
|
|
|
if res.Create {
|
|
|
|
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
|
|
|
|
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
|
2019-06-01 23:00:21 +08:00
|
|
|
} else {
|
|
|
|
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
|
2019-12-26 19:29:45 +08:00
|
|
|
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
|
2019-06-01 23:00:21 +08:00
|
|
|
}
|
|
|
|
fmt.Fprintln(os.Stderr, "")
|
2019-12-26 19:29:45 +08:00
|
|
|
os.Stderr.Sync()
|
2017-02-25 22:54:40 +08:00
|
|
|
}
|
2017-02-23 11:40:44 +08:00
|
|
|
}
|
2020-08-24 00:02:35 +08:00
|
|
|
|
|
|
|
func pushOptions() map[string]string {
|
|
|
|
opts := make(map[string]string)
|
|
|
|
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
|
|
|
|
for idx := 0; idx < pushCount; idx++ {
|
|
|
|
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
|
|
|
|
kv := strings.SplitN(opt, "=", 2)
|
|
|
|
if len(kv) == 2 {
|
|
|
|
opts[kv[0]] = kv[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return opts
|
|
|
|
}
|
2021-07-28 17:42:56 +08:00
|
|
|
|
|
|
|
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)
|
2022-05-09 00:46:32 +08:00
|
|
|
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
|
|
|
repoName := os.Getenv(repo_module.EnvRepoName)
|
|
|
|
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
|
|
|
pusherName := os.Getenv(repo_module.EnvPusherName)
|
2021-07-28 17:42:56 +08:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
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])
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
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)
|
|
|
|
}
|
2022-06-20 18:02:49 +08:00
|
|
|
if lr != 4 {
|
2021-07-28 17:42:56 +08:00
|
|
|
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
|
|
|
|
}
|