mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-05 12:08:31 +00:00
Split Bitbucket Server comments if over max length
- Refactor the comment split method into a common package
This commit is contained in:
@@ -11,11 +11,17 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/runatlantis/atlantis/server/events/vcs/common"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
// maxCommentLength is the maximum number of chars allowed by Bitbucket in a
|
||||
// single comment.
|
||||
const maxCommentLength = 32768
|
||||
|
||||
type Client struct {
|
||||
HttpClient *http.Client
|
||||
Username string
|
||||
@@ -117,8 +123,22 @@ func (b *Client) GetProjectKey(repoName string, cloneURL string) (string, error)
|
||||
return matches[1], nil
|
||||
}
|
||||
|
||||
// CreateComment creates a comment on the merge request.
|
||||
// CreateComment creates a comment on the merge request. It will write multiple
|
||||
// comments if a single comment is too long.
|
||||
func (b *Client) CreateComment(repo models.Repo, pullNum int, comment string) error {
|
||||
sepEnd := "\n```\n**Warning**: Output length greater than max comment size. Continued in next comment."
|
||||
sepStart := "Continued from previous comment.\n```diff\n"
|
||||
comments := common.SplitComment(comment, maxCommentLength, sepEnd, sepStart)
|
||||
for _, c := range comments {
|
||||
if err := b.postComment(repo, pullNum, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// postComment actually posts the comment. It's a helper for CreateComment().
|
||||
func (b *Client) postComment(repo models.Repo, pullNum int, comment string) error {
|
||||
bodyBytes, err := json.Marshal(map[string]string{"text": comment})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "json encoding")
|
||||
|
||||
37
server/events/vcs/common/comment_splitter.go
Normal file
37
server/events/vcs/common/comment_splitter.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// SplitComment splits comment into a slice of comments that are under maxSize.
|
||||
// It appends sepEnd to all comments that have a following comment.
|
||||
// It prepends sepStart to all comments that have a preceding comment.
|
||||
func SplitComment(comment string, maxSize int, sepEnd string, sepStart string) []string {
|
||||
if len(comment) <= maxSize {
|
||||
return []string{comment}
|
||||
}
|
||||
|
||||
maxWithSep := maxSize - len(sepEnd) - len(sepStart)
|
||||
var comments []string
|
||||
numComments := int(math.Ceil(float64(len(comment)) / float64(maxWithSep)))
|
||||
for i := 0; i < numComments; i++ {
|
||||
upTo := min(len(comment), (i+1)*maxWithSep)
|
||||
portion := comment[i*maxWithSep : upTo]
|
||||
if i < numComments-1 {
|
||||
portion += sepEnd
|
||||
}
|
||||
if i > 0 {
|
||||
portion = sepStart + portion
|
||||
}
|
||||
comments = append(comments, portion)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
63
server/events/vcs/common/comment_splitter_test.go
Normal file
63
server/events/vcs/common/comment_splitter_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2017 HootSuite Media Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the License);
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an AS IS BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// Modified hereafter by contributors to runatlantis/atlantis.
|
||||
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/runatlantis/atlantis/server/events/vcs/common"
|
||||
|
||||
. "github.com/runatlantis/atlantis/testing"
|
||||
)
|
||||
|
||||
// If under the maximum number of chars, we shouldn't split the comments.
|
||||
func TestSplitComment_UnderMax(t *testing.T) {
|
||||
comment := "comment under max size"
|
||||
split := common.SplitComment(comment, len(comment)+1, "sepEnd", "sepStart")
|
||||
Equals(t, []string{comment}, split)
|
||||
}
|
||||
|
||||
// If the comment needs to be split into 2 we should do the split and add the
|
||||
// separators properly.
|
||||
func TestSplitComment_TwoComments(t *testing.T) {
|
||||
comment := strings.Repeat("a", 1000)
|
||||
sepEnd := "-sepEnd"
|
||||
sepStart := "-sepStart"
|
||||
split := common.SplitComment(comment, len(comment)-1, sepEnd, sepStart)
|
||||
|
||||
expCommentLen := len(comment) - len(sepEnd) - len(sepStart) - 1
|
||||
expFirstComment := comment[:expCommentLen]
|
||||
expSecondComment := comment[expCommentLen:]
|
||||
Equals(t, 2, len(split))
|
||||
Equals(t, expFirstComment+sepEnd, split[0])
|
||||
Equals(t, sepStart+expSecondComment, split[1])
|
||||
}
|
||||
|
||||
// If the comment needs to be split into 4 we should do the split and add the
|
||||
// separators properly.
|
||||
func TestSplitComment_FourComments(t *testing.T) {
|
||||
comment := strings.Repeat("a", 1000)
|
||||
sepEnd := "-sepEnd"
|
||||
sepStart := "-sepStart"
|
||||
max := (len(comment) / 4) + len(sepEnd) + len(sepStart)
|
||||
split := common.SplitComment(comment, max, sepEnd, sepStart)
|
||||
|
||||
expMax := len(comment) / 4
|
||||
Equals(t, []string{
|
||||
comment[:expMax] + sepEnd,
|
||||
sepStart + comment[expMax:expMax*2] + sepEnd,
|
||||
sepStart + comment[expMax*2:expMax*3] + sepEnd,
|
||||
sepStart + comment[expMax*3:]}, split)
|
||||
}
|
||||
@@ -16,28 +16,19 @@ package vcs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/runatlantis/atlantis/server/events/vcs/common"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
)
|
||||
|
||||
// detailsClose is appended to a comment that is so long we split it into
|
||||
// multiple comments.
|
||||
const detailsClose = "\n```\n</details>" +
|
||||
"\n<br>\n\n**Warning**: Output length greater than max comment size. Continued in next comment."
|
||||
|
||||
// detailsOpen is prepended to the following comments when we split.
|
||||
const detailsOpen = "Continued from previous comment.\n<details><summary>Show Output</summary>\n\n" +
|
||||
"```diff\n"
|
||||
|
||||
// maxCommentBodySize is derived from the error message when you go over
|
||||
// this limit.
|
||||
// We deduct some characters for appending details close/open tag
|
||||
const maxCommentBodySize = 65536 - len(detailsClose) - len(detailsOpen)
|
||||
// maxCommentLength is the maximum number of chars allowed in a single comment
|
||||
// by GitHub.
|
||||
const maxCommentLength = 65536
|
||||
|
||||
// GithubClient is used to perform GitHub actions.
|
||||
type GithubClient struct {
|
||||
@@ -101,7 +92,12 @@ func (g *GithubClient) GetModifiedFiles(repo models.Repo, pull models.PullReques
|
||||
// If comment length is greater than the max comment length we split into
|
||||
// multiple comments.
|
||||
func (g *GithubClient) CreateComment(repo models.Repo, pullNum int, comment string) error {
|
||||
comments := g.splitAtMaxChars(comment, maxCommentBodySize)
|
||||
sepEnd := "\n```\n</details>" +
|
||||
"\n<br>\n\n**Warning**: Output length greater than max comment size. Continued in next comment."
|
||||
sepStart := "Continued from previous comment.\n<details><summary>Show Output</summary>\n\n" +
|
||||
"```diff\n"
|
||||
|
||||
comments := common.SplitComment(comment, maxCommentLength, sepEnd, sepStart)
|
||||
for _, c := range comments {
|
||||
_, _, err := g.client.Issues.CreateComment(g.ctx, repo.Owner, repo.Name, pullNum, &github.IssueComment{Body: &c})
|
||||
if err != nil {
|
||||
@@ -151,35 +147,3 @@ func (g *GithubClient) UpdateStatus(repo models.Repo, pull models.PullRequest, s
|
||||
_, _, err := g.client.Repositories.CreateStatus(g.ctx, repo.Owner, repo.Name, pull.HeadCommit, status)
|
||||
return err
|
||||
}
|
||||
|
||||
// splitAtMaxChars splits comment into a slice with string up to max
|
||||
// len separated by join which gets appended to the ends of the middle strings.
|
||||
// nolint: unparam
|
||||
func (g *GithubClient) splitAtMaxChars(comment string, maxSize int) []string {
|
||||
// If we're under the limit then no need to split.
|
||||
if len(comment) <= maxSize {
|
||||
return []string{comment}
|
||||
}
|
||||
|
||||
var comments []string
|
||||
numComments := int(math.Ceil(float64(len(comment)) / float64(maxSize)))
|
||||
for i := 0; i < numComments; i++ {
|
||||
upTo := g.min(len(comment), (i+1)*maxSize)
|
||||
portion := comment[i*maxSize : upTo]
|
||||
if i < numComments-1 {
|
||||
portion += detailsClose
|
||||
}
|
||||
if i > 0 {
|
||||
portion = detailsOpen + portion
|
||||
}
|
||||
comments = append(comments, portion)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func (g *GithubClient) min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -19,35 +19,6 @@ import (
|
||||
. "github.com/runatlantis/atlantis/testing"
|
||||
)
|
||||
|
||||
// If under the maximum number of chars, we shouldn't split the comments.
|
||||
func TestSplitAtMaxChars_UnderMax(t *testing.T) {
|
||||
client := &GithubClient{}
|
||||
comment := "comment under max size"
|
||||
split := client.splitAtMaxChars(comment, len(comment)+1)
|
||||
Equals(t, []string{comment}, split)
|
||||
}
|
||||
|
||||
// If the comment is over the max number of chars, we should split it into
|
||||
// multiple comments.
|
||||
func TestSplitAtMaxChars_OverMaxOnce(t *testing.T) {
|
||||
client := &GithubClient{}
|
||||
comment := "comment over max size"
|
||||
split := client.splitAtMaxChars(comment, len(comment)-1)
|
||||
Equals(t, []string{"comment over max siz" + detailsClose, detailsOpen + "e"}, split)
|
||||
}
|
||||
|
||||
// Test that it works for multiple comments.
|
||||
func TestSplitAtMaxChars_OverMaxMultiple(t *testing.T) {
|
||||
client := &GithubClient{}
|
||||
comment := "comment over max size"
|
||||
third := len(comment) / 3
|
||||
split := client.splitAtMaxChars(comment, third)
|
||||
Equals(t, []string{
|
||||
comment[:third] + detailsClose,
|
||||
detailsOpen + comment[third:third*2] + detailsClose,
|
||||
detailsOpen + comment[third*2:]}, split)
|
||||
}
|
||||
|
||||
// If the hostname is github.com, should use normal BaseURL.
|
||||
func TestNewGithubClient_GithubCom(t *testing.T) {
|
||||
client, err := NewGithubClient("github.com", "user", "pass")
|
||||
|
||||
Reference in New Issue
Block a user