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

Adjust error reporting from merge failures and use LC_ALL=C for git #8548

Merged
merged 33 commits into from
Nov 10, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
97b534f
Adjust error reporting from merge failures
zeripath Oct 15, 2019
33d1d28
Add errbuf resets
zeripath Oct 22, 2019
c1f49d8
Remove the -q as we want the message if there is a failure
zeripath Oct 22, 2019
137d700
save the stdout
zeripath Oct 22, 2019
a439b57
sanitize the error out
zeripath Oct 22, 2019
28d0f84
truncate message slightly and adjust message
zeripath Oct 22, 2019
e4eb801
in runes...
zeripath Oct 22, 2019
ad111ce
ReplaceAll is too new - use Replace(... -1)
zeripath Oct 22, 2019
2290433
Merge branch 'master' into merging-fixes
zeripath Oct 23, 2019
a1e23e9
Apply suggestions from code review
zeripath Oct 23, 2019
218368b
Merge branch 'master' into merging-fixes
zeripath Oct 29, 2019
d3eaac2
Set locale
zeripath Oct 30, 2019
b6077bb
Handle lots of types of error better
zeripath Oct 30, 2019
08cffb3
use fallthrough to reduce replication
zeripath Oct 30, 2019
252b605
More golangci-lint fixes
zeripath Oct 31, 2019
6b7855f
finally fixed golangci-lint
zeripath Oct 31, 2019
6511e18
Update modules/auth/repo_form.go
zeripath Oct 31, 2019
18300d0
update swagger
zeripath Oct 31, 2019
130f61a
Update command.go
zeripath Oct 31, 2019
d4be899
Merge branch 'master' into merging-fixes
zeripath Nov 1, 2019
6546958
Remove MergeStyleUnrelatedMerge
zeripath Nov 3, 2019
98ab798
Merge branch 'master' into merging-fixes
zeripath Nov 3, 2019
cef2489
remove mergeunrelated message in locale_en-US.ini
zeripath Nov 3, 2019
e56d6ce
Apply suggestions from code review
zeripath Nov 5, 2019
2954f0f
Switch to use Locale instead of LCALL name
zeripath Nov 5, 2019
ac66b00
Fix fmt
zeripath Nov 6, 2019
4f35d66
Fix fmt 2 (the revenge)
zeripath Nov 6, 2019
5a30d6f
Remove unused configurability from git.Command
zeripath Nov 6, 2019
c115d79
Add tests
zeripath Nov 9, 2019
08e0b49
Merge branch 'master' into merging-fixes
zeripath Nov 9, 2019
4462fe9
Merge branch 'master' into merging-fixes
zeripath Nov 9, 2019
9f38346
Merge branch 'master' into merging-fixes
zeripath Nov 10, 2019
a234bf1
Merge branch 'master' into merging-fixes
zeripath Nov 10, 2019
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
37 changes: 37 additions & 0 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,43 @@ func (err ErrInvalidMergeStyle) Error() string {
err.ID, err.Style)
}

// ErrMergeConflicts represents an error if merging fails with a conflict
type ErrMergeConflicts struct {
Style MergeStyle
StdOut string
StdErr string
Err error
}

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

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

// ErrRebaseConflicts represents an error if rebase fails with a conflict
type ErrRebaseConflicts struct {
Style MergeStyle
CommitSHA string
StdOut string
StdErr string
Err error
}

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

func (err ErrRebaseConflicts) Error() string {
return fmt.Sprintf("Rebase Error: %v: Whilst Rebasing: %s\n%s\n%s", err.Err, err.CommitSHA, err.StdErr, err.StdOut)
}

