// Copyright 2017 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package cmd import ( "bufio" "bytes" "context" "fmt" "io" "os" "strconv" "strings" "time" "forgejo.org/modules/git" "forgejo.org/modules/git/pushoptions" "forgejo.org/modules/log" "forgejo.org/modules/private" repo_module "forgejo.org/modules/repository" "forgejo.org/modules/setting" "github.com/urfave/cli/v2") const ( hookBatchSize = 30) var ( // CmdHook represents the available hooks sub-command. CmdHook = &cli.Command{ Name: "hook", Usage: "(internal) Should only be called by Git", Description: "Delegate commands to corresponding Git hooks", 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.timer.Stop() { d.buf = nil } if 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"), true) 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 byForgejo 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.New().ReadEnv().Map(), 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 := git.CheckGitVersionAtLeast("2.29") == nil 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} // runHookUpdate process the update hook: https://git-scm.com/docs/githooks#updatefunc runHookUpdate(c *cli.Context) error { // Now if we're an internal don't do anything else if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } ctx, cancel := installSignals() defer cancel() if c.NArg() != 3 { return nil } args := c.Args().Slice() // The arguments given to the hook are in order: reference name, old commit ID and new commit ID. refFullName := git.RefName(args[0]) newCommitID := args[2] // Only process pull references. if !refFullName.IsPull() { return nil } // Empty new commit ID means deletion. if git.IsEmptyCommitID(newCommitID, nil) { return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "") } // If the new comment isn't empty it means modification. return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "")} func runHookPostReceive(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() setup(ctx, c.Bool("debug"), true) // 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 byForgejo or set your environment appropriately.`, "") } return nil } 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 } } // 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) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 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.New().ReadEnv().Map(), PullRequestID: prID, PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)), } 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" && !git.IsEmptyCommitID(newCommitIDs[count], nil) && 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() { 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) 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 { 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) } } 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 runHookProcReceive(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() setup(ctx, c.Bool("debug"), true) 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 byForgejo or set your environment appropriately.`, "")Lines 1–500 of 793. Load more
// Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package cmd import ( "bufio" "bytes" "context" "fmt" "io" "os" "strconv" "strings" "time" "forgejo.org/modules/git" "forgejo.org/modules/git/pushoptions" "forgejo.org/modules/log" "forgejo.org/modules/private" repo_module "forgejo.org/modules/repository" "forgejo.org/modules/setting" "github.com/urfave/cli/v2" ) const ( hookBatchSize = 30 ) var ( // CmdHook represents the available hooks sub-command. CmdHook = &cli.Command{ Name: "hook", Usage: "(internal) Should only be called by Git", Description: "Delegate commands to corresponding Git hooks", 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.timer.Stop() { d.buf = nil } if 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"), true) 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.New().ReadEnv().Map(), 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 := git.CheckGitVersionAtLeast("2.29") == nil 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 } // runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update func runHookUpdate(c *cli.Context) error { // Now if we're an internal don't do anything else if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } ctx, cancel := installSignals() defer cancel() if c.NArg() != 3 { return nil } args := c.Args().Slice() // The arguments given to the hook are in order: reference name, old commit ID and new commit ID. refFullName := git.RefName(args[0]) newCommitID := args[2] // Only process pull references. if !refFullName.IsPull() { return nil } // Empty new commit ID means deletion. if git.IsEmptyCommitID(newCommitID, nil) { return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "") } // If the new comment isn't empty it means modification. return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "") } func runHookPostReceive(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() setup(ctx, c.Bool("debug"), true) // 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 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) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 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.New().ReadEnv().Map(), PullRequestID: prID, PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)), } 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" && !git.IsEmptyCommitID(newCommitIDs[count], nil) && 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() { 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) 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 { 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) } } 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 runHookProcReceive(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() setup(ctx, c.Bool("debug"), true) 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.`, "")