Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Handle push rejection in branch and upload #10854

Merged
merged 2 commits into from
Mar 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 0 additions & 66 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package models

import (
"fmt"
"strings"

"code.gitea.io/gitea/modules/git"
)
Expand Down Expand Up @@ -1388,71 +1387,6 @@ func (err ErrMergeUnrelatedHistories) Error() string {
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}

// ErrMergePushOutOfDate represents an error if merging fails due to unrelated histories
type ErrMergePushOutOfDate struct {
Style MergeStyle
StdOut string
StdErr string
Err error
}

// IsErrMergePushOutOfDate checks if an error is a ErrMergePushOutOfDate.
func IsErrMergePushOutOfDate(err error) bool {
_, ok := err.(ErrMergePushOutOfDate)
return ok
}

func (err ErrMergePushOutOfDate) Error() string {
return fmt.Sprintf("Merge PushOutOfDate Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}

// ErrPushRejected represents an error if merging fails due to rejection from a hook
type ErrPushRejected struct {
Style MergeStyle
Message string
StdOut string
StdErr string
Err error
}

// IsErrPushRejected checks if an error is a ErrPushRejected.
func IsErrPushRejected(err error) bool {
_, ok := err.(ErrPushRejected)
return ok
}

func (err ErrPushRejected) Error() string {
return fmt.Sprintf("Merge PushRejected Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}

// GenerateMessage generates the remote message from the stderr
func (err *ErrPushRejected) GenerateMessage() {
messageBuilder := &strings.Builder{}
i := strings.Index(err.StdErr, "remote: ")
if i < 0 {
err.Message = ""
return
}
for {
if len(err.StdErr) <= i+8 {
break
}
if err.StdErr[i:i+8] != "remote: " {
break
}
i += 8
nl := strings.IndexByte(err.StdErr[i:], '\n')
if nl >= 0 {
messageBuilder.WriteString(err.StdErr[i : i+nl+1])
i = i + nl + 1
} else {
messageBuilder.WriteString(err.StdErr[i:])
i = len(err.StdErr)
}
}
err.Message = strings.TrimSpace(messageBuilder.String())
}

// ErrRebaseConflicts represents an error if rebase fails with a conflict
type ErrRebaseConflicts struct {
Style MergeStyle
Expand Down
74 changes: 74 additions & 0 deletions modules/git/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package git

import (
"fmt"
"strings"
"time"
)

Expand Down Expand Up @@ -85,3 +86,76 @@ func IsErrBranchNotExist(err error) bool {
func (err ErrBranchNotExist) Error() string {
return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
}

// ErrPushOutOfDate represents an error if merging fails due to unrelated histories
type ErrPushOutOfDate struct {
StdOut string
StdErr string
Err error
}

// IsErrPushOutOfDate checks if an error is a ErrPushOutOfDate.
func IsErrPushOutOfDate(err error) bool {
_, ok := err.(*ErrPushOutOfDate)
return ok
}

func (err *ErrPushOutOfDate) Error() string {
return fmt.Sprintf("PushOutOfDate Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}

// Unwrap unwraps the underlying error
func (err *ErrPushOutOfDate) Unwrap() error {
return fmt.Errorf("%v - %s", err.Err, err.StdErr)
}

// ErrPushRejected represents an error if merging fails due to rejection from a hook
type ErrPushRejected struct {
Message string
StdOut string
StdErr string
Err error
}

// IsErrPushRejected checks if an error is a ErrPushRejected.
func IsErrPushRejected(err error) bool {
_, ok := err.(*ErrPushRejected)
return ok
}

func (err *ErrPushRejected) Error() string {
return fmt.Sprintf("PushRejected Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}

// Unwrap unwraps the underlying error
func (err *ErrPushRejected) Unwrap() error {
return fmt.Errorf("%v - %s", err.Err, err.StdErr)
}

// GenerateMessage generates the remote message from the stderr
func (err *ErrPushRejected) GenerateMessage() {
messageBuilder := &strings.Builder{}
i := strings.Index(err.StdErr, "remote: ")
if i < 0 {
err.Message = ""
return
}
for {
if len(err.StdErr) <= i+8 {
break
}
if err.StdErr[i:i+8] != "remote: " {
break
}
i += 8
nl := strings.IndexByte(err.StdErr[i:], '\n')
if nl >= 0 {
messageBuilder.WriteString(err.StdErr[i : i+nl+1])
i = i + nl + 1
} else {
messageBuilder.WriteString(err.StdErr[i:])
i = len(err.StdErr)
}
}
err.Message = strings.TrimSpace(messageBuilder.String())
}
26 changes: 25 additions & 1 deletion modules/git/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,31 @@ func Push(repoPath string, opts PushOptions) error {
cmd.AddArguments("-f")
}
cmd.AddArguments("--", opts.Remote, opts.Branch)
_, err := cmd.RunInDirWithEnv(repoPath, opts.Env)
var outbuf, errbuf strings.Builder

err := cmd.RunInDirTimeoutEnvPipeline(opts.Env, -1, repoPath, &outbuf, &errbuf)
if err != nil {
if strings.Contains(errbuf.String(), "non-fast-forward") {
return &ErrPushOutOfDate{
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
} else if strings.Contains(errbuf.String(), "! [remote rejected]") {
err := &ErrPushRejected{
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
err.GenerateMessage()
return err
}
}

if errbuf.Len() > 0 && err != nil {
return fmt.Errorf("%v - %s", err, errbuf.String())
}

return err
}

Expand Down
35 changes: 13 additions & 22 deletions modules/repofiles/temp_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,30 +242,21 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models
func (t *TemporaryUploadRepository) Push(doer *models.User, commitHash string, branch string) error {
// Because calls hooks we need to pass in the environment
env := models.PushingEnvironment(doer, t.repo)
stdout := &strings.Builder{}
stderr := &strings.Builder{}

if err := git.NewCommand("push", t.repo.RepoPath(), strings.TrimSpace(commitHash)+":refs/heads/"+strings.TrimSpace(branch)).RunInDirTimeoutEnvPipeline(env, -1, t.basePath, stdout, stderr); err != nil {
errString := stderr.String()
if strings.Contains(errString, "non-fast-forward") {
return models.ErrMergePushOutOfDate{
StdOut: stdout.String(),
StdErr: errString,
Err: err,
}
} else if strings.Contains(errString, "! [remote rejected]") {
log.Error("Unable to push back to repo from temporary repo due to rejection: %s (%s)\nStdout: %s\nStderr: %s\nError: %v",
t.repo.FullName(), t.basePath, stdout, errString, err)
err := models.ErrPushRejected{
StdOut: stdout.String(),
StdErr: errString,
Err: err,
}
err.GenerateMessage()
if err := git.Push(t.basePath, git.PushOptions{
Remote: t.repo.RepoPath(),
Branch: strings.TrimSpace(commitHash) + ":refs/heads/" + strings.TrimSpace(branch),
Env: env,
}); err != nil {
if git.IsErrPushOutOfDate(err) {
return err
} else if git.IsErrPushRejected(err) {
rejectErr := err.(*git.ErrPushRejected)
log.Info("Unable to push back to repo from temporary repo due to rejection: %s (%s)\nStdout: %s\nStderr: %s\nError: %v",
t.repo.FullName(), t.basePath, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
return err
}
log.Error("Unable to push back to repo from temporary repo: %s (%s)\nStdout: %s\nError: %v",
t.repo.FullName(), t.basePath, stdout, err)
log.Error("Unable to push back to repo from temporary repo: %s (%s)\nError: %v",
t.repo.FullName(), t.basePath, err)
return fmt.Errorf("Unable to push back to repo from temporary repo: %s (%s) Error: %v",
t.repo.FullName(), t.basePath, err)
}
Expand Down
1 change: 1 addition & 0 deletions modules/repofiles/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up

// Then push this tree to NewBranch
if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
log.Error("%T %v", err, err)
return nil, err
}

Expand Down
6 changes: 6 additions & 0 deletions modules/repository/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ func CreateNewBranch(doer *models.User, repo *models.Repository, oldBranchName,
Branch: branchName,
Env: models.PushingEnvironment(doer, repo),
}); err != nil {
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
return fmt.Errorf("Push: %v", err)
}

Expand Down Expand Up @@ -156,6 +159,9 @@ func CreateNewBranchFromCommit(doer *models.User, repo *models.Repository, commi
Branch: branchName,
Env: models.PushingEnvironment(doer, repo),
}); err != nil {
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
return fmt.Errorf("Push: %v", err)
}

Expand Down
6 changes: 3 additions & 3 deletions routers/api/v1/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,11 +678,11 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
} else if models.IsErrMergeUnrelatedHistories(err) {
conflictError := err.(models.ErrMergeUnrelatedHistories)
ctx.JSON(http.StatusConflict, conflictError)
} else if models.IsErrMergePushOutOfDate(err) {
} else if git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "Merge", "merge push out of date")
return
} else if models.IsErrPushRejected(err) {
errPushRej := err.(models.ErrPushRejected)
} else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 {
ctx.Error(http.StatusConflict, "Merge", "PushRejected without remote error message")
return
Expand Down
16 changes: 13 additions & 3 deletions routers/repo/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/repofiles"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/utils"
)

const (
Expand Down Expand Up @@ -335,9 +336,8 @@ func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
if models.IsErrBranchAlreadyExists(err) {
e := err.(models.ErrBranchAlreadyExists)
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", e.BranchName))
if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
Expand All @@ -347,6 +347,16 @@ func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
if git.IsErrPushRejected(err) {
e := err.(*git.ErrPushRejected)
if len(e.Message) == 0 {
ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message"))
} else {
ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(e.Message)))
}
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}

ctx.ServerError("CreateNewBranch", err)
return
Expand Down
Loading