// _________ __
// \_ ___ \ ____ _____ _____ ____ _____/ |_
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,8 @@ pulls.rebase_merge_pull_request = Rebase and Merge
pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff)
pulls.squash_merge_pull_request = Squash and Merge
pulls.invalid_merge_option = You cannot use this merge option for this pull request.
pulls.merge_conflict = Merge Failed: There was a conflict whilst merging: %[1]s<br>%[2]s<br>Try a different strategy
pulls.rebase_conflict = Merge Failed: There was a conflict whilst rebasing commit: %s[1]<br>%[1]s<br>%[2]s<br>Try a different strategy
pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.`
pulls.status_checking = Some checks are pending
pulls.status_checks_success = All checks were successful
Expand Down
8 changes: 8 additions & 0 deletions routers/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,14 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
return
} else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
ctx.Flash.Error(ctx.Tr("repo.pulls.merge_conflict", conflictError.StdErr, conflictError.StdOut))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
zeripath marked this conversation as resolved.
Show resolved Hide resolved
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
ctx.Flash.Error(ctx.Tr("repo.pulls.rebase_conflict", conflictError.CommitSHA, conflictError.StdErr, conflictError.StdOut))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
zeripath marked this conversation as resolved.
Show resolved Hide resolved
}
ctx.ServerError("Merge", err)
return
Expand Down
89 changes: 84 additions & 5 deletions services/pull/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err)
}

var errbuf strings.Builder
var outbuf, errbuf strings.Builder
if err := git.NewCommand("remote", "add", "-t", pr.BaseBranch, "-m", pr.BaseBranch, "origin", baseGitRepo.Path).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git remote add [%s -> %s]: %s", baseGitRepo.Path, tmpBasePath, errbuf.String())
}
Expand Down Expand Up @@ -209,9 +209,20 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
// Merge commits.
switch mergeStyle {
case models.MergeStyleMerge:
if err := git.NewCommand("merge", "--no-ff", "--no-commit", trackingBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
if err := git.NewCommand("merge", "--no-ff", "--no-commit", trackingBranch).RunInDirPipeline(tmpBasePath, &outbuf, &errbuf); err != nil {
// Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil {
// We have a merge conflict error
return models.ErrMergeConflicts{
Style: mergeStyle,
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
}
return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, errbuf.String())
}
outbuf.Reset()

if signArg == "" {
if err := git.NewCommand("commit", "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
Expand All @@ -229,33 +240,89 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
}
// Rebase before merging
if err := git.NewCommand("rebase", "-q", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
// Rebase will leave a REBASE_HEAD file in .git if there is a conflict
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil {
// The original commit SHA1 that is failing will be in .git/rebase-apply/original-commit
commitShaBytes, readErr := ioutil.ReadFile(filepath.Join(tmpBasePath, ".git", "rebase-apply", "original-commit"))
if readErr != nil {
// Abandon this attempt to handle the error
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
return models.ErrRebaseConflicts{
Style: mergeStyle,
CommitSHA: strings.TrimSpace(string(commitShaBytes)),
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
}
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
outbuf.Reset()
// Checkout base branch again
if err := git.NewCommand("checkout", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git checkout: %s", errbuf.String())
}
// Merge fast forward
if err := git.NewCommand("merge", "--ff-only", "-q", stagingBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
if err := git.NewCommand("merge", "--ff-only", "-q", stagingBranch).RunInDirPipeline(tmpBasePath, &outbuf, &errbuf); err != nil {
// Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil {
// We have a merge conflict error
return models.ErrMergeConflicts{
Style: mergeStyle,
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
}
return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
outbuf.Reset()
zeripath marked this conversation as resolved.
Show resolved Hide resolved
case models.MergeStyleRebaseMerge:
// Checkout head branch
if err := git.NewCommand("checkout", "-b", stagingBranch, trackingBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git checkout: %s", errbuf.String())
}
// Rebase before merging
if err := git.NewCommand("rebase", "-q", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
if err := git.NewCommand("rebase", "-q", baseBranch).RunInDirPipeline(tmpBasePath, &outbuf, &errbuf); err != nil {
// Rebase will leave a REBASE_HEAD file in .git if there is a conflict
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil {
// The original commit SHA1 that is failing will be in .git/rebase-apply/original-commit
commitShaBytes, readErr := ioutil.ReadFile(filepath.Join(tmpBasePath, ".git", "rebase-apply", "original-commit"))
if readErr != nil {
// Abandon this attempt to handle the error
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
return models.ErrRebaseConflicts{
Style: mergeStyle,
CommitSHA: strings.TrimSpace(string(commitShaBytes)),
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
}
return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
outbuf.Reset()
// Checkout base branch again
if err := git.NewCommand("checkout", baseBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
return fmt.Errorf("git checkout: %s", errbuf.String())
}
// Prepare merge with commit
if err := git.NewCommand("merge", "--no-ff", "--no-commit", "-q", stagingBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
if err := git.NewCommand("merge", "--no-ff", "--no-commit", "-q", stagingBranch).RunInDirPipeline(tmpBasePath, &outbuf, &errbuf); err != nil {
// Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil {
// We have a merge conflict error
return models.ErrMergeConflicts{
Style: mergeStyle,
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
}
return fmt.Errorf("git merge --no-ff [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
outbuf.Reset()

// Set custom message and author and create merge commit
if signArg == "" {
Expand All @@ -271,8 +338,20 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
case models.MergeStyleSquash:
// Merge with squash
if err := git.NewCommand("merge", "-q", "--squash", trackingBranch).RunInDirPipeline(tmpBasePath, nil, &errbuf); err != nil {
// Merge will leave a MERGE_MSG file in the .git folder if there is a conflict
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_MSG")); statErr == nil {
// We have a merge conflict error
return models.ErrMergeConflicts{
Style: mergeStyle,
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
}
return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, errbuf.String())
}
outbuf.Reset()

sig := pr.Issue.Poster.NewGitSig()
if signArg == "" {
if err := git.NewCommand("commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, nil, &errbuf); err != nil {
Expand Down