mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-10-24 19:12:24 +00:00
As the docs of codeberg refer to the strings printed by the Forgejo ssh servers, this is user-facing and is nice to update to the new product name. (cherry picked from commit103991d73f) (cherry picked from commit2a0d3f85f1) (cherry picked from commiteb2b4ce388) (cherry picked from commit0998b51716) [BRANDING] forgejo log message (cherry picked from commitd51a046ebe) (cherry picked from commitd66e1c7b6e) (cherry picked from commitb5bffe4ce8) (cherry picked from commit3fa776d856) (cherry picked from commit18d064f472) (cherry picked from commitc95094e355) (cherry picked from commit5784290bc4) (cherry picked from commitaee336886b) (cherry picked from commitec2f60b516) (cherry picked from commit7af742a284) (cherry picked from commitf279e2a264) (cherry picked from commitfd38cfb14e) (cherry picked from commit64c8226618) (cherry picked from commitb546fb2304) (cherry picked from commitad10202177) (cherry picked from commitc89cab9c2b) (cherry picked from commit9579322ec2) (cherry picked from commit16b44ad18d) (cherry picked from commit2571ff703b) (cherry picked from commitad61d9ce9b) (cherry picked from commit9b2c45d4d3) (cherry picked from commited01b79a59) (cherry picked from commitd040b66427) (cherry picked from commitffe0bbea48) (cherry picked from commit4c1b2c409b) (cherry picked from commit3d8338ed10) (cherry picked from commita92f044ea9) [BRANDING] link to forgejo.org/docs instead of docs.gitea.io (cherry picked from commit3efafd0e08) (cherry picked from commit148185e34b) (cherry picked from commit834e264698) (cherry picked from commite72fa6eb1e) [BRANDING] link to forgejo.org/docs instead of docs.gitea.io Fix the link that was 404. (cherry picked from commitae515d7258) (cherry picked from commitfacc2367f0) (cherry picked from commit25784b9f21) (cherry picked from commit2efc6138d9) (cherry picked from commitb9d0871631) (cherry picked from commitf0446e51b9) (cherry picked from commit1638aa67fb) (cherry picked from commit290db6a018) (cherry picked from commit89b87cf542) (cherry picked from commit656ed94962) (cherry picked from commit036f879f96) (cherry picked from commit69eea35f81) (cherry picked from commitb72e3f4a92) (cherry picked from commitaf606b8574) (cherry picked from commit7e47f8135c) (cherry picked from commit0e5218cc53) (cherry picked from commit7c2a20a528) (cherry picked from commit4e94006363) (cherry picked from commite47cdfc43f) (cherry picked from commit1dcb3e1da4) (cherry picked from commit67367c4e0f) (cherry picked from commit252087d1ff) (cherry picked from commitf5977a43e5) Conflicts: templates/base/head_navbar.tmpl https://codeberg.org/forgejo/forgejo/pulls/1351 (cherry picked from commit594938eb15) (cherry picked from commit0257d038a7) (cherry picked from commit72821dd140) [BRANDING] s/gitea/forgejo/ in HTML placeholders Replaced Gitea branding with Forgejo for input placeholders Closes: #686 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/752 (cherry picked from commit6160d37ca9) (cherry picked from commitdf61138c7e) (cherry picked from commit1f30566c3f) (cherry picked from commit539bb825f5) (cherry picked from commitbee0f66c86) (cherry picked from commit60ad005c95) (cherry picked from commit282e26222e) (cherry picked from commitf9ca551f3d) (cherry picked from commitb2e04b04c3) (cherry picked from commitc8f395a03c) (cherry picked from commit0d58ce49ae) (cherry picked from commitc602ddf91e) (cherry picked from commit029e37271e) (cherry picked from commitfdaa96b3cc) (cherry picked from commit515d99e27d) (cherry picked from commitda73274ba1) (cherry picked from commitce90b696a0) (cherry picked from commitb6bf98763b) (cherry picked from commit5b380d22d7) [BRANDING] How to start a runner: URL to Actions admin documentation (cherry picked from commitda91799e6f) (cherry picked from commit28231663b6) (cherry picked from commit533a90345b) (cherry picked from commit6a0e4e55dd) (cherry picked from commitf47cd611c6) (cherry picked from commit001264b784) (cherry picked from commite4099e9bb9) (cherry picked from commit3a1885649f) (cherry picked from commitc42802c710) (cherry picked from commita611ce8d6d) (cherry picked from commita3d7d10a80) (cherry picked from commit52adde671f) (cherry picked from commitc9a3820fef) (cherry picked from commitdce40997c9) (cherry picked from commit312a6b92f3) [BRANDING] package templates & links - Change Gitea to Forgejo where necessary. - Point all documentation to Forgejo's documentation. - Resolves #992 (cherry picked from commitd0b78a6ede) (cherry picked from commite2382f30ba) (cherry picked from commitc41cf05a33) (cherry picked from commit797e598ae7) (cherry picked from commit970031a1c2) (cherry picked from commit0c1180e2e1) Conflicts: templates/package/content/alpine.tmpl templates/package/content/cargo.tmpl templates/package/content/chef.tmpl templates/package/content/composer.tmpl templates/package/content/conan.tmpl templates/package/content/conda.tmpl templates/package/content/container.tmpl templates/package/content/cran.tmpl templates/package/content/debian.tmpl templates/package/content/generic.tmpl templates/package/content/go.tmpl templates/package/content/helm.tmpl templates/package/content/maven.tmpl templates/package/content/npm.tmpl templates/package/content/nuget.tmpl templates/package/content/pub.tmpl templates/package/content/pypi.tmpl templates/package/content/rpm.tmpl templates/package/content/rubygems.tmpl templates/package/content/swift.tmpl templates/package/content/vagrant.tmpl https://codeberg.org/forgejo/forgejo/pulls/1351 (cherry picked from commit42ac9ff2ab) (cherry picked from commite390000bce) (cherry picked from commit56a437b29b) Conflicts: templates/package/content/cargo.tmpl https://codeberg.org/forgejo/forgejo/pulls/1466 [BRANDING] s/Gitea/Forgejo/ in user visible help & comments - Modify the README of the docker directory to point to the relevant docker files and documentation for Forgejo. (cherry picked from commitaca6371215) (cherry picked from commit0ba96b1bc4) (cherry picked from commit5c8e6b53f1) Conflicts: docker/README.md https://codeberg.org/forgejo/forgejo/pulls/1351 (cherry picked from commitb3121c8004) (cherry picked from commit607f870416) (cherry picked from commit191d96afe4) [BRANDING] healthcheck/check.go (cherry picked from commitd703a236ce) (cherry picked from commitd84ce3ff20) (cherry picked from commit2dbb844606) (cherry picked from commit14d3ae7e3a) [BRANDING] s/Gitea/Forgejo/g in CLI output (cherry picked from commit7543c126bb) (cherry picked from commitb66f422fc3) (cherry picked from commita81e4e46f3) [BRANDING] Gitea->Forgejo in mailer code (cherry picked from commitb91afea4ff) (cherry picked from commit5d7428167c) (cherry picked from commited8101ba6c) [BRANDING] use 'Forgejo' for Discord, Packagist, and Slack webhooks Refs: https://codeberg.org/forgejo/forgejo/issues/1387 (cherry picked from commit7dc3a05f5b) (cherry picked from commit133f2fc6cc) [BRANDING] cmd/manager.go (cherry picked from commitd1dba2c79d) [BRANDING] pyproject.toml (cherry picked from commit7e8c868db2) (cherry picked from commit2395995c8b) (cherry picked from commitdd6fbbf332) Conflicts: templates/package/content/cargo.tmpl https://codeberg.org/forgejo/forgejo/pulls/1548 (cherry picked from commit6f9a5d5cab) (cherry picked from commitd0635c4a07)
792 lines
20 KiB
Go
792 lines
20 KiB
Go
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/private"
|
|
repo_module "code.gitea.io/gitea/modules/repository"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
const (
|
|
hookBatchSize = 30
|
|
)
|
|
|
|
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",
|
|
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
|
Subcommands: []*cli.Command{
|
|
subcmdHookPreReceive,
|
|
subcmdHookUpdate,
|
|
subcmdHookPostReceive,
|
|
subcmdHookProcReceive,
|
|
},
|
|
}
|
|
|
|
subcmdHookPreReceive = &cli.Command{
|
|
Name: "pre-receive",
|
|
Usage: "Delegate pre-receive Git hook",
|
|
Description: "This command should only be called by Git",
|
|
Action: runHookPreReceive,
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "debug",
|
|
},
|
|
},
|
|
}
|
|
subcmdHookUpdate = &cli.Command{
|
|
Name: "update",
|
|
Usage: "Delegate update Git hook",
|
|
Description: "This command should only be called by Git",
|
|
Action: runHookUpdate,
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "debug",
|
|
},
|
|
},
|
|
}
|
|
subcmdHookPostReceive = &cli.Command{
|
|
Name: "post-receive",
|
|
Usage: "Delegate post-receive Git hook",
|
|
Description: "This command should only be called by Git",
|
|
Action: runHookPostReceive,
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "debug",
|
|
},
|
|
},
|
|
}
|
|
// 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 {
|
|
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
|
|
}
|
|
stopped := d.timer.Stop()
|
|
if stopped || d.buf == nil {
|
|
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
|
|
}
|
|
|
|
func runHookPreReceive(c *cli.Context) error {
|
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
|
return nil
|
|
}
|
|
ctx, cancel := installSignals()
|
|
defer cancel()
|
|
|
|
setup(ctx, c.Bool("debug"))
|
|
|
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
|
return fail(ctx, `Rejecting changes as Forgejo environment not set.
|
|
If you are pushing over SSH you must push with a key managed by
|
|
Forgejo or set your environment appropriately.`, "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// the environment is set by serv command
|
|
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)
|
|
actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64)
|
|
|
|
hookOptions := private.HookOptions{
|
|
UserID: userID,
|
|
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
|
|
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
|
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
|
GitPushOptions: pushOptions(),
|
|
PullRequestID: prID,
|
|
DeployKeyID: deployKeyID,
|
|
ActionPerm: int(actionPerm),
|
|
}
|
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
oldCommitIDs := make([]string, hookBatchSize)
|
|
newCommitIDs := make([]string, hookBatchSize)
|
|
refFullNames := make([]git.RefName, hookBatchSize)
|
|
count := 0
|
|
total := 0
|
|
lastline := 0
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
supportProcReceive := false
|
|
if git.CheckGitVersionAtLeast("2.29") == nil {
|
|
supportProcReceive = true
|
|
}
|
|
|
|
for scanner.Scan() {
|
|
// TODO: support news feeds for wiki
|
|
if isWiki {
|
|
continue
|
|
}
|
|
|
|
fields := bytes.Fields(scanner.Bytes())
|
|
if len(fields) != 3 {
|
|
continue
|
|
}
|
|
|
|
oldCommitID := string(fields[0])
|
|
newCommitID := string(fields[1])
|
|
refFullName := git.RefName(fields[2])
|
|
total++
|
|
lastline++
|
|
|
|
// If the ref is a branch or tag, check if it's protected
|
|
// if supportProcReceive all ref should be checked because
|
|
// permission check was delayed
|
|
if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() {
|
|
oldCommitIDs[count] = oldCommitID
|
|
newCommitIDs[count] = newCommitID
|
|
refFullNames[count] = refFullName
|
|
count++
|
|
fmt.Fprintf(out, "*")
|
|
|
|
if count >= hookBatchSize {
|
|
fmt.Fprintf(out, " Checking %d references\n", count)
|
|
|
|
hookOptions.OldCommitIDs = oldCommitIDs
|
|
hookOptions.NewCommitIDs = newCommitIDs
|
|
hookOptions.RefFullNames = refFullNames
|
|
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
|
if extra.HasError() {
|
|
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
|
}
|
|
count = 0
|
|
lastline = 0
|
|
}
|
|
} else {
|
|
fmt.Fprintf(out, ".")
|
|
}
|
|
if lastline >= hookBatchSize {
|
|
fmt.Fprintf(out, "\n")
|
|
lastline = 0
|
|
}
|
|
}
|
|
|
|
if count > 0 {
|
|
hookOptions.OldCommitIDs = oldCommitIDs[:count]
|
|
hookOptions.NewCommitIDs = newCommitIDs[:count]
|
|
hookOptions.RefFullNames = refFullNames[:count]
|
|
|
|
fmt.Fprintf(out, " Checking %d references\n", count)
|
|
|
|
extra := private.HookPreReceive(ctx, username, reponame, hookOptions)
|
|
if extra.HasError() {
|
|
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
|
|
}
|
|
} else if lastline > 0 {
|
|
fmt.Fprintf(out, "\n")
|
|
}
|
|
|
|
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
|
return nil
|
|
}
|
|
|
|
func runHookUpdate(c *cli.Context) error {
|
|
// Update is empty and is kept only for backwards compatibility
|
|
return nil
|
|
}
|
|
|
|
func runHookPostReceive(c *cli.Context) error {
|
|
ctx, cancel := installSignals()
|
|
defer cancel()
|
|
|
|
setup(ctx, c.Bool("debug"))
|
|
|
|
// First of all run update-server-info no matter what
|
|
if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
|
|
return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
|
|
}
|
|
|
|
// Now if we're an internal don't do anything else
|
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
|
return nil
|
|
}
|
|
|
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
|
return fail(ctx, `Rejecting changes as Forgejo environment not set.
|
|
If you are pushing over SSH you must push with a key managed by
|
|
Forgejo or set your environment appropriately.`, "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
// the environment is set by serv command
|
|
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)
|
|
|
|
hookOptions := private.HookOptions{
|
|
UserName: pusherName,
|
|
UserID: pusherID,
|
|
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
|
|
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
|
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
|
GitPushOptions: pushOptions(),
|
|
}
|
|
oldCommitIDs := make([]string, hookBatchSize)
|
|
newCommitIDs := make([]string, hookBatchSize)
|
|
refFullNames := make([]git.RefName, hookBatchSize)
|
|
count := 0
|
|
total := 0
|
|
wasEmpty := false
|
|
masterPushed := false
|
|
results := make([]private.HookPostReceiveBranchResult, 0)
|
|
|
|
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
|
|
}
|
|
|
|
fmt.Fprintf(out, ".")
|
|
oldCommitIDs[count] = string(fields[0])
|
|
newCommitIDs[count] = string(fields[1])
|
|
refFullNames[count] = git.RefName(fields[2])
|
|
if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
|
|
masterPushed = true
|
|
}
|
|
count++
|
|
total++
|
|
|
|
if count >= hookBatchSize {
|
|
fmt.Fprintf(out, " Processing %d references\n", count)
|
|
hookOptions.OldCommitIDs = oldCommitIDs
|
|
hookOptions.NewCommitIDs = newCommitIDs
|
|
hookOptions.RefFullNames = refFullNames
|
|
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
|
if extra.HasError() {
|
|
_ = dWriter.Close()
|
|
hookPrintResults(results)
|
|
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
|
}
|
|
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
|
|
extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
|
if extra.HasError() {
|
|
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
|
|
}
|
|
}
|
|
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
|
|
|
_ = dWriter.Close()
|
|
hookPrintResults(results)
|
|
return nil
|
|
}
|
|
|
|
hookOptions.OldCommitIDs = oldCommitIDs[:count]
|
|
hookOptions.NewCommitIDs = newCommitIDs[:count]
|
|
hookOptions.RefFullNames = refFullNames[:count]
|
|
|
|
fmt.Fprintf(out, " Processing %d references\n", count)
|
|
|
|
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
|
if resp == nil {
|
|
_ = dWriter.Close()
|
|
hookPrintResults(results)
|
|
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
|
}
|
|
wasEmpty = wasEmpty || resp.RepoWasEmpty
|
|
results = append(results, resp.Results...)
|
|
|
|
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
|
|
|
if wasEmpty && masterPushed {
|
|
// We need to tell the repo to reset the default branch to master
|
|
extra := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
|
|
if extra.HasError() {
|
|
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
|
|
}
|
|
}
|
|
_ = dWriter.Close()
|
|
hookPrintResults(results)
|
|
|
|
return nil
|
|
}
|
|
|
|
func hookPrintResults(results []private.HookPostReceiveBranchResult) {
|
|
for _, res := range results {
|
|
if !res.Message {
|
|
continue
|
|
}
|
|
|
|
fmt.Fprintln(os.Stderr, "")
|
|
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)
|
|
} else {
|
|
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
|
|
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
|
|
}
|
|
fmt.Fprintln(os.Stderr, "")
|
|
os.Stderr.Sync()
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func runHookProcReceive(c *cli.Context) error {
|
|
ctx, cancel := installSignals()
|
|
defer cancel()
|
|
|
|
setup(ctx, c.Bool("debug"))
|
|
|
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
|
return fail(ctx, `Rejecting changes as Forgejo environment not set.
|
|
If you are pushing over SSH you must push with a key managed by
|
|
Forgejo or set your environment appropriately.`, "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if git.CheckGitVersionAtLeast("2.29") != nil {
|
|
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
|
}
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
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)
|
|
|
|
// 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(ctx, 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(ctx, "Protocol: format 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(ctx, "Protocol: format error", "pkt-line: format error "+fmt.Sprint(rs.Data))
|
|
}
|
|
}
|
|
|
|
if string(rs.Data[0:index]) != VersionHead {
|
|
return fail(ctx, "Protocol: version 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(ctx, reader, pktLineTypeFlush)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeDataPktLine(ctx, os.Stdout, response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeFlushPktLine(ctx, 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([]git.RefName, 0, hookBatchSize)
|
|
|
|
for {
|
|
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
|
|
rs, err = readPktLine(ctx, 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, git.RefName(t[2]))
|
|
}
|
|
|
|
hookOptions.GitPushOptions = make(map[string]string)
|
|
|
|
if hasPushOptions {
|
|
for {
|
|
rs, err = readPktLine(ctx, 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, extra := private.HookProcReceive(ctx, repoUser, repoName, hookOptions)
|
|
if extra.HasError() {
|
|
return fail(ctx, extra.UserMsg, "HookProcReceive failed: %v", extra.Error)
|
|
}
|
|
|
|
// 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(ctx, os.Stdout, []byte("ng "+rs.OriginalRef.String()+" "+rs.Err))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
if rs.IsNotMatched {
|
|
err = writeDataPktLine(ctx, os.Stdout, []byte("ok "+rs.OriginalRef.String()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = writeDataPktLine(ctx, os.Stdout, []byte("option fall-through"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
err = writeDataPktLine(ctx, os.Stdout, []byte("ok "+rs.OriginalRef))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = writeDataPktLine(ctx, os.Stdout, []byte("option refname "+rs.Ref))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rs.OldOID != git.EmptySHA {
|
|
err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = writeDataPktLine(ctx, os.Stdout, []byte("option new-oid "+rs.NewOID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rs.IsForcePush {
|
|
err = writeDataPktLine(ctx, os.Stdout, []byte("option forced-update"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
err = writeFlushPktLine(ctx, 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(ctx context.Context, 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(ctx, "Protocol: stdin 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(ctx, "Protocol: format parse error", "Pkt-Line format is wrong :%v", err)
|
|
}
|
|
|
|
if r.Length == 0 {
|
|
if requestType == pktLineTypeData {
|
|
return nil, fail(ctx, "Protocol: format data error", "Pkt-Line format is wrong")
|
|
}
|
|
r.Type = pktLineTypeFlush
|
|
return r, nil
|
|
}
|
|
|
|
if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush {
|
|
return nil, fail(ctx, "Protocol: format length 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(ctx, "Protocol: data error", "Pkt-Line: read stdin failed : %v", err)
|
|
}
|
|
}
|
|
|
|
r.Type = pktLineTypeData
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func writeFlushPktLine(ctx context.Context, out io.Writer) error {
|
|
l, err := out.Write([]byte("0000"))
|
|
if err != nil || l != 4 {
|
|
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeDataPktLine(ctx context.Context, 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 || lr != 4 {
|
|
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
|
|
}
|
|
|
|
lr, err = out.Write(data)
|
|
if err != nil || int(length-4) != lr {
|
|
return fail(ctx, "Protocol: write error", "Pkt-Line response failed: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|