Merge branch 'code-comment-create' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#17)

This commit is contained in:
Marko Gacesa 2023-04-13 18:54:10 +00:00 committed by Harness
commit b886a96943
54 changed files with 2824 additions and 197 deletions

View File

@ -34,6 +34,7 @@ import (
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/harness/gitness/internal/server"
"github.com/harness/gitness/internal/services"
"github.com/harness/gitness/internal/services/codecomments"
pullreqservice "github.com/harness/gitness/internal/services/pullreq"
"github.com/harness/gitness/internal/services/webhook"
"github.com/harness/gitness/internal/store/cache"
@ -86,6 +87,7 @@ func initSystem(ctx context.Context, config *gitnesstypes.Config) (*system, erro
githook.WireSet,
lock.WireSet,
pubsub.WireSet,
codecomments.WireSet,
)
return &system{}, nil
}

View File

@ -7,7 +7,6 @@ package server
import (
"context"
"github.com/harness/gitness/events"
"github.com/harness/gitness/gitrpc"
server2 "github.com/harness/gitness/gitrpc/server"
@ -33,6 +32,7 @@ import (
router2 "github.com/harness/gitness/internal/router"
"github.com/harness/gitness/internal/server"
"github.com/harness/gitness/internal/services"
"github.com/harness/gitness/internal/services/codecomments"
pullreq2 "github.com/harness/gitness/internal/services/pullreq"
"github.com/harness/gitness/internal/services/webhook"
"github.com/harness/gitness/internal/store/cache"
@ -116,6 +116,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
principalInfoCache := cache.ProvidePrincipalInfoCache(principalInfoView)
pullReqStore := database.ProvidePullReqStore(db, principalInfoCache)
pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache)
codeCommentView := database.ProvideCodeCommentView(db)
pullReqReviewStore := database.ProvidePullReqReviewStore(db)
pullReqReviewerStore := database.ProvidePullReqReviewerStore(db, principalInfoCache)
eventsConfig, err := ProvideEventsConfig()
@ -136,7 +137,8 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
}
lockConfig := lock.ProvideConfig(config)
mutexManager := lock.ProvideMutexManager(lockConfig, universalClient)
pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface, reporter, mutexManager)
migrator := codecomments.ProvideMigrator(gitrpcInterface)
pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface, reporter, mutexManager, migrator)
webhookConfig, err := ProvideWebhookConfig()
if err != nil {
return nil, err
@ -179,7 +181,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
repoGitInfoCache := cache.ProvideRepoGitInfoCache(repoGitInfoView)
pubsubConfig := pubsub.ProvideConfig(config)
pubSub := pubsub.ProvidePubSub(pubsubConfig, universalClient)
pullreqService, err := pullreq2.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter, gitrpcInterface, db, repoGitInfoCache, principalInfoCache, repoStore, pullReqStore, pullReqActivityStore, pubSub)
pullreqService, err := pullreq2.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter, gitrpcInterface, db, repoGitInfoCache, principalInfoCache, repoStore, pullReqStore, pullReqActivityStore, codeCommentView, migrator, pubSub)
if err != nil {
return nil, err
}

View File

@ -31,6 +31,7 @@ import (
"github.com/harness/gitness/internal/router"
"github.com/harness/gitness/internal/server"
"github.com/harness/gitness/internal/services"
"github.com/harness/gitness/internal/services/codecomments"
pullreqservice "github.com/harness/gitness/internal/services/pullreq"
"github.com/harness/gitness/internal/services/webhook"
"github.com/harness/gitness/internal/store"
@ -83,6 +84,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
githook.WireSet,
lock.WireSet,
pubsub.WireSet,
codecomments.WireSet,
)
return &system{}, nil
}

View File

@ -7,7 +7,6 @@ package server
import (
"context"
"github.com/harness/gitness/events"
"github.com/harness/gitness/gitrpc"
server2 "github.com/harness/gitness/gitrpc/server"
@ -29,6 +28,7 @@ import (
"github.com/harness/gitness/internal/router"
"github.com/harness/gitness/internal/server"
"github.com/harness/gitness/internal/services"
"github.com/harness/gitness/internal/services/codecomments"
pullreq2 "github.com/harness/gitness/internal/services/pullreq"
"github.com/harness/gitness/internal/services/webhook"
"github.com/harness/gitness/internal/store"
@ -81,6 +81,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
principalInfoCache := cache.ProvidePrincipalInfoCache(principalInfoView)
pullReqStore := database.ProvidePullReqStore(db, principalInfoCache)
pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache)
codeCommentView := database.ProvideCodeCommentView(db)
pullReqReviewStore := database.ProvidePullReqReviewStore(db)
pullReqReviewerStore := database.ProvidePullReqReviewerStore(db, principalInfoCache)
eventsConfig, err := ProvideEventsConfig()
@ -101,7 +102,8 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
}
lockConfig := lock.ProvideConfig(config)
mutexManager := lock.ProvideMutexManager(lockConfig, universalClient)
pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface, reporter, mutexManager)
migrator := codecomments.ProvideMigrator(gitrpcInterface)
pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface, reporter, mutexManager, migrator)
webhookConfig, err := ProvideWebhookConfig()
if err != nil {
return nil, err
@ -146,7 +148,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
repoGitInfoCache := cache.ProvideRepoGitInfoCache(repoGitInfoView)
pubsubConfig := pubsub.ProvideConfig(config)
pubSub := pubsub.ProvidePubSub(pubsubConfig, universalClient)
pullreqService, err := pullreq2.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter, gitrpcInterface, db, repoGitInfoCache, principalInfoCache, repoStore, pullReqStore, pullReqActivityStore, pubSub)
pullreqService, err := pullreq2.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter, gitrpcInterface, db, repoGitInfoCache, principalInfoCache, repoStore, pullReqStore, pullReqActivityStore, codeCommentView, migrator, pubSub)
if err != nil {
return nil, err
}

View File

@ -9,8 +9,10 @@ import (
"errors"
"fmt"
"io"
"strings"
"github.com/harness/gitness/gitrpc/internal/streamio"
"github.com/harness/gitness/gitrpc/internal/types"
"github.com/harness/gitness/gitrpc/rpc"
"golang.org/x/sync/errgroup"
@ -152,3 +154,131 @@ func (c *Client) DiffStats(ctx context.Context, params *DiffParams) (DiffStatsOu
FilesChanged: totalFiles,
}, nil
}
type GetDiffHunkHeadersParams struct {
ReadParams
SourceCommitSHA string
TargetCommitSHA string
}
type DiffFileHeader struct {
OldName string
NewName string
}
type HunkHeader struct {
OldLine int
OldSpan int
NewLine int
NewSpan int
Text string
}
type DiffFileHunkHeaders struct {
FileHeader DiffFileHeader
HunkHeaders []HunkHeader
}
type GetDiffHunkHeadersOutput struct {
Files []DiffFileHunkHeaders
}
func (c *Client) GetDiffHunkHeaders(
ctx context.Context,
params GetDiffHunkHeadersParams,
) (GetDiffHunkHeadersOutput, error) {
if params.SourceCommitSHA == params.TargetCommitSHA {
return GetDiffHunkHeadersOutput{}, nil
}
hunkHeaders, err := c.diffService.GetDiffHunkHeaders(ctx, &rpc.GetDiffHunkHeadersRequest{
Base: mapToRPCReadRequest(params.ReadParams),
SourceCommitSha: params.SourceCommitSHA,
TargetCommitSha: params.TargetCommitSHA,
})
if err != nil {
return GetDiffHunkHeadersOutput{}, processRPCErrorf(err, "failed to get git diff hunk headers")
}
files := make([]DiffFileHunkHeaders, len(hunkHeaders.Files))
for i, file := range hunkHeaders.Files {
headers := make([]HunkHeader, len(file.HunkHeaders))
for j, header := range file.HunkHeaders {
headers[j] = mapHunkHeader(header)
}
files[i] = DiffFileHunkHeaders{
FileHeader: mapDiffFileHeader(file.FileHeader),
HunkHeaders: headers,
}
}
return GetDiffHunkHeadersOutput{
Files: files,
}, nil
}
type DiffCutOutput struct {
Header HunkHeader
Lines []string
MergeBaseSHA string
LatestSourceSHA string
AnyNew bool
}
type DiffCutParams struct {
ReadParams
SourceCommitSHA string
SourceBranch string
TargetCommitSHA string
TargetBranch string
Path string
LineStart int
LineStartNew bool
LineEnd int
LineEndNew bool
}
// DiffCut extracts diff snippet from a git diff hunk.
// The snippet is from the specific commit (specified by commit SHA), between refs
// source branch and target branch, from the specific file.
func (c *Client) DiffCut(ctx context.Context, params *DiffCutParams) (DiffCutOutput, error) {
result, err := c.diffService.DiffCut(ctx, &rpc.DiffCutRequest{
Base: mapToRPCReadRequest(params.ReadParams),
SourceCommitSha: params.SourceCommitSHA,
SourceBranch: params.SourceBranch,
TargetCommitSha: params.TargetCommitSHA,
TargetBranch: params.TargetBranch,
Path: params.Path,
LineStart: int32(params.LineStart),
LineStartNew: params.LineStartNew,
LineEnd: int32(params.LineEnd),
LineEndNew: params.LineEndNew,
})
if err != nil {
return DiffCutOutput{}, processRPCErrorf(err, "failed to get git diff sub hunk")
}
var anyNew bool
for _, line := range result.Lines {
if strings.HasPrefix(line, "+") {
anyNew = true
break
}
}
hunkHeader := types.HunkHeader{
OldLine: int(result.HunkHeader.OldLine),
OldSpan: int(result.HunkHeader.OldSpan),
NewLine: int(result.HunkHeader.NewLine),
NewSpan: int(result.HunkHeader.NewSpan),
Text: result.HunkHeader.Text,
}
return DiffCutOutput{
Header: HunkHeader(hunkHeader),
Lines: result.Lines,
MergeBaseSHA: result.MergeBaseSha,
LatestSourceSHA: result.LatestSourceSha,
AnyNew: anyNew,
}, nil
}

View File

@ -50,6 +50,9 @@ type Interface interface {
DiffShortStat(ctx context.Context, params *DiffParams) (DiffShortStatOutput, error)
DiffStats(ctx context.Context, params *DiffParams) (DiffStatsOutput, error)
GetDiffHunkHeaders(ctx context.Context, params GetDiffHunkHeadersParams) (GetDiffHunkHeadersOutput, error)
DiffCut(ctx context.Context, params *DiffCutParams) (DiffCutOutput, error)
/*
* Merge services
*/

View File

@ -7,9 +7,12 @@ package gitea
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/harness/gitness/gitrpc/internal/parser"
"github.com/harness/gitness/gitrpc/internal/types"
"code.gitea.io/gitea/modules/git"
@ -70,3 +73,106 @@ func (g Adapter) DiffShortStat(
Deletions: totalDeletions,
}, nil
}
// GetDiffHunkHeaders for each file in diff output returns file name (old and new to detect renames),
// and all hunk headers. The diffs are generated with unified=0 parameter to create minimum sized hunks.
// Hunks' body is ignored.
// The purpose of this function is to get data based on which code comments could be repositioned.
func (g Adapter) GetDiffHunkHeaders(
ctx context.Context,
repoPath, targetRef, sourceRef string,
) ([]*types.DiffFileHunkHeaders, error) {
pipeRead, pipeWrite := io.Pipe()
stderr := &bytes.Buffer{}
go func() {
var err error
defer func() {
// If running of the command below fails, make the pipe reader also fail with the same error.
_ = pipeWrite.CloseWithError(err)
}()
cmd := git.NewCommand(ctx,
"diff", "--patch", "--no-color", "--unified=0", sourceRef, targetRef)
err = cmd.Run(&git.RunOpts{
Dir: repoPath,
Stdout: pipeWrite,
Stderr: stderr, // We capture stderr output in a buffer.
})
}()
fileHunkHeaders, err := parser.GetHunkHeaders(pipeRead)
// First check if there's something in the stderr buffer, if yes that's the error
if errStderr := parseDiffStderr(stderr); errStderr != nil {
return nil, errStderr
}
// Next check if reading the git diff output caused an error
if err != nil {
return nil, err
}
return fileHunkHeaders, nil
}
// DiffCut parses full file git diff output and returns lines specified with the parameters.
// The purpose of this function is to get diff data with which code comments could be generated.
func (g Adapter) DiffCut(
ctx context.Context,
repoPath, targetRef, sourceRef, path string,
params types.DiffCutParams,
) (types.Hunk, error) {
pipeRead, pipeWrite := io.Pipe()
stderr := &bytes.Buffer{}
go func() {
var err error
defer func() {
// If running of the command below fails, make the pipe reader also fail with the same error.
_ = pipeWrite.CloseWithError(err)
}()
cmd := git.NewCommand(ctx,
"diff", "--merge-base", "--patch", "--no-color", "--unified=100000000",
targetRef, sourceRef, "--", path)
err = cmd.Run(&git.RunOpts{
Dir: repoPath,
Stdout: pipeWrite,
Stderr: stderr, // We capture stderr output in a buffer.
})
}()
hunk, err := parser.DiffCut(pipeRead, params)
// First check if there's something in the stderr buffer, if yes that's the error
if errStderr := parseDiffStderr(stderr); errStderr != nil {
return types.Hunk{}, errStderr
}
// Next check if reading the git diff output caused an error
if err != nil {
return types.Hunk{}, err
}
return hunk, nil
}
func parseDiffStderr(stderr *bytes.Buffer) error {
errRaw := stderr.String() // assume there will never be a lot of output to stdout
if len(errRaw) == 0 {
return nil
}
if idx := strings.IndexByte(errRaw, '\n'); idx > 0 {
errRaw = errRaw[:idx] // get only the first line of the output
}
errRaw = strings.TrimPrefix(errRaw, "fatal: ") // git errors start with the "fatal: " prefix
if strings.Contains(errRaw, "bad revision") {
return types.ErrSHADoesNotMatch
}
return errors.New(errRaw)
}

View File

@ -0,0 +1,263 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package parser
import (
"bufio"
"errors"
"io"
"github.com/harness/gitness/gitrpc/internal/types"
)
// DiffCut parses git diff output that should consist of a single hunk
// (usually generated with large value passed to the "--unified" parameter)
// and returns lines specified with the parameters.
//
//nolint:funlen,gocognit,nestif,gocognit,gocyclo,cyclop // it's actually very readable
func DiffCut(r io.Reader, params types.DiffCutParams) (types.Hunk, error) {
scanner := bufio.NewScanner(r)
var err error
var hunkHeader types.HunkHeader
if _, err = scanFileHeader(scanner); err != nil {
return types.Hunk{}, err
}
if hunkHeader, err = scanHunkHeader(scanner); err != nil {
return types.Hunk{}, err
}
currentOldLine := hunkHeader.OldLine
currentNewLine := hunkHeader.NewLine
var inCut bool
var diffCutHeader types.HunkHeader
var diffCut []string
linesBeforeBuf := newStrCircBuf(params.BeforeLines)
for {
if params.LineEndNew && currentNewLine > params.LineEnd ||
!params.LineEndNew && currentOldLine > params.LineEnd {
break // exceeded the requested line range
}
var line string
var action diffAction
line, action, err = scanHunkLine(scanner)
if err != nil {
return types.Hunk{}, err
}
if line == "" {
err = io.EOF
break
}
if params.LineStartNew && currentNewLine < params.LineStart ||
!params.LineStartNew && currentOldLine < params.LineStart {
// not yet in the requested line range
linesBeforeBuf.push(line)
} else {
if !inCut {
diffCutHeader.NewLine = currentNewLine
diffCutHeader.OldLine = currentOldLine
}
inCut = true
if action != actionRemoved {
diffCutHeader.NewSpan++
}
if action != actionAdded {
diffCutHeader.OldSpan++
}
diffCut = append(diffCut, line)
if len(diffCut) > params.LineLimit {
break // safety break
}
}
// increment the line numbers
if action != actionRemoved {
currentNewLine++
}
if action != actionAdded {
currentOldLine++
}
}
if !inCut {
return types.Hunk{}, types.ErrHunkNotFound
}
var (
linesBefore []string
linesAfter []string
)
linesBefore = linesBeforeBuf.lines()
if !errors.Is(err, io.EOF) {
for i := 0; i < params.AfterLines && scanner.Scan(); i++ {
line := scanner.Text()
if line == "" {
break
}
linesAfter = append(linesAfter, line)
}
if err = scanner.Err(); err != nil {
return types.Hunk{}, err
}
}
for _, s := range linesBefore {
action := diffAction(s[0])
if action != actionRemoved {
diffCutHeader.NewLine--
diffCutHeader.NewSpan++
}
if action != actionAdded {
diffCutHeader.OldLine--
diffCutHeader.OldSpan++
}
}
for _, s := range linesAfter {
action := diffAction(s[0])
if action != actionRemoved {
diffCutHeader.NewSpan++
}
if action != actionAdded {
diffCutHeader.OldSpan++
}
}
return types.Hunk{
HunkHeader: diffCutHeader,
Lines: concat(linesBefore, diffCut, linesAfter),
}, nil
}
// scanFileHeader keeps reading lines until file header line is read.
func scanFileHeader(scan *bufio.Scanner) (types.DiffFileHeader, error) {
for scan.Scan() {
line := scan.Text()
if h, ok := ParseDiffFileHeader(line); ok {
return h, nil
}
}
if err := scan.Err(); err != nil {
return types.DiffFileHeader{}, err
}
return types.DiffFileHeader{}, types.ErrHunkNotFound
}
// scanHunkHeader keeps reading lines until hunk header line is read.
func scanHunkHeader(scan *bufio.Scanner) (types.HunkHeader, error) {
for scan.Scan() {
line := scan.Text()
if h, ok := ParseDiffHunkHeader(line); ok {
return h, nil
}
}
if err := scan.Err(); err != nil {
return types.HunkHeader{}, err
}
return types.HunkHeader{}, types.ErrHunkNotFound
}
type diffAction byte
const (
actionUnchanged diffAction = ' '
actionRemoved diffAction = '-'
actionAdded diffAction = '+'
)
func scanHunkLine(scan *bufio.Scanner) (string, diffAction, error) {
if !scan.Scan() {
return "", actionUnchanged, scan.Err()
}
line := scan.Text()
if line == "" {
return "", actionUnchanged, types.ErrHunkNotFound // should not happen: empty line in diff output
}
action := diffAction(line[0])
if action != actionRemoved && action != actionAdded && action != actionUnchanged {
return "", actionUnchanged, nil
}
return line, action, nil
}
type strCircBuf struct {
head int
entries []string
}
func newStrCircBuf(size int) strCircBuf {
return strCircBuf{
head: -1,
entries: make([]string, 0, size),
}
}
func (b *strCircBuf) push(s string) {
n := cap(b.entries)
if n == 0 {
return
}
b.head++
if len(b.entries) < n {
b.entries = append(b.entries, s)
return
}
if b.head >= n {
b.head = 0
}
b.entries[b.head] = s
}
func (b *strCircBuf) lines() []string {
n := cap(b.entries)
if len(b.entries) < n {
return b.entries
}
res := make([]string, n)
for i := 0; i < n; i++ {
idx := (b.head + 1 + i) % n
res[i] = b.entries[idx]
}
return res
}
func concat[T any](a ...[]T) []T {
var n int
for _, m := range a {
n += len(m)
}
res := make([]T, n)
n = 0
for _, m := range a {
copy(res[n:], m)
n += len(m)
}
return res
}

View File

@ -0,0 +1,165 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package parser
import (
"reflect"
"strings"
"testing"
"github.com/harness/gitness/gitrpc/internal/types"
)
func TestDiffCut(t *testing.T) {
const input = `diff --git a/test.txt b/test.txt
--- a/test.txt
+++ b/test.txt
@@ -1,15 +1,11 @@
+0
1
2
3
4
5
-6
-7
-8
+6,7,8
9
10
11
12
-13
-14
-15
`
tests := []struct {
name string
params types.DiffCutParams
expCutHeader string
expCut []string
expError error
}{
{
name: "at-'+6,7,8':new",
params: types.DiffCutParams{
LineStart: 7, LineStartNew: true,
LineEnd: 7, LineEndNew: true,
BeforeLines: 0, AfterLines: 0,
LineLimit: 1000,
},
expCutHeader: "@@ -6,3 +7 @@",
expCut: []string{"-6", "-7", "-8", "+6,7,8"},
expError: nil,
},
{
name: "at-'+6,7,8':new-with-lines-around",
params: types.DiffCutParams{
LineStart: 7, LineStartNew: true,
LineEnd: 7, LineEndNew: true,
BeforeLines: 1, AfterLines: 2,
LineLimit: 1000,
},
expCutHeader: "@@ -5,6 +6,4 @@",
expCut: []string{" 5", "-6", "-7", "-8", "+6,7,8", " 9", " 10"},
expError: nil,
},
{
name: "at-'+0':new-with-lines-around",
params: types.DiffCutParams{
LineStart: 1, LineStartNew: true,
LineEnd: 1, LineEndNew: true,
BeforeLines: 3, AfterLines: 3,
LineLimit: 1000,
},
expCutHeader: "@@ -1,3 +1,4 @@",
expCut: []string{"+0", " 1", " 2", " 3"},
expError: nil,
},
{
name: "at-'-13':one-with-lines-around",
params: types.DiffCutParams{
LineStart: 13, LineStartNew: false,
LineEnd: 13, LineEndNew: false,
BeforeLines: 1, AfterLines: 1,
LineLimit: 1000,
},
expCutHeader: "@@ -12,3 +11 @@",
expCut: []string{" 12", "-13", "-14"},
expError: nil,
},
{
name: "at-'-13':mixed",
params: types.DiffCutParams{
LineStart: 7, LineStartNew: false,
LineEnd: 7, LineEndNew: true,
BeforeLines: 0, AfterLines: 0,
LineLimit: 1000,
},
expCutHeader: "@@ -7,2 +7 @@",
expCut: []string{"-7", "-8", "+6,7,8"},
expError: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
hunk, err := DiffCut(
strings.NewReader(input),
test.params,
)
//nolint:errorlint // this error will not be wrapped
if want, got := test.expError, err; want != got {
t.Errorf("error mismatch: want=%v got=%v", want, got)
return
}
if err != nil {
return
}
if want, got := test.expCutHeader, hunk.HunkHeader.String(); want != got {
t.Errorf("header mismatch: want=%s got=%s", want, got)
}
if want, got := test.expCut, hunk.Lines; !reflect.DeepEqual(want, got) {
t.Errorf("lines mismatch: want=%s got=%s", want, got)
}
})
}
}
func TestStrCircBuf(t *testing.T) {
tests := []struct {
name string
cap int
feed []string
exp []string
}{
{name: "empty", cap: 10, feed: nil, exp: []string{}},
{name: "zero-cap", cap: 0, feed: []string{"A", "B"}, exp: []string{}},
{name: "one", cap: 5, feed: []string{"A"}, exp: []string{"A"}},
{name: "two", cap: 3, feed: []string{"A", "B"}, exp: []string{"A", "B"}},
{name: "cap", cap: 3, feed: []string{"A", "B", "C"}, exp: []string{"A", "B", "C"}},
{name: "cap+1", cap: 3, feed: []string{"A", "B", "C", "D"}, exp: []string{"B", "C", "D"}},
{name: "cap+2", cap: 3, feed: []string{"A", "B", "C", "D", "E"}, exp: []string{"C", "D", "E"}},
{name: "cap*2+1", cap: 2, feed: []string{"A", "B", "C", "D", "E"}, exp: []string{"D", "E"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
b := newStrCircBuf(test.cap)
for _, s := range test.feed {
b.push(s)
}
if want, got := test.exp, b.lines(); !reflect.DeepEqual(want, got) {
t.Errorf("want=%v, got=%v", want, got)
}
})
}
}

View File

@ -0,0 +1,71 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package parser
import (
"bufio"
"io"
"regexp"
"github.com/harness/gitness/gitrpc/internal/types"
)
var regExpDiffFileHeader = regexp.MustCompile(`^diff --git a/(.+) b/(.+)$`)
func ParseDiffFileHeader(line string) (types.DiffFileHeader, bool) {
groups := regExpDiffFileHeader.FindStringSubmatch(line)
if groups == nil {
return types.DiffFileHeader{}, false
}
return types.DiffFileHeader{
OldFileName: groups[1],
NewFileName: groups[2],
}, true
}
// GetHunkHeaders parses git diff output and returns all diff headers for all files.
// See for documentation: https://git-scm.com/docs/git-diff#generate_patch_text_with_p
func GetHunkHeaders(r io.Reader) ([]*types.DiffFileHunkHeaders, error) {
scanner := bufio.NewScanner(r)
var currentFile *types.DiffFileHunkHeaders
var result []*types.DiffFileHunkHeaders
for scanner.Scan() {
line := scanner.Text()
if h, ok := ParseDiffFileHeader(line); ok {
if currentFile != nil {
result = append(result, currentFile)
}
currentFile = &types.DiffFileHunkHeaders{
FileHeader: h,
HunksHeaders: nil,
}
continue
}
if h, ok := ParseDiffHunkHeader(line); ok {
if currentFile == nil {
// should not happen: we reached the hunk header without first finding the file header.
return nil, types.ErrHunkNotFound
}
currentFile.HunksHeaders = append(currentFile.HunksHeaders, h)
continue
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
if currentFile != nil {
result = append(result, currentFile)
}
return result, nil
}

View File

@ -0,0 +1,78 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package parser
import (
"strings"
"testing"
"github.com/harness/gitness/gitrpc/internal/types"
"github.com/google/go-cmp/cmp"
)
func TestGetHunkHeaders(t *testing.T) {
input := `diff --git a/new_file.txt b/new_file.txt
new file mode 100644
index 0000000..fb0c863
--- /dev/null
+++ b/new_file.txt
@@ -0,0 +1,3 @@
This is a new file
created for this
unit test.
diff --git a/old_file_name.txt b/changed_file.txt
index f043b93..e9449b5 100644
--- a/changed_file.txt
+++ b/changed_file.txt
@@ -7,3 +7,4 @@
Unchanged line
-Removed line 1
+Added line 1
+Added line 2
Unchanged line
@@ -27,2 +28,3 @@
Unchanged line
+Added line
Unchanged line
diff --git a/deleted_file.txt b/deleted_file.txt
deleted file mode 100644
index f043b93..0000000
--- a/deleted_file.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-This is content of
-a deleted file
-in git diff output.
`
got, err := GetHunkHeaders(strings.NewReader(input))
if err != nil {
t.Errorf("got error: %v", err)
return
}
want := []*types.DiffFileHunkHeaders{
{
FileHeader: types.DiffFileHeader{OldFileName: "new_file.txt", NewFileName: "new_file.txt"},
HunksHeaders: []types.HunkHeader{{OldLine: 0, OldSpan: 0, NewLine: 1, NewSpan: 3}},
},
{
FileHeader: types.DiffFileHeader{OldFileName: "old_file_name.txt", NewFileName: "changed_file.txt"},
HunksHeaders: []types.HunkHeader{
{OldLine: 7, OldSpan: 3, NewLine: 7, NewSpan: 4},
{OldLine: 27, OldSpan: 2, NewLine: 28, NewSpan: 3},
},
},
{
FileHeader: types.DiffFileHeader{OldFileName: "deleted_file.txt", NewFileName: "deleted_file.txt"},
HunksHeaders: []types.HunkHeader{{OldLine: 1, OldSpan: 3, NewLine: 0, NewSpan: 0}},
},
}
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf(diff)
}
}

View File

@ -0,0 +1,40 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package parser
import (
"github.com/harness/gitness/gitrpc/internal/types"
"regexp"
"strconv"
)
var regExpHunkHeader = regexp.MustCompile(`^@@ -([0-9]+)(,([0-9]+))? \+([0-9]+)(,([0-9]+))? @@( (.+))?$`)
func ParseDiffHunkHeader(line string) (types.HunkHeader, bool) {
groups := regExpHunkHeader.FindStringSubmatch(line)
if groups == nil {
return types.HunkHeader{}, false
}
oldLine, _ := strconv.Atoi(groups[1])
oldSpan := 1
if groups[3] != "" {
oldSpan, _ = strconv.Atoi(groups[3])
}
newLine, _ := strconv.Atoi(groups[4])
newSpan := 1
if groups[6] != "" {
newSpan, _ = strconv.Atoi(groups[6])
}
return types.HunkHeader{
OldLine: oldLine,
OldSpan: oldSpan,
NewLine: newLine,
NewSpan: newSpan,
Text: groups[8],
}, true
}

View File

@ -94,3 +94,58 @@ func (s DiffService) DiffShortStat(ctx context.Context, r *rpc.DiffRequest) (*rp
Deletions: int32(stat.Deletions),
}, nil
}
func (s DiffService) GetDiffHunkHeaders(
ctx context.Context,
r *rpc.GetDiffHunkHeadersRequest,
) (*rpc.GetDiffHunkHeadersResponse, error) {
base := r.GetBase()
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
hunkHeaders, err := s.adapter.GetDiffHunkHeaders(ctx, repoPath, r.TargetCommitSha, r.SourceCommitSha)
if err != nil {
return nil, processGitErrorf(err, "failed to get diff hunk headers between two commits")
}
return &rpc.GetDiffHunkHeadersResponse{
Files: mapDiffFileHunkHeaders(hunkHeaders),
}, nil
}
func (s DiffService) DiffCut(
ctx context.Context,
r *rpc.DiffCutRequest,
) (*rpc.DiffCutResponse, error) {
base := r.GetBase()
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
mergeBase, _, err := s.adapter.GetMergeBase(ctx, repoPath, "", r.TargetBranch, r.SourceBranch)
if err != nil {
return nil, processGitErrorf(err, "failed to find merge base")
}
sourceCommits, err := s.adapter.ListCommits(ctx, repoPath, r.SourceBranch, r.TargetBranch, 0, 1)
if err != nil || len(sourceCommits) == 0 {
return nil, processGitErrorf(err, "failed to get list of source branch commits")
}
hunk, err := s.adapter.DiffCut(ctx, repoPath, r.TargetCommitSha, r.SourceCommitSha, r.Path, types.DiffCutParams{
LineStart: int(r.LineStart),
LineStartNew: r.LineStartNew,
LineEnd: int(r.LineEnd),
LineEndNew: r.LineEndNew,
BeforeLines: 2,
AfterLines: 2,
LineLimit: 40,
})
if err != nil {
return nil, processGitErrorf(err, "failed to get diff hunk")
}
return &rpc.DiffCutResponse{
HunkHeader: mapHunkHeader(hunk.HunkHeader),
Lines: hunk.Lines,
MergeBaseSha: mergeBase,
LatestSourceSha: sourceCommits[0].SHA,
}, nil
}

View File

@ -197,7 +197,9 @@ func processGitErrorf(err error, format string, args ...interface{}) error {
// when we add err as argument it will be part of the new error
args = append(args, err)
switch {
case errors.Is(err, types.ErrNotFound):
case errors.Is(err, types.ErrNotFound),
errors.Is(err, types.ErrSHADoesNotMatch),
errors.Is(err, types.ErrHunkNotFound):
return ErrNotFoundf(format, args...)
case errors.Is(err, types.ErrAlreadyExists):
return ErrAlreadyExistsf(format, args...)

View File

@ -51,5 +51,8 @@ type GitAdapter interface {
RawDiff(ctx context.Context, repoPath, base, head string, w io.Writer, args ...string) error
DiffShortStat(ctx context.Context, repoPath string,
baseRef string, headRef string, direct bool) (types.DiffShortStat, error)
GetDiffHunkHeaders(ctx context.Context, repoPath, targetRef, sourceRef string) ([]*types.DiffFileHunkHeaders, error)
DiffCut(ctx context.Context, repoPath, targetRef, sourceRef, path string,
params types.DiffCutParams) (types.Hunk, error)
Blame(ctx context.Context, repoPath, rev, file string, lineFrom, lineTo int) types.BlameReader
}

View File

@ -108,3 +108,34 @@ func mapGitSignature(gitSignature types.Signature) *rpc.Signature {
When: gitSignature.When.Unix(),
}
}
func mapHunkHeader(hunkHeader types.HunkHeader) *rpc.HunkHeader {
return &rpc.HunkHeader{
OldLine: int32(hunkHeader.OldLine),
OldSpan: int32(hunkHeader.OldSpan),
NewLine: int32(hunkHeader.NewLine),
NewSpan: int32(hunkHeader.NewSpan),
Text: hunkHeader.Text,
}
}
func mapDiffFileHeader(h types.DiffFileHeader) *rpc.DiffFileHeader {
return &rpc.DiffFileHeader{
OldFileName: h.OldFileName,
NewFileName: h.NewFileName,
}
}
func mapDiffFileHunkHeaders(diffHunkHeaders []*types.DiffFileHunkHeaders) []*rpc.DiffFileHunkHeaders {
res := make([]*rpc.DiffFileHunkHeaders, len(diffHunkHeaders))
for i, diffHunkHeader := range diffHunkHeaders {
hunkHeaders := make([]*rpc.HunkHeader, len(diffHunkHeader.HunksHeaders))
for j, hunkHeader := range diffHunkHeader.HunksHeaders {
hunkHeaders[j] = mapHunkHeader(hunkHeader)
}
res[i] = &rpc.DiffFileHunkHeaders{
FileHeader: mapDiffFileHeader(diffHunkHeader.FileHeader),
HunkHeaders: hunkHeaders,
}
}
return res
}

View File

@ -208,7 +208,10 @@ func isValidGitSHA(sha string) bool {
return gitSHARegex.MatchString(sha)
}
func (s RepositoryService) DeleteRepository(ctx context.Context, request *rpc.DeleteRepositoryRequest) (*rpc.DeleteRepositoryResponse, error) {
func (s RepositoryService) DeleteRepository(
ctx context.Context,
request *rpc.DeleteRepositoryRequest,
) (*rpc.DeleteRepositoryResponse, error) {
base := request.GetBase()
if base == nil {

View File

@ -23,6 +23,7 @@ var (
ErrSHADoesNotMatch = errors.New("sha does not match")
ErrEmptyBaseRef = errors.New("empty base reference")
ErrEmptyHeadRef = errors.New("empty head reference")
ErrHunkNotFound = errors.New("hunk not found")
)
// MergeConflictsError represents an error if merging fails with a conflict.

View File

@ -0,0 +1,62 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package types
import (
"strconv"
"strings"
)
type Hunk struct {
HunkHeader
Lines []string
}
type HunkHeader struct {
OldLine int
OldSpan int
NewLine int
NewSpan int
Text string
}
func (h *HunkHeader) IsZero() bool {
return h.OldLine == 0 && h.OldSpan == 0 && h.NewLine == 0 && h.NewSpan == 0
}
func (h *HunkHeader) IsValid() bool {
oldOk := h.OldLine == 0 && h.OldSpan == 0 || h.OldLine > 0 && h.OldSpan > 0
newOk := h.NewLine == 0 && h.NewSpan == 0 || h.NewLine > 0 && h.NewSpan > 0
return !h.IsZero() && oldOk && newOk
}
func (h *HunkHeader) String() string {
sb := strings.Builder{}
sb.WriteString("@@ -")
sb.WriteString(strconv.Itoa(h.OldLine))
if h.OldSpan != 1 {
sb.WriteByte(',')
sb.WriteString(strconv.Itoa(h.OldSpan))
}
sb.WriteString(" +")
sb.WriteString(strconv.Itoa(h.NewLine))
if h.NewSpan != 1 {
sb.WriteByte(',')
sb.WriteString(strconv.Itoa(h.NewSpan))
}
sb.WriteString(" @@")
if h.Text != "" {
sb.WriteByte(' ')
sb.WriteString(h.Text)
}
return sb.String()
}

View File

@ -253,6 +253,26 @@ type DiffShortStat struct {
Deletions int
}
type DiffFileHeader struct {
OldFileName string
NewFileName string
}
type DiffFileHunkHeaders struct {
FileHeader DiffFileHeader
HunksHeaders []HunkHeader
}
type DiffCutParams struct {
LineStart int
LineStartNew bool
LineEnd int
LineEndNew bool
BeforeLines int
AfterLines int
LineLimit int
}
type BlameReader interface {
NextPart() (*BlamePart, error)
}

View File

@ -225,3 +225,20 @@ func mapToRPCIdentityOptional(identity *Identity) *rpc.Identity {
Email: identity.Email,
}
}
func mapHunkHeader(h *rpc.HunkHeader) HunkHeader {
return HunkHeader{
OldLine: int(h.OldLine),
OldSpan: int(h.OldSpan),
NewLine: int(h.NewLine),
NewSpan: int(h.NewSpan),
Text: h.Text,
}
}
func mapDiffFileHeader(h *rpc.DiffFileHeader) DiffFileHeader {
return DiffFileHeader{
OldName: h.OldFileName,
NewName: h.NewFileName,
}
}

View File

@ -10,6 +10,8 @@ import "shared.proto";
service DiffService {
rpc RawDiff(DiffRequest) returns (stream RawDiffResponse) {}
rpc DiffShortStat(DiffRequest) returns (DiffShortStatResponse) {}
rpc GetDiffHunkHeaders(GetDiffHunkHeadersRequest) returns (GetDiffHunkHeadersResponse) {}
rpc DiffCut(DiffCutRequest) returns (DiffCutResponse) {}
}
message DiffRequest {
@ -32,3 +34,51 @@ message DiffShortStatResponse {
int32 additions = 2;
int32 deletions = 3;
}
message HunkHeader {
int32 old_line = 1;
int32 old_span = 2;
int32 new_line = 3;
int32 new_span = 4;
string text = 5;
}
message DiffFileHeader {
string old_file_name = 1;
string new_file_name = 2;
}
message DiffFileHunkHeaders {
DiffFileHeader file_header = 1;
repeated HunkHeader hunk_headers = 2;
}
message GetDiffHunkHeadersRequest {
ReadRequest base = 1;
string source_commit_sha = 2;
string target_commit_sha = 4;
}
message GetDiffHunkHeadersResponse {
repeated DiffFileHunkHeaders files = 1;
}
message DiffCutRequest {
ReadRequest base = 1;
string source_commit_sha = 2;
string source_branch = 3;
string target_commit_sha = 4;
string target_branch = 5;
string path = 6;
int32 line_start = 7;
bool line_start_new = 8;
int32 line_end = 9;
bool line_end_new = 10;
}
message DiffCutResponse {
HunkHeader hunk_header = 1;
repeated string lines = 2;
string merge_base_sha = 3;
string latest_source_sha = 4;
}

View File

@ -205,6 +205,495 @@ func (x *DiffShortStatResponse) GetDeletions() int32 {
return 0
}
type HunkHeader struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
OldLine int32 `protobuf:"varint,1,opt,name=old_line,json=oldLine,proto3" json:"old_line,omitempty"`
OldSpan int32 `protobuf:"varint,2,opt,name=old_span,json=oldSpan,proto3" json:"old_span,omitempty"`
NewLine int32 `protobuf:"varint,3,opt,name=new_line,json=newLine,proto3" json:"new_line,omitempty"`
NewSpan int32 `protobuf:"varint,4,opt,name=new_span,json=newSpan,proto3" json:"new_span,omitempty"`
Text string `protobuf:"bytes,5,opt,name=text,proto3" json:"text,omitempty"`
}
func (x *HunkHeader) Reset() {
*x = HunkHeader{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HunkHeader) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HunkHeader) ProtoMessage() {}
func (x *HunkHeader) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HunkHeader.ProtoReflect.Descriptor instead.
func (*HunkHeader) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{3}
}
func (x *HunkHeader) GetOldLine() int32 {
if x != nil {
return x.OldLine
}
return 0
}
func (x *HunkHeader) GetOldSpan() int32 {
if x != nil {
return x.OldSpan
}
return 0
}
func (x *HunkHeader) GetNewLine() int32 {
if x != nil {
return x.NewLine
}
return 0
}
func (x *HunkHeader) GetNewSpan() int32 {
if x != nil {
return x.NewSpan
}
return 0
}
func (x *HunkHeader) GetText() string {
if x != nil {
return x.Text
}
return ""
}
type DiffFileHeader struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
OldFileName string `protobuf:"bytes,1,opt,name=old_file_name,json=oldFileName,proto3" json:"old_file_name,omitempty"`
NewFileName string `protobuf:"bytes,2,opt,name=new_file_name,json=newFileName,proto3" json:"new_file_name,omitempty"`
}
func (x *DiffFileHeader) Reset() {
*x = DiffFileHeader{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DiffFileHeader) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DiffFileHeader) ProtoMessage() {}
func (x *DiffFileHeader) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DiffFileHeader.ProtoReflect.Descriptor instead.
func (*DiffFileHeader) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{4}
}
func (x *DiffFileHeader) GetOldFileName() string {
if x != nil {
return x.OldFileName
}
return ""
}
func (x *DiffFileHeader) GetNewFileName() string {
if x != nil {
return x.NewFileName
}
return ""
}
type DiffFileHunkHeaders struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FileHeader *DiffFileHeader `protobuf:"bytes,1,opt,name=file_header,json=fileHeader,proto3" json:"file_header,omitempty"`
HunkHeaders []*HunkHeader `protobuf:"bytes,2,rep,name=hunk_headers,json=hunkHeaders,proto3" json:"hunk_headers,omitempty"`
}
func (x *DiffFileHunkHeaders) Reset() {
*x = DiffFileHunkHeaders{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DiffFileHunkHeaders) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DiffFileHunkHeaders) ProtoMessage() {}
func (x *DiffFileHunkHeaders) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DiffFileHunkHeaders.ProtoReflect.Descriptor instead.
func (*DiffFileHunkHeaders) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{5}
}
func (x *DiffFileHunkHeaders) GetFileHeader() *DiffFileHeader {
if x != nil {
return x.FileHeader
}
return nil
}
func (x *DiffFileHunkHeaders) GetHunkHeaders() []*HunkHeader {
if x != nil {
return x.HunkHeaders
}
return nil
}
type GetDiffHunkHeadersRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Base *ReadRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
SourceCommitSha string `protobuf:"bytes,2,opt,name=source_commit_sha,json=sourceCommitSha,proto3" json:"source_commit_sha,omitempty"`
TargetCommitSha string `protobuf:"bytes,4,opt,name=target_commit_sha,json=targetCommitSha,proto3" json:"target_commit_sha,omitempty"`
}
func (x *GetDiffHunkHeadersRequest) Reset() {
*x = GetDiffHunkHeadersRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetDiffHunkHeadersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetDiffHunkHeadersRequest) ProtoMessage() {}
func (x *GetDiffHunkHeadersRequest) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetDiffHunkHeadersRequest.ProtoReflect.Descriptor instead.
func (*GetDiffHunkHeadersRequest) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{6}
}
func (x *GetDiffHunkHeadersRequest) GetBase() *ReadRequest {
if x != nil {
return x.Base
}
return nil
}
func (x *GetDiffHunkHeadersRequest) GetSourceCommitSha() string {
if x != nil {
return x.SourceCommitSha
}
return ""
}
func (x *GetDiffHunkHeadersRequest) GetTargetCommitSha() string {
if x != nil {
return x.TargetCommitSha
}
return ""
}
type GetDiffHunkHeadersResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Files []*DiffFileHunkHeaders `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"`
}
func (x *GetDiffHunkHeadersResponse) Reset() {
*x = GetDiffHunkHeadersResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetDiffHunkHeadersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetDiffHunkHeadersResponse) ProtoMessage() {}
func (x *GetDiffHunkHeadersResponse) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetDiffHunkHeadersResponse.ProtoReflect.Descriptor instead.
func (*GetDiffHunkHeadersResponse) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{7}
}
func (x *GetDiffHunkHeadersResponse) GetFiles() []*DiffFileHunkHeaders {
if x != nil {
return x.Files
}
return nil
}
type DiffCutRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Base *ReadRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
SourceCommitSha string `protobuf:"bytes,2,opt,name=source_commit_sha,json=sourceCommitSha,proto3" json:"source_commit_sha,omitempty"`
SourceBranch string `protobuf:"bytes,3,opt,name=source_branch,json=sourceBranch,proto3" json:"source_branch,omitempty"`
TargetCommitSha string `protobuf:"bytes,4,opt,name=target_commit_sha,json=targetCommitSha,proto3" json:"target_commit_sha,omitempty"`
TargetBranch string `protobuf:"bytes,5,opt,name=target_branch,json=targetBranch,proto3" json:"target_branch,omitempty"`
Path string `protobuf:"bytes,6,opt,name=path,proto3" json:"path,omitempty"`
LineStart int32 `protobuf:"varint,7,opt,name=line_start,json=lineStart,proto3" json:"line_start,omitempty"`
LineStartNew bool `protobuf:"varint,8,opt,name=line_start_new,json=lineStartNew,proto3" json:"line_start_new,omitempty"`
LineEnd int32 `protobuf:"varint,9,opt,name=line_end,json=lineEnd,proto3" json:"line_end,omitempty"`
LineEndNew bool `protobuf:"varint,10,opt,name=line_end_new,json=lineEndNew,proto3" json:"line_end_new,omitempty"`
}
func (x *DiffCutRequest) Reset() {
*x = DiffCutRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DiffCutRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DiffCutRequest) ProtoMessage() {}
func (x *DiffCutRequest) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DiffCutRequest.ProtoReflect.Descriptor instead.
func (*DiffCutRequest) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{8}
}
func (x *DiffCutRequest) GetBase() *ReadRequest {
if x != nil {
return x.Base
}
return nil
}
func (x *DiffCutRequest) GetSourceCommitSha() string {
if x != nil {
return x.SourceCommitSha
}
return ""
}
func (x *DiffCutRequest) GetSourceBranch() string {
if x != nil {
return x.SourceBranch
}
return ""
}
func (x *DiffCutRequest) GetTargetCommitSha() string {
if x != nil {
return x.TargetCommitSha
}
return ""
}
func (x *DiffCutRequest) GetTargetBranch() string {
if x != nil {
return x.TargetBranch
}
return ""
}
func (x *DiffCutRequest) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *DiffCutRequest) GetLineStart() int32 {
if x != nil {
return x.LineStart
}
return 0
}
func (x *DiffCutRequest) GetLineStartNew() bool {
if x != nil {
return x.LineStartNew
}
return false
}
func (x *DiffCutRequest) GetLineEnd() int32 {
if x != nil {
return x.LineEnd
}
return 0
}
func (x *DiffCutRequest) GetLineEndNew() bool {
if x != nil {
return x.LineEndNew
}
return false
}
type DiffCutResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
HunkHeader *HunkHeader `protobuf:"bytes,1,opt,name=hunk_header,json=hunkHeader,proto3" json:"hunk_header,omitempty"`
Lines []string `protobuf:"bytes,2,rep,name=lines,proto3" json:"lines,omitempty"`
MergeBaseSha string `protobuf:"bytes,3,opt,name=merge_base_sha,json=mergeBaseSha,proto3" json:"merge_base_sha,omitempty"`
LatestSourceSha string `protobuf:"bytes,4,opt,name=latest_source_sha,json=latestSourceSha,proto3" json:"latest_source_sha,omitempty"`
}
func (x *DiffCutResponse) Reset() {
*x = DiffCutResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DiffCutResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DiffCutResponse) ProtoMessage() {}
func (x *DiffCutResponse) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DiffCutResponse.ProtoReflect.Descriptor instead.
func (*DiffCutResponse) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{9}
}
func (x *DiffCutResponse) GetHunkHeader() *HunkHeader {
if x != nil {
return x.HunkHeader
}
return nil
}
func (x *DiffCutResponse) GetLines() []string {
if x != nil {
return x.Lines
}
return nil
}
func (x *DiffCutResponse) GetMergeBaseSha() string {
if x != nil {
return x.MergeBaseSha
}
return ""
}
func (x *DiffCutResponse) GetLatestSourceSha() string {
if x != nil {
return x.LatestSourceSha
}
return ""
}
var File_diff_proto protoreflect.FileDescriptor
var file_diff_proto_rawDesc = []byte{
@ -227,19 +716,99 @@ var file_diff_proto_rawDesc = []byte{
0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20,
0x01, 0x28, 0x05, 0x52, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c,
0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x05, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x85, 0x01, 0x0a,
0x0b, 0x44, 0x69, 0x66, 0x66, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x35, 0x0a, 0x07,
0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69,
0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x70, 0x63, 0x2e,
0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0d, 0x44, 0x69, 0x66, 0x66, 0x53, 0x68, 0x6f, 0x72, 0x74,
0x53, 0x74, 0x61, 0x74, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x66,
0x66, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x6e, 0x65,
0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
0x05, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8c, 0x01, 0x0a,
0x0a, 0x48, 0x75, 0x6e, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6f,
0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6f,
0x6c, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x73, 0x70,
0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6f, 0x6c, 0x64, 0x53, 0x70, 0x61,
0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08,
0x6e, 0x65, 0x77, 0x5f, 0x73, 0x70, 0x61, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07,
0x6e, 0x65, 0x77, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x58, 0x0a, 0x0e, 0x44,
0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a,
0x0d, 0x6f, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x6c, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6e, 0x65, 0x77, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x46, 0x69, 0x6c,
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x7f, 0x0a, 0x13, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c,
0x65, 0x48, 0x75, 0x6e, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x34, 0x0a, 0x0b,
0x66, 0x69, 0x6c, 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x13, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65,
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x48, 0x65, 0x61, 0x64,
0x65, 0x72, 0x12, 0x32, 0x0a, 0x0c, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65,
0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48,
0x75, 0x6e, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0b, 0x68, 0x75, 0x6e, 0x6b, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x44, 0x69,
0x66, 0x66, 0x48, 0x75, 0x6e, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6d,
0x6d, 0x69, 0x74, 0x53, 0x68, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53,
0x68, 0x61, 0x22, 0x4c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x69, 0x66, 0x66, 0x48, 0x75, 0x6e,
0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x2e, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x75,
0x6e, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73,
0x22, 0xee, 0x02, 0x0a, 0x0e, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6d, 0x6d,
0x69, 0x74, 0x53, 0x68, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,
0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61,
0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6d,
0x6d, 0x69, 0x74, 0x53, 0x68, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
0x5f, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74,
0x61, 0x72, 0x67, 0x65, 0x74, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70,
0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07, 0x20,
0x01, 0x28, 0x05, 0x52, 0x09, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x24,
0x0a, 0x0e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6e, 0x65, 0x77,
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x72,
0x74, 0x4e, 0x65, 0x77, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x65, 0x6e, 0x64,
0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6c, 0x69, 0x6e, 0x65, 0x45, 0x6e, 0x64, 0x12,
0x20, 0x0a, 0x0c, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x6e, 0x65, 0x77, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6c, 0x69, 0x6e, 0x65, 0x45, 0x6e, 0x64, 0x4e, 0x65,
0x77, 0x22, 0xab, 0x01, 0x0a, 0x0f, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x0b, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x68, 0x65,
0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x70, 0x63,
0x2e, 0x48, 0x75, 0x6e, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0a, 0x68, 0x75, 0x6e,
0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x24, 0x0a,
0x0e, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65,
0x53, 0x68, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f,
0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x61, 0x32,
0x96, 0x02, 0x0a, 0x0b, 0x44, 0x69, 0x66, 0x66, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x35, 0x0a, 0x07, 0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63,
0x2e, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72,
0x70, 0x63, 0x2e, 0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0d, 0x44, 0x69, 0x66, 0x66, 0x53, 0x68,
0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69,
0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e,
0x44, 0x69, 0x66, 0x66, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x44, 0x69,
0x66, 0x66, 0x48, 0x75, 0x6e, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e,
0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x66, 0x66, 0x48, 0x75, 0x6e, 0x6b, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x66, 0x66, 0x48, 0x75, 0x6e, 0x6b, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x36, 0x0a, 0x07, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x12, 0x13, 0x2e, 0x72, 0x70,
0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x14, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67,
0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x70,
0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -254,24 +823,41 @@ func file_diff_proto_rawDescGZIP() []byte {
return file_diff_proto_rawDescData
}
var file_diff_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_diff_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_diff_proto_goTypes = []interface{}{
(*DiffRequest)(nil), // 0: rpc.DiffRequest
(*RawDiffResponse)(nil), // 1: rpc.RawDiffResponse
(*DiffShortStatResponse)(nil), // 2: rpc.DiffShortStatResponse
(*ReadRequest)(nil), // 3: rpc.ReadRequest
(*DiffRequest)(nil), // 0: rpc.DiffRequest
(*RawDiffResponse)(nil), // 1: rpc.RawDiffResponse
(*DiffShortStatResponse)(nil), // 2: rpc.DiffShortStatResponse
(*HunkHeader)(nil), // 3: rpc.HunkHeader
(*DiffFileHeader)(nil), // 4: rpc.DiffFileHeader
(*DiffFileHunkHeaders)(nil), // 5: rpc.DiffFileHunkHeaders
(*GetDiffHunkHeadersRequest)(nil), // 6: rpc.GetDiffHunkHeadersRequest
(*GetDiffHunkHeadersResponse)(nil), // 7: rpc.GetDiffHunkHeadersResponse
(*DiffCutRequest)(nil), // 8: rpc.DiffCutRequest
(*DiffCutResponse)(nil), // 9: rpc.DiffCutResponse
(*ReadRequest)(nil), // 10: rpc.ReadRequest
}
var file_diff_proto_depIdxs = []int32{
3, // 0: rpc.DiffRequest.base:type_name -> rpc.ReadRequest
0, // 1: rpc.DiffService.RawDiff:input_type -> rpc.DiffRequest
0, // 2: rpc.DiffService.DiffShortStat:input_type -> rpc.DiffRequest
1, // 3: rpc.DiffService.RawDiff:output_type -> rpc.RawDiffResponse
2, // 4: rpc.DiffService.DiffShortStat:output_type -> rpc.DiffShortStatResponse
3, // [3:5] is the sub-list for method output_type
1, // [1:3] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
10, // 0: rpc.DiffRequest.base:type_name -> rpc.ReadRequest
4, // 1: rpc.DiffFileHunkHeaders.file_header:type_name -> rpc.DiffFileHeader
3, // 2: rpc.DiffFileHunkHeaders.hunk_headers:type_name -> rpc.HunkHeader
10, // 3: rpc.GetDiffHunkHeadersRequest.base:type_name -> rpc.ReadRequest
5, // 4: rpc.GetDiffHunkHeadersResponse.files:type_name -> rpc.DiffFileHunkHeaders
10, // 5: rpc.DiffCutRequest.base:type_name -> rpc.ReadRequest
3, // 6: rpc.DiffCutResponse.hunk_header:type_name -> rpc.HunkHeader
0, // 7: rpc.DiffService.RawDiff:input_type -> rpc.DiffRequest
0, // 8: rpc.DiffService.DiffShortStat:input_type -> rpc.DiffRequest
6, // 9: rpc.DiffService.GetDiffHunkHeaders:input_type -> rpc.GetDiffHunkHeadersRequest
8, // 10: rpc.DiffService.DiffCut:input_type -> rpc.DiffCutRequest
1, // 11: rpc.DiffService.RawDiff:output_type -> rpc.RawDiffResponse
2, // 12: rpc.DiffService.DiffShortStat:output_type -> rpc.DiffShortStatResponse
7, // 13: rpc.DiffService.GetDiffHunkHeaders:output_type -> rpc.GetDiffHunkHeadersResponse
9, // 14: rpc.DiffService.DiffCut:output_type -> rpc.DiffCutResponse
11, // [11:15] is the sub-list for method output_type
7, // [7:11] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_diff_proto_init() }
@ -317,6 +903,90 @@ func file_diff_proto_init() {
return nil
}
}
file_diff_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HunkHeader); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_diff_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DiffFileHeader); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_diff_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DiffFileHunkHeaders); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_diff_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetDiffHunkHeadersRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_diff_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetDiffHunkHeadersResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_diff_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DiffCutRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_diff_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DiffCutResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -324,7 +994,7 @@ func file_diff_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_diff_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -24,6 +24,8 @@ const _ = grpc.SupportPackageIsVersion7
type DiffServiceClient interface {
RawDiff(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (DiffService_RawDiffClient, error)
DiffShortStat(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffShortStatResponse, error)
GetDiffHunkHeaders(ctx context.Context, in *GetDiffHunkHeadersRequest, opts ...grpc.CallOption) (*GetDiffHunkHeadersResponse, error)
DiffCut(ctx context.Context, in *DiffCutRequest, opts ...grpc.CallOption) (*DiffCutResponse, error)
}
type diffServiceClient struct {
@ -75,12 +77,32 @@ func (c *diffServiceClient) DiffShortStat(ctx context.Context, in *DiffRequest,
return out, nil
}
func (c *diffServiceClient) GetDiffHunkHeaders(ctx context.Context, in *GetDiffHunkHeadersRequest, opts ...grpc.CallOption) (*GetDiffHunkHeadersResponse, error) {
out := new(GetDiffHunkHeadersResponse)
err := c.cc.Invoke(ctx, "/rpc.DiffService/GetDiffHunkHeaders", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *diffServiceClient) DiffCut(ctx context.Context, in *DiffCutRequest, opts ...grpc.CallOption) (*DiffCutResponse, error) {
out := new(DiffCutResponse)
err := c.cc.Invoke(ctx, "/rpc.DiffService/DiffCut", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DiffServiceServer is the server API for DiffService service.
// All implementations must embed UnimplementedDiffServiceServer
// for forward compatibility
type DiffServiceServer interface {
RawDiff(*DiffRequest, DiffService_RawDiffServer) error
DiffShortStat(context.Context, *DiffRequest) (*DiffShortStatResponse, error)
GetDiffHunkHeaders(context.Context, *GetDiffHunkHeadersRequest) (*GetDiffHunkHeadersResponse, error)
DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error)
mustEmbedUnimplementedDiffServiceServer()
}
@ -94,6 +116,12 @@ func (UnimplementedDiffServiceServer) RawDiff(*DiffRequest, DiffService_RawDiffS
func (UnimplementedDiffServiceServer) DiffShortStat(context.Context, *DiffRequest) (*DiffShortStatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DiffShortStat not implemented")
}
func (UnimplementedDiffServiceServer) GetDiffHunkHeaders(context.Context, *GetDiffHunkHeadersRequest) (*GetDiffHunkHeadersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDiffHunkHeaders not implemented")
}
func (UnimplementedDiffServiceServer) DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DiffCut not implemented")
}
func (UnimplementedDiffServiceServer) mustEmbedUnimplementedDiffServiceServer() {}
// UnsafeDiffServiceServer may be embedded to opt out of forward compatibility for this service.
@ -146,6 +174,42 @@ func _DiffService_DiffShortStat_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _DiffService_GetDiffHunkHeaders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetDiffHunkHeadersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DiffServiceServer).GetDiffHunkHeaders(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rpc.DiffService/GetDiffHunkHeaders",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiffServiceServer).GetDiffHunkHeaders(ctx, req.(*GetDiffHunkHeadersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _DiffService_DiffCut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DiffCutRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DiffServiceServer).DiffCut(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rpc.DiffService/DiffCut",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiffServiceServer).DiffCut(ctx, req.(*DiffCutRequest))
}
return interceptor(ctx, in, info, handler)
}
// DiffService_ServiceDesc is the grpc.ServiceDesc for DiffService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -157,6 +221,14 @@ var DiffService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DiffShortStat",
Handler: _DiffService_DiffShortStat_Handler,
},
{
MethodName: "GetDiffHunkHeaders",
Handler: _DiffService_GetDiffHunkHeaders_Handler,
},
{
MethodName: "DiffCut",
Handler: _DiffService_DiffCut_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@ -10,6 +10,7 @@ import (
"fmt"
"time"
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/store"
@ -20,12 +21,57 @@ import (
)
type CommentCreateInput struct {
ParentID int64 `json:"parent_id"`
Text string `json:"text"`
Payload *types.PullRequestActivityPayloadComment `json:"payload"`
// ParentID is set only for replies
ParentID int64 `json:"parent_id"`
// Text is comment text
Text string `json:"text"`
// Used only for code comments
TargetCommitSHA string `json:"target_commit_sha"`
SourceCommitSHA string `json:"source_commit_sha"`
Path string `json:"path"`
LineStart int `json:"line_start"`
LineStartNew bool `json:"line_start_new"`
LineEnd int `json:"line_end"`
LineEndNew bool `json:"line_end_new"`
}
// CommentCreate creates a new pull request comment (pull request activity, type=comment).
func (in *CommentCreateInput) IsReply() bool {
return in.ParentID != 0
}
func (in *CommentCreateInput) IsCodeComment() bool {
return in.SourceCommitSHA != ""
}
func (in *CommentCreateInput) Validate() error {
// TODO: Validate Text size.
if in.SourceCommitSHA == "" && in.TargetCommitSHA == "" {
return nil // not a code comment
}
if in.SourceCommitSHA == "" || in.TargetCommitSHA == "" {
return usererror.BadRequest("for code comments source commit SHA and target commit SHA must be provided")
}
if in.ParentID != 0 {
return usererror.BadRequest("can't create a reply that is a code comment")
}
if in.Path == "" {
return usererror.BadRequest("code comment requires file path")
}
if in.LineStart <= 0 || in.LineEnd <= 0 {
return usererror.BadRequest("code comments require line numbers")
}
return nil
}
// CommentCreate creates a new pull request comment (pull request activity, type=comment/code-comment).
//
//nolint:gocognit // refactor if needed
func (c *Controller) CommentCreate(
ctx context.Context,
session *auth.Session,
@ -43,17 +89,77 @@ func (c *Controller) CommentCreate(
return nil, fmt.Errorf("failed to find pull request by number: %w", err)
}
if errValidate := in.Validate(); errValidate != nil {
return nil, errValidate
}
act := getCommentActivity(session, pr, in)
if in.ParentID != 0 {
switch {
case in.IsCodeComment():
var cut gitrpc.DiffCutOutput
cut, err = c.gitRPCClient.DiffCut(ctx, &gitrpc.DiffCutParams{
ReadParams: gitrpc.ReadParams{RepoUID: repo.GitUID},
SourceCommitSHA: in.SourceCommitSHA,
SourceBranch: pr.SourceBranch,
TargetCommitSHA: in.TargetCommitSHA,
TargetBranch: pr.TargetBranch,
Path: in.Path,
LineStart: in.LineStart,
LineStartNew: in.LineStartNew,
LineEnd: in.LineEnd,
LineEndNew: in.LineEndNew,
})
if gitrpc.ErrorStatus(err) == gitrpc.StatusNotFound {
return nil, usererror.BadRequest(gitrpc.ErrorMessage(err))
}
if err != nil {
return nil, err
}
setAsCodeComment(act, cut, in.Path, in.SourceCommitSHA)
_ = act.SetPayload(&types.PullRequestActivityPayloadCodeComment{
Title: cut.Header.Text,
Lines: cut.Lines,
AnyNew: cut.AnyNew,
})
err = c.writeActivity(ctx, pr, act)
// Migrate the comment if necessary... Note: we still need to return the code comment as is.
needsNewLineMigrate := in.SourceCommitSHA != cut.LatestSourceSHA
needsOldLineMigrate := pr.MergeBaseSHA != nil && *pr.MergeBaseSHA != cut.MergeBaseSHA
if err == nil && (needsNewLineMigrate || needsOldLineMigrate) {
comments := []*types.CodeComment{act.AsCodeComment()}
if needsNewLineMigrate {
c.codeCommentMigrator.MigrateNew(ctx, repo.GitUID, cut.LatestSourceSHA, comments)
}
if needsOldLineMigrate {
c.codeCommentMigrator.MigrateOld(ctx, repo.GitUID, cut.MergeBaseSHA, comments)
}
if errMigrateUpdate := c.codeCommentView.UpdateAll(ctx, comments); errMigrateUpdate != nil {
// non-critical error
log.Ctx(ctx).Err(errMigrateUpdate).
Msgf("failed to migrate code comment to the latest source/merge-base commit SHA")
}
}
case in.ParentID != 0:
var parentAct *types.PullReqActivity
parentAct, err = c.checkIsReplyable(ctx, pr, in.ParentID)
if err != nil {
return nil, err
}
act.ParentID = &parentAct.ID
act.Kind = parentAct.Kind
_ = act.SetPayload(types.PullRequestActivityPayloadComment{})
err = c.writeReplyActivity(ctx, parentAct, act)
} else {
default:
_ = act.SetPayload(types.PullRequestActivityPayloadComment{})
err = c.writeActivity(ctx, pr, act)
}
if err != nil {
@ -162,7 +268,24 @@ func getCommentActivity(session *auth.Session, pr *types.PullReq, in *CommentCre
Author: *session.Principal.ToPrincipalInfo(),
}
_ = act.SetPayload(in.Payload)
return act
}
func setAsCodeComment(a *types.PullReqActivity, cut gitrpc.DiffCutOutput, path, sourceCommitSHA string) {
var falseBool bool
newLine := int64(cut.Header.NewLine)
newSpan := int64(cut.Header.NewSpan)
oldLine := int64(cut.Header.OldLine)
oldSpan := int64(cut.Header.OldSpan)
a.Type = enum.PullReqActivityTypeCodeComment
a.Kind = enum.PullReqActivityKindChangeComment
a.Outdated = &falseBool
a.CodeCommentMergeBaseSHA = &cut.MergeBaseSHA
a.CodeCommentSourceSHA = &sourceCommitSHA
a.CodeCommentPath = &path
a.CodeCommentLineNew = &newLine
a.CodeCommentSpanNew = &newSpan
a.CodeCommentLineOld = &oldLine
a.CodeCommentSpanOld = &oldSpan
}

View File

@ -6,9 +6,7 @@ package pullreq
import (
"context"
"errors"
"fmt"
"reflect"
"time"
"github.com/harness/gitness/internal/auth"
@ -17,30 +15,11 @@ import (
)
type CommentUpdateInput struct {
Text *string `json:"text"`
Payload *types.PullRequestActivityPayloadComment `json:"payload"`
Text string `json:"text"`
}
func (in *CommentUpdateInput) hasChanges(act *types.PullReqActivity) (bool, error) {
if in.Text != nil && *in.Text != act.Text {
return true, nil
}
if in.Payload != nil {
oldPayload, err := act.GetPayload()
if errors.Is(err, types.ErrNoPayload) {
return true, nil
}
if err != nil {
return false, fmt.Errorf("failed to get old payload: %w", err)
}
if !reflect.DeepEqual(oldPayload, in.Payload) {
return true, nil
}
}
return false, nil
func (in *CommentUpdateInput) hasChanges(act *types.PullReqActivity) bool {
return in.Text != act.Text
}
// CommentUpdate updates a pull request comment.
@ -67,23 +46,14 @@ func (c *Controller) CommentUpdate(
return nil, fmt.Errorf("failed to get comment: %w", err)
}
hasChanges, err := in.hasChanges(act)
if err != nil {
return nil, fmt.Errorf("failed to verify if input has changes: %w", err)
}
if !hasChanges {
if !in.hasChanges(act) {
return act, nil
}
now := time.Now().UnixMilli()
act.Edited = now
if in.Text != nil {
act.Text = *in.Text
}
if in.Payload != nil {
_ = act.SetPayload(in.Payload)
}
act.Text = in.Text
err = c.activityStore.Update(ctx, act)
if err != nil {

View File

@ -15,6 +15,7 @@ import (
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/auth/authz"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/harness/gitness/internal/services/codecomments"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/url"
"github.com/harness/gitness/lock"
@ -25,18 +26,20 @@ import (
)
type Controller struct {
db *sqlx.DB
urlProvider *url.Provider
authorizer authz.Authorizer
pullreqStore store.PullReqStore
activityStore store.PullReqActivityStore
reviewStore store.PullReqReviewStore
reviewerStore store.PullReqReviewerStore
repoStore store.RepoStore
principalStore store.PrincipalStore
gitRPCClient gitrpc.Interface
eventReporter *pullreqevents.Reporter
mtxManager lock.MutexManager
db *sqlx.DB
urlProvider *url.Provider
authorizer authz.Authorizer
pullreqStore store.PullReqStore
activityStore store.PullReqActivityStore
codeCommentView store.CodeCommentView
reviewStore store.PullReqReviewStore
reviewerStore store.PullReqReviewerStore
repoStore store.RepoStore
principalStore store.PrincipalStore
gitRPCClient gitrpc.Interface
eventReporter *pullreqevents.Reporter
mtxManager lock.MutexManager
codeCommentMigrator *codecomments.Migrator
}
func NewController(
@ -45,6 +48,7 @@ func NewController(
authorizer authz.Authorizer,
pullreqStore store.PullReqStore,
pullreqActivityStore store.PullReqActivityStore,
codeCommentView store.CodeCommentView,
pullreqReviewStore store.PullReqReviewStore,
pullreqReviewerStore store.PullReqReviewerStore,
repoStore store.RepoStore,
@ -52,20 +56,23 @@ func NewController(
gitRPCClient gitrpc.Interface,
eventReporter *pullreqevents.Reporter,
mtxManager lock.MutexManager,
codeCommentMigrator *codecomments.Migrator,
) *Controller {
return &Controller{
db: db,
urlProvider: urlProvider,
authorizer: authorizer,
pullreqStore: pullreqStore,
activityStore: pullreqActivityStore,
reviewStore: pullreqReviewStore,
reviewerStore: pullreqReviewerStore,
repoStore: repoStore,
principalStore: principalStore,
gitRPCClient: gitRPCClient,
eventReporter: eventReporter,
mtxManager: mtxManager,
db: db,
urlProvider: urlProvider,
authorizer: authorizer,
pullreqStore: pullreqStore,
activityStore: pullreqActivityStore,
codeCommentView: codeCommentView,
reviewStore: pullreqReviewStore,
reviewerStore: pullreqReviewerStore,
repoStore: repoStore,
principalStore: principalStore,
gitRPCClient: gitRPCClient,
codeCommentMigrator: codeCommentMigrator,
eventReporter: eventReporter,
mtxManager: mtxManager,
}
}

View File

@ -38,7 +38,7 @@ func (c *Controller) Commits(
gitRef = pr.SourceSHA
}
afterRef := pr.TargetBranch
if pr.State == enum.PullReqStateMerged {
if pr.MergeBaseSHA != nil {
afterRef = *pr.MergeBaseSHA
}

View File

@ -37,7 +37,7 @@ func (c *Controller) RawDiff(
headRef = pr.SourceSHA
}
baseRef := pr.TargetBranch
if pr.State == enum.PullReqStateMerged {
if pr.MergeBaseSHA != nil {
baseRef = *pr.MergeBaseSHA
}

View File

@ -124,8 +124,6 @@ func (c *Controller) State(ctx context.Context,
if in.State == enum.PullReqStateClosed {
// clear all merge (check) related fields
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
pr.MergeTargetSHA = nil
pr.MergeBaseSHA = nil
pr.MergeSHA = nil
pr.MergeConflicts = nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/internal/auth/authz"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/harness/gitness/internal/services/codecomments"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/url"
"github.com/harness/gitness/lock"
@ -23,13 +24,16 @@ var WireSet = wire.NewSet(
func ProvideController(db *sqlx.DB, urlProvider *url.Provider, authorizer authz.Authorizer,
pullReqStore store.PullReqStore, pullReqActivityStore store.PullReqActivityStore,
codeCommentsView store.CodeCommentView,
pullReqReviewStore store.PullReqReviewStore, pullReqReviewerStore store.PullReqReviewerStore,
repoStore store.RepoStore, principalStore store.PrincipalStore,
rpcClient gitrpc.Interface, eventReporter *pullreqevents.Reporter,
mtxManager lock.MutexManager) *Controller {
mtxManager lock.MutexManager, codeCommentMigrator *codecomments.Migrator) *Controller {
return NewController(db, urlProvider, authorizer,
pullReqStore, pullReqActivityStore,
codeCommentsView,
pullReqReviewStore, pullReqReviewerStore,
repoStore, principalStore,
rpcClient, eventReporter, mtxManager)
rpcClient, eventReporter,
mtxManager, codeCommentMigrator)
}

View File

@ -22,6 +22,7 @@ import (
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)

View File

@ -13,6 +13,7 @@ import (
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)

View File

@ -0,0 +1,200 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package codecomments
import (
"context"
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
// Migrator is a utility used to migrate code comments after update of the pull request's source branch.
type Migrator struct {
gitRPCClient gitrpc.Interface
}
// MigrateNew updates the "+" (the added lines) part of code comments
// after a new commit on the pull request's source branch.
// The parameter newSHA should contain the latest commit SHA of the pull request's source branch.
func (migrator *Migrator) MigrateNew(
ctx context.Context,
repoGitUID string,
newSHA string,
comments []*types.CodeComment,
) {
migrator.migrate(
ctx,
repoGitUID,
newSHA,
comments,
func(codeComment *types.CodeComment) string {
return codeComment.SourceSHA
},
func(codeComment *types.CodeComment, sha string) {
codeComment.SourceSHA = sha
},
func(codeComment *types.CodeComment) (int, int) {
return codeComment.LineNew, codeComment.LineNew + codeComment.SpanNew - 1
},
func(codeComment *types.CodeComment, line int) {
codeComment.LineNew += line
},
)
}
// MigrateOld updates the "-" (the removes lines) part of code comments
// after the pull request's change of the merge base commit.
func (migrator *Migrator) MigrateOld(
ctx context.Context,
repoGitUID string,
newSHA string,
comments []*types.CodeComment,
) {
migrator.migrate(
ctx,
repoGitUID,
newSHA,
comments,
func(codeComment *types.CodeComment) string {
return codeComment.MergeBaseSHA
},
func(codeComment *types.CodeComment, sha string) {
codeComment.MergeBaseSHA = sha
},
func(codeComment *types.CodeComment) (int, int) {
return codeComment.LineOld, codeComment.LineOld + codeComment.SpanOld - 1
},
func(codeComment *types.CodeComment, line int) {
codeComment.LineOld += line
},
)
}
//nolint:gocognit // refactor if needed
func (migrator *Migrator) migrate(
ctx context.Context,
repoGitUID string,
newSHA string,
comments []*types.CodeComment,
getSHA func(codeComment *types.CodeComment) string,
setSHA func(codeComment *types.CodeComment, sha string),
getCommentStartEnd func(codeComment *types.CodeComment) (int, int),
updateCommentLine func(codeComment *types.CodeComment, line int),
) {
if len(comments) == 0 {
return
}
commitMap := mapCodeComments(comments, getSHA)
for commentSHA, fileMap := range commitMap {
// get all hunk headers for the diff between the SHA that's stored in the comment and the new SHA.
diffSummary, errDiff := migrator.gitRPCClient.GetDiffHunkHeaders(ctx, gitrpc.GetDiffHunkHeadersParams{
ReadParams: gitrpc.ReadParams{
RepoUID: repoGitUID,
},
SourceCommitSHA: commentSHA,
TargetCommitSHA: newSHA,
})
if gitrpc.ErrorStatus(errDiff) == gitrpc.StatusNotFound {
// Handle the commit SHA not found error and mark all code comments as outdated.
for _, codeComments := range fileMap {
for _, codeComment := range codeComments {
codeComment.Outdated = true
}
}
continue
}
if errDiff != nil {
log.Ctx(ctx).Err(errDiff).
Msgf("failed to get git diff between comment's sha %s and the latest %s", commentSHA, newSHA)
continue
}
// Traverse all the changed files
for _, file := range diffSummary.Files {
var codeComments []*types.CodeComment
codeComments = fileMap[file.FileHeader.OldName]
// Handle file renames
if file.FileHeader.OldName != file.FileHeader.NewName {
if len(codeComments) == 0 {
// If the code comments are not found using the old name of the file, try with the new name.
codeComments = fileMap[file.FileHeader.NewName]
} else {
// Update the code comment's path to the new file name
for _, cc := range codeComments {
cc.Path = file.FileHeader.NewName
}
}
}
// Handle file delete
if len(file.HunkHeaders) == 1 && file.HunkHeaders[0].NewLine == 0 && file.HunkHeaders[0].NewSpan == 0 {
for _, codeComment := range codeComments {
codeComment.Outdated = true
}
continue
}
for _, hunk := range file.HunkHeaders {
hunkStart := hunk.NewLine
hunkEnd := hunk.NewLine + hunk.NewSpan - 1
for _, cc := range codeComments {
commentStart, commentEnd := getCommentStartEnd(cc)
if commentEnd < hunkStart {
continue
}
outdated := commentStart <= hunkEnd
cc.Outdated = outdated
if outdated {
continue
}
updateCommentLine(cc, hunk.NewSpan-hunk.OldSpan)
}
}
}
for _, codeComments := range fileMap {
for _, codeComment := range codeComments {
if codeComment.Outdated {
continue
}
setSHA(codeComment, newSHA)
}
}
}
}
// mapCodeComments groups code comments to maps, first by commit SHA and then by file name.
// It assumes the incoming list is already sorted.
func mapCodeComments(
comments []*types.CodeComment,
extractSHA func(*types.CodeComment) string,
) map[string]map[string][]*types.CodeComment {
commitMap := map[string]map[string][]*types.CodeComment{}
for _, comment := range comments {
commitSHA := extractSHA(comment)
fileMap := commitMap[commitSHA]
if fileMap == nil {
fileMap = map[string][]*types.CodeComment{}
}
fileComments := fileMap[comment.Path]
fileComments = append(fileComments, comment)
fileMap[comment.Path] = fileComments
commitMap[commitSHA] = fileMap
}
return commitMap
}

View File

@ -0,0 +1,23 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package codecomments
import (
"github.com/harness/gitness/gitrpc"
"github.com/google/wire"
)
var WireSet = wire.NewSet(
ProvideMigrator,
)
func ProvideMigrator(
gitRPCClient gitrpc.Interface,
) *Migrator {
return &Migrator{
gitRPCClient: gitRPCClient,
}
}

View File

@ -36,8 +36,6 @@ func (s *Service) triggerPREventOnBranchUpdate(ctx context.Context,
// reset merge-check fields for new run
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
pr.MergeTargetSHA = nil
pr.MergeBaseSHA = nil
pr.MergeSHA = nil
pr.MergeConflicts = nil
return nil
@ -85,8 +83,6 @@ func (s *Service) closePullReqOnBranchDelete(ctx context.Context,
pr.State = enum.PullReqStateClosed
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
pr.MergeTargetSHA = nil
pr.MergeBaseSHA = nil
pr.MergeSHA = nil
pr.MergeConflicts = nil

View File

@ -0,0 +1,72 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pullreq
import (
"context"
"fmt"
"github.com/harness/gitness/events"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
func (s *Service) updateCodeCommentsOnBranchUpdate(ctx context.Context,
event *events.Event[*pullreqevents.BranchUpdatedPayload],
) error {
oldSourceSHA := event.Payload.OldSHA // NOTE: we're ignoring the old value and instead try to update all
newSourceSHA := event.Payload.NewSHA
log.Ctx(ctx).Debug().
Str("oldSHA", oldSourceSHA).
Str("newSHA", newSourceSHA).
Msgf("code comment update after source branch update")
repoGit, err := s.repoGitInfoCache.Get(ctx, event.Payload.SourceRepoID)
if err != nil {
return fmt.Errorf("failed to get repo git info: %w", err)
}
codeComments, err := s.codeCommentView.ListNotAtSourceSHA(ctx, event.Payload.PullReqID, newSourceSHA)
if err != nil {
return fmt.Errorf("failed to get list of code comments for update after source branch update: %w", err)
}
s.codeCommentMigrator.MigrateNew(ctx, repoGit.GitUID, newSourceSHA, codeComments)
err = s.codeCommentView.UpdateAll(ctx, codeComments)
if err != nil {
return fmt.Errorf("failed to update code comments after source branch update: %w", err)
}
return nil
}
func (s *Service) updateCodeCommentsOnMergeBaseUpdate(ctx context.Context,
pr *types.PullReq,
gitUID string,
oldMergeBaseSHA, newMergeBaseSHA string,
) error {
log.Ctx(ctx).Debug().
Str("oldSHA", oldMergeBaseSHA).
Str("newSHA", newMergeBaseSHA).
Msgf("code comment update after merge base update")
codeComments, err := s.codeCommentView.ListNotAtMergeBaseSHA(ctx, pr.ID, newMergeBaseSHA)
if err != nil {
return fmt.Errorf("failed to get list of code comments for update after merge base update: %w", err)
}
s.codeCommentMigrator.MigrateOld(ctx, gitUID, newMergeBaseSHA, codeComments)
err = s.codeCommentView.UpdateAll(ctx, codeComments)
if err != nil {
return fmt.Errorf("failed to update code comments after merge base update: %w", err)
}
return nil
}

View File

@ -122,7 +122,7 @@ func (s *Service) deleteMergeRef(ctx context.Context, principalID int64, repoID
return nil
}
//nolint:funlen // refactor if required.
//nolint:funlen,gocognit // refactor if required.
func (s *Service) updateMergeData(
ctx context.Context,
principalID int64,
@ -161,14 +161,14 @@ func (s *Service) updateMergeData(
}()
// load repository objects
targetRepo, err := s.repoStore.Find(ctx, pr.TargetRepoID)
targetRepo, err := s.repoGitInfoCache.Get(ctx, pr.TargetRepoID)
if err != nil {
return err
}
sourceRepo := targetRepo
if pr.TargetRepoID != pr.SourceRepoID {
sourceRepo, err = s.repoStore.Find(ctx, pr.SourceRepoID)
sourceRepo, err = s.repoGitInfoCache.Get(ctx, pr.SourceRepoID)
if err != nil {
return err
}
@ -187,8 +187,7 @@ func (s *Service) updateMergeData(
}
// call merge and store output in pr merge reference.
var output gitrpc.MergeOutput
output, err = s.gitRPCClient.Merge(ctx, &gitrpc.MergeParams{
output, err := s.gitRPCClient.Merge(ctx, &gitrpc.MergeParams{
WriteParams: gitrpc.WriteParams{
RepoUID: targetRepo.GitUID,
Actor: gitrpc.Identity{
@ -212,8 +211,10 @@ func (s *Service) updateMergeData(
isNotMergeableError := gitrpc.ErrorStatus(err) == gitrpc.StatusNotMergeable
if err != nil && !isNotMergeableError {
return fmt.Errorf("merge check failed for %s and %s with err: %w",
targetRepo.UID+":"+pr.TargetBranch, sourceRepo.UID+":"+pr.SourceBranch, err)
return fmt.Errorf("merge check failed for %d:%s and %d:%s with err: %w",
targetRepo.ID, pr.TargetBranch,
sourceRepo.ID, pr.SourceBranch,
err)
}
// Update DB in both cases (failure or success)
@ -225,8 +226,8 @@ func (s *Service) updateMergeData(
if isNotMergeableError {
// TODO: gitrpc should return sha's either way, and also conflicting files!
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
pr.MergeTargetSHA = nil
pr.MergeBaseSHA = nil
pr.MergeTargetSHA = &output.BaseSHA
pr.MergeBaseSHA = &output.MergeBaseSHA
pr.MergeSHA = nil
pr.MergeConflicts = nil
} else {
@ -242,5 +243,14 @@ func (s *Service) updateMergeData(
return fmt.Errorf("failed to update PR merge ref in db with error: %w", err)
}
if pr.MergeBaseSHA != nil && *pr.MergeBaseSHA != output.MergeBaseSHA {
oldMergeBaseSHA := *pr.MergeBaseSHA
newMergeBaseSHA := output.MergeBaseSHA
err = s.updateCodeCommentsOnMergeBaseUpdate(ctx, pr, sourceRepo.GitUID, oldMergeBaseSHA, newMergeBaseSHA)
if err != nil {
return fmt.Errorf("failed to update code comment after merge base SHA change: %w", err)
}
}
return nil
}

View File

@ -13,6 +13,7 @@ import (
"github.com/harness/gitness/gitrpc"
gitevents "github.com/harness/gitness/internal/events/git"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/harness/gitness/internal/services/codecomments"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/pubsub"
"github.com/harness/gitness/stream"
@ -22,14 +23,16 @@ import (
)
type Service struct {
pullreqEvReporter *pullreqevents.Reporter
gitRPCClient gitrpc.Interface
db *sqlx.DB
repoGitInfoCache store.RepoGitInfoCache
principalCache store.PrincipalInfoCache
repoStore store.RepoStore
pullreqStore store.PullReqStore
activityStore store.PullReqActivityStore
pullreqEvReporter *pullreqevents.Reporter
gitRPCClient gitrpc.Interface
db *sqlx.DB
repoGitInfoCache store.RepoGitInfoCache
principalCache store.PrincipalInfoCache
repoStore store.RepoStore
pullreqStore store.PullReqStore
activityStore store.PullReqActivityStore
codeCommentView store.CodeCommentView
codeCommentMigrator *codecomments.Migrator
cancelMutex sync.Mutex
cancelMergability map[string]context.CancelFunc
@ -50,19 +53,23 @@ func New(ctx context.Context,
repoStore store.RepoStore,
pullreqStore store.PullReqStore,
activityStore store.PullReqActivityStore,
codeCommentView store.CodeCommentView,
codeCommentMigrator *codecomments.Migrator,
bus pubsub.PubSub,
) (*Service, error) {
service := &Service{
pullreqEvReporter: pullreqEvReporter,
gitRPCClient: gitRPCClient,
db: db,
repoGitInfoCache: repoGitInfoCache,
principalCache: principalCache,
repoStore: repoStore,
pullreqStore: pullreqStore,
activityStore: activityStore,
cancelMergability: make(map[string]context.CancelFunc),
pubsub: bus,
pullreqEvReporter: pullreqEvReporter,
gitRPCClient: gitRPCClient,
db: db,
repoGitInfoCache: repoGitInfoCache,
principalCache: principalCache,
repoStore: repoStore,
pullreqStore: pullreqStore,
activityStore: activityStore,
codeCommentView: codeCommentView,
codeCommentMigrator: codeCommentMigrator,
cancelMergability: make(map[string]context.CancelFunc),
pubsub: bus,
}
var err error
@ -179,5 +186,25 @@ func New(ctx context.Context,
return nil
}, pubsub.WithChannelNamespace("pullreq"))
// mergability check
const groupPullReqCodeComments = "gitness:pullreq:codecomments"
_, err = pullreqEvReaderFactory.Launch(ctx, groupPullReqCodeComments, config.InstanceID,
func(r *pullreqevents.Reader) error {
const idleTimeout = 10 * time.Second
r.Configure(
stream.WithConcurrency(3),
stream.WithHandlerOptions(
stream.WithIdleTimeout(idleTimeout),
stream.WithMaxRetries(2),
))
_ = r.RegisterBranchUpdated(service.updateCodeCommentsOnBranchUpdate)
return nil
})
if err != nil {
return nil, err
}
return service, nil
}

View File

@ -11,6 +11,7 @@ import (
"github.com/harness/gitness/gitrpc"
gitevents "github.com/harness/gitness/internal/events/git"
pullreqevents "github.com/harness/gitness/internal/events/pullreq"
"github.com/harness/gitness/internal/services/codecomments"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/pubsub"
"github.com/harness/gitness/types"
@ -35,8 +36,11 @@ func ProvideService(ctx context.Context,
repoStore store.RepoStore,
pullreqStore store.PullReqStore,
activityStore store.PullReqActivityStore,
codeCommentView store.CodeCommentView,
codeCommentMigrator *codecomments.Migrator,
pubsub pubsub.PubSub,
) (*Service, error) {
return New(ctx, config, gitReaderFactory, pullReqEvFactory, pullReqEvReporter, gitRPCClient,
db, repoGitInfoCache, principalCache, repoStore, pullreqStore, activityStore, pubsub)
db, repoGitInfoCache, principalCache, repoStore, pullreqStore, activityStore,
codeCommentView, codeCommentMigrator, pubsub)
}

View File

@ -20,7 +20,10 @@ type Services struct {
bms *pullreq.Service
}
func ProvideServices(ws *webhook.Service, bms *pullreq.Service) Services {
func ProvideServices(
ws *webhook.Service,
bms *pullreq.Service,
) Services {
return Services{
ws: ws,
bms: bms,

View File

@ -302,6 +302,22 @@ type (
List(ctx context.Context, prID int64, opts *types.PullReqActivityFilter) ([]*types.PullReqActivity, error)
}
// CodeCommentView is to manipulate only code-comment subset of PullReqActivity.
// It's used by internal service that migrates code comment line numbers after new commits.
CodeCommentView interface {
// ListNotAtSourceSHA loads code comments that need to be updated after a new commit.
// Resulting list is ordered by the file name and the relevant line number.
ListNotAtSourceSHA(ctx context.Context, prID int64, sourceSHA string) ([]*types.CodeComment, error)
// ListNotAtMergeBaseSHA loads code comments that need to be updated after merge base update.
// Resulting list is ordered by the file name and the relevant line number.
ListNotAtMergeBaseSHA(ctx context.Context, prID int64, targetSHA string) ([]*types.CodeComment, error)
// UpdateAll updates code comments (pull request activity of types code-comment).
// entities coming from the input channel.
UpdateAll(ctx context.Context, codeComments []*types.CodeComment) error
}
// PullReqReviewStore defines the pull request review storage.
PullReqReviewStore interface {
// Find returns the pull request review entity or an error if it doesn't exist.

View File

@ -0,0 +1,151 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package database
import (
"context"
"time"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
var _ store.CodeCommentView = (*CodeCommentView)(nil)
// NewCodeCommentView returns a new CodeCommentView.
func NewCodeCommentView(db *sqlx.DB) *CodeCommentView {
return &CodeCommentView{
db: db,
}
}
// CodeCommentView implements store.CodeCommentView backed by a relational database.
type CodeCommentView struct {
db *sqlx.DB
}
// ListNotAtSourceSHA lists all code comments not already at the provided source SHA.
func (s *CodeCommentView) ListNotAtSourceSHA(ctx context.Context,
prID int64, sourceSHA string,
) ([]*types.CodeComment, error) {
return s.list(ctx, prID, "", sourceSHA)
}
// ListNotAtMergeBaseSHA lists all code comments not already at the provided merge base SHA.
func (s *CodeCommentView) ListNotAtMergeBaseSHA(ctx context.Context,
prID int64, mergeBaseSHA string,
) ([]*types.CodeComment, error) {
return s.list(ctx, prID, mergeBaseSHA, "")
}
// list is used by internal service that updates line numbers of code comments after
// branch updates and requires either mergeBaseSHA or sourceSHA but not both.
// Resulting list is ordered by the file name and the relevant line number.
func (s *CodeCommentView) list(ctx context.Context,
prID int64, mergeBaseSHA, sourceSHA string,
) ([]*types.CodeComment, error) {
const codeCommentColumns = `
pullreq_activity_id
,pullreq_activity_version
,pullreq_activity_updated
,coalesce(pullreq_activity_outdated, false) as "pullreq_activity_outdated"
,coalesce(pullreq_activity_code_comment_merge_base_sha, '') as "pullreq_activity_code_comment_merge_base_sha"
,coalesce(pullreq_activity_code_comment_source_sha, '') as "pullreq_activity_code_comment_source_sha"
,coalesce(pullreq_activity_code_comment_path, '') as "pullreq_activity_code_comment_path"
,coalesce(pullreq_activity_code_comment_line_new, 1) as "pullreq_activity_code_comment_line_new"
,coalesce(pullreq_activity_code_comment_span_new, 0) as "pullreq_activity_code_comment_span_new"
,coalesce(pullreq_activity_code_comment_line_old, 1) as "pullreq_activity_code_comment_line_old"
,coalesce(pullreq_activity_code_comment_span_old, 0) as "pullreq_activity_code_comment_span_old"`
stmt := builder.
Select(codeCommentColumns).
From("pullreq_activities").
Where("pullreq_activity_pullreq_id = ?", prID).
Where("not pullreq_activity_outdated").
Where("pullreq_activity_type = ?", enum.PullReqActivityTypeCodeComment).
Where("pullreq_activity_kind = ?", enum.PullReqActivityKindChangeComment).
Where("pullreq_activity_deleted is null and pullreq_activity_parent_id is null")
if mergeBaseSHA != "" {
stmt = stmt.
Where("pullreq_activity_code_comment_merge_base_sha <> ?", mergeBaseSHA)
} else {
stmt = stmt.
Where("pullreq_activity_code_comment_source_sha <> ?", sourceSHA)
}
stmt = stmt.OrderBy("pullreq_activity_code_comment_path asc",
"pullreq_activity_code_comment_line_new asc")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert pull request activity query to sql")
}
result := make([]*types.CodeComment, 0)
db := dbtx.GetAccessor(ctx, s.db)
if err = db.SelectContext(ctx, &result, sql, args...); err != nil {
return nil, processSQLErrorf(err, "Failed executing code comment list query")
}
return result, nil
}
// UpdateAll updates all code comments provided in the slice.
func (s *CodeCommentView) UpdateAll(ctx context.Context, codeComments []*types.CodeComment) error {
const sqlQuery = `
UPDATE pullreq_activities
SET
pullreq_activity_version = :pullreq_activity_version
,pullreq_activity_updated = :pullreq_activity_updated
,pullreq_activity_outdated = :pullreq_activity_outdated
,pullreq_activity_code_comment_merge_base_sha = :pullreq_activity_code_comment_merge_base_sha
,pullreq_activity_code_comment_source_sha = :pullreq_activity_code_comment_source_sha
,pullreq_activity_code_comment_path = :pullreq_activity_code_comment_path
,pullreq_activity_code_comment_line_new = :pullreq_activity_code_comment_line_new
,pullreq_activity_code_comment_span_new = :pullreq_activity_code_comment_span_new
,pullreq_activity_code_comment_line_old = :pullreq_activity_code_comment_line_old
,pullreq_activity_code_comment_span_old = :pullreq_activity_code_comment_span_old
WHERE pullreq_activity_id = :pullreq_activity_id AND pullreq_activity_version = :pullreq_activity_version - 1`
db := dbtx.GetAccessor(ctx, s.db)
stmt, err := db.PrepareNamedContext(ctx, sqlQuery)
if err != nil {
return processSQLErrorf(err, "Failed to prepare update statement for update code comments")
}
updatedAt := time.Now()
for _, codeComment := range codeComments {
codeComment.Version++
codeComment.Updated = updatedAt.UnixMilli()
result, err := stmt.ExecContext(ctx, codeComment)
if err != nil {
return processSQLErrorf(err, "Failed to update code comment=%d", codeComment.ID)
}
count, err := result.RowsAffected()
if err != nil {
return processSQLErrorf(err, "Failed to get number of updated rows for code comment=%d", codeComment.ID)
}
if count == 0 {
log.Ctx(ctx).Warn().Msgf("Version conflict when trying to update code comment=%d", codeComment.ID)
continue
}
}
return nil
}

View File

@ -0,0 +1,2 @@
-- Can't migrate down from this point.
-- This file must be present here.

View File

@ -0,0 +1,9 @@
ALTER TABLE pullreq_activities
DROP COLUMN pullreq_activity_outdated,
DROP COLUMN pullreq_activity_code_comment_merge_base_sha,
DROP COLUMN pullreq_activity_code_comment_source_sha,
DROP COLUMN pullreq_activity_code_comment_path,
DROP COLUMN pullreq_activity_code_comment_line_new,
DROP COLUMN pullreq_activity_code_comment_span_new,
DROP COLUMN pullreq_activity_code_comment_line_old,
DROP COLUMN pullreq_activity_code_comment_span_old;

View File

@ -0,0 +1,9 @@
ALTER TABLE pullreq_activities
ADD COLUMN pullreq_activity_outdated BOOLEAN,
ADD COLUMN pullreq_activity_code_comment_merge_base_sha TEXT,
ADD COLUMN pullreq_activity_code_comment_source_sha TEXT,
ADD COLUMN pullreq_activity_code_comment_path TEXT,
ADD COLUMN pullreq_activity_code_comment_line_new INTEGER,
ADD COLUMN pullreq_activity_code_comment_span_new INTEGER,
ADD COLUMN pullreq_activity_code_comment_line_old INTEGER,
ADD COLUMN pullreq_activity_code_comment_span_old INTEGER;

View File

@ -0,0 +1,2 @@
-- Can't migrate down from this point.
-- This file must be present here.

View File

@ -0,0 +1,8 @@
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_outdated;
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_code_comment_merge_base_sha;
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_code_comment_source_sha;
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_code_comment_path;
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_code_comment_line_new;
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_code_comment_span_new;
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_code_comment_line_old;
ALTER TABLE pullreq_activities DROP COLUMN pullreq_activity_code_comment_span_old;

View File

@ -0,0 +1,8 @@
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_outdated BOOLEAN;
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_code_comment_merge_base_sha TEXT;
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_code_comment_source_sha TEXT;
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_code_comment_path TEXT;
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_code_comment_line_new INTEGER;
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_code_comment_span_new INTEGER;
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_code_comment_line_old INTEGER;
ALTER TABLE pullreq_activities ADD COLUMN pullreq_activity_code_comment_span_old INTEGER;

View File

@ -68,6 +68,15 @@ type pullReqActivity struct {
ResolvedBy null.Int `db:"pullreq_activity_resolved_by"`
Resolved null.Int `db:"pullreq_activity_resolved"`
Outdated null.Bool `db:"pullreq_activity_outdated"`
CodeCommentMergeBaseSHA null.String `db:"pullreq_activity_code_comment_merge_base_sha"`
CodeCommentSourceSHA null.String `db:"pullreq_activity_code_comment_source_sha"`
CodeCommentPath null.String `db:"pullreq_activity_code_comment_path"`
CodeCommentLineNew null.Int `db:"pullreq_activity_code_comment_line_new"`
CodeCommentSpanNew null.Int `db:"pullreq_activity_code_comment_span_new"`
CodeCommentLineOld null.Int `db:"pullreq_activity_code_comment_line_old"`
CodeCommentSpanOld null.Int `db:"pullreq_activity_code_comment_span_old"`
}
const (
@ -91,7 +100,15 @@ const (
,pullreq_activity_payload
,pullreq_activity_metadata
,pullreq_activity_resolved_by
,pullreq_activity_resolved`
,pullreq_activity_resolved
,pullreq_activity_outdated
,pullreq_activity_code_comment_merge_base_sha
,pullreq_activity_code_comment_source_sha
,pullreq_activity_code_comment_path
,pullreq_activity_code_comment_line_new
,pullreq_activity_code_comment_span_new
,pullreq_activity_code_comment_line_old
,pullreq_activity_code_comment_span_old`
pullreqActivitySelectBase = `
SELECT` + pullreqActivityColumns + `
@ -136,6 +153,14 @@ func (s *PullReqActivityStore) Create(ctx context.Context, act *types.PullReqAct
,pullreq_activity_metadata
,pullreq_activity_resolved_by
,pullreq_activity_resolved
,pullreq_activity_outdated
,pullreq_activity_code_comment_merge_base_sha
,pullreq_activity_code_comment_source_sha
,pullreq_activity_code_comment_path
,pullreq_activity_code_comment_line_new
,pullreq_activity_code_comment_span_new
,pullreq_activity_code_comment_line_old
,pullreq_activity_code_comment_span_old
) values (
:pullreq_activity_version
,:pullreq_activity_created_by
@ -156,6 +181,14 @@ func (s *PullReqActivityStore) Create(ctx context.Context, act *types.PullReqAct
,:pullreq_activity_metadata
,:pullreq_activity_resolved_by
,:pullreq_activity_resolved
,:pullreq_activity_outdated
,:pullreq_activity_code_comment_merge_base_sha
,:pullreq_activity_code_comment_source_sha
,:pullreq_activity_code_comment_path
,:pullreq_activity_code_comment_line_new
,:pullreq_activity_code_comment_span_new
,:pullreq_activity_code_comment_line_old
,:pullreq_activity_code_comment_span_old
) RETURNING pullreq_activity_id`
db := dbtx.GetAccessor(ctx, s.db)
@ -217,6 +250,14 @@ func (s *PullReqActivityStore) Update(ctx context.Context, act *types.PullReqAct
,pullreq_activity_metadata = :pullreq_activity_metadata
,pullreq_activity_resolved_by = :pullreq_activity_resolved_by
,pullreq_activity_resolved = :pullreq_activity_resolved
,pullreq_activity_outdated = :pullreq_activity_outdated
,pullreq_activity_code_comment_merge_base_sha = :pullreq_activity_code_comment_merge_base_sha
,pullreq_activity_code_comment_source_sha = :pullreq_activity_code_comment_source_sha
,pullreq_activity_code_comment_path = :pullreq_activity_code_comment_path
,pullreq_activity_code_comment_line_new = :pullreq_activity_code_comment_line_new
,pullreq_activity_code_comment_span_new = :pullreq_activity_code_comment_span_new
,pullreq_activity_code_comment_line_old = :pullreq_activity_code_comment_line_old
,pullreq_activity_code_comment_span_old = :pullreq_activity_code_comment_span_old
WHERE pullreq_activity_id = :pullreq_activity_id AND pullreq_activity_version = :pullreq_activity_version - 1`
db := dbtx.GetAccessor(ctx, s.db)
@ -375,28 +416,36 @@ func (s *PullReqActivityStore) List(ctx context.Context, prID int64,
func mapPullReqActivity(act *pullReqActivity) *types.PullReqActivity {
m := &types.PullReqActivity{
ID: act.ID,
Version: act.Version,
CreatedBy: act.CreatedBy,
Created: act.Created,
Updated: act.Updated,
Edited: act.Edited,
Deleted: act.Deleted.Ptr(),
ParentID: act.ParentID.Ptr(),
RepoID: act.RepoID,
PullReqID: act.PullReqID,
Order: act.Order,
SubOrder: act.SubOrder,
ReplySeq: act.ReplySeq,
Type: act.Type,
Kind: act.Kind,
Text: act.Text,
PayloadRaw: act.Payload,
Metadata: make(map[string]interface{}),
ResolvedBy: act.ResolvedBy.Ptr(),
Resolved: act.Resolved.Ptr(),
Author: types.PrincipalInfo{},
Resolver: nil,
ID: act.ID,
Version: act.Version,
CreatedBy: act.CreatedBy,
Created: act.Created,
Updated: act.Updated,
Edited: act.Edited,
Deleted: act.Deleted.Ptr(),
ParentID: act.ParentID.Ptr(),
RepoID: act.RepoID,
PullReqID: act.PullReqID,
Order: act.Order,
SubOrder: act.SubOrder,
ReplySeq: act.ReplySeq,
Type: act.Type,
Kind: act.Kind,
Text: act.Text,
PayloadRaw: act.Payload,
Metadata: make(map[string]interface{}),
ResolvedBy: act.ResolvedBy.Ptr(),
Resolved: act.Resolved.Ptr(),
Author: types.PrincipalInfo{},
Resolver: nil,
Outdated: act.Outdated.Ptr(),
CodeCommentMergeBaseSHA: act.CodeCommentMergeBaseSHA.Ptr(),
CodeCommentSourceSHA: act.CodeCommentSourceSHA.Ptr(),
CodeCommentPath: act.CodeCommentPath.Ptr(),
CodeCommentLineNew: act.CodeCommentLineNew.Ptr(),
CodeCommentSpanNew: act.CodeCommentSpanNew.Ptr(),
CodeCommentLineOld: act.CodeCommentLineOld.Ptr(),
CodeCommentSpanOld: act.CodeCommentSpanOld.Ptr(),
}
_ = json.Unmarshal(act.Metadata, &m.Metadata)
@ -406,26 +455,34 @@ func mapPullReqActivity(act *pullReqActivity) *types.PullReqActivity {
func mapInternalPullReqActivity(act *types.PullReqActivity) *pullReqActivity {
m := &pullReqActivity{
ID: act.ID,
Version: act.Version,
CreatedBy: act.CreatedBy,
Created: act.Created,
Updated: act.Updated,
Edited: act.Edited,
Deleted: null.IntFromPtr(act.Deleted),
ParentID: null.IntFromPtr(act.ParentID),
RepoID: act.RepoID,
PullReqID: act.PullReqID,
Order: act.Order,
SubOrder: act.SubOrder,
ReplySeq: act.ReplySeq,
Type: act.Type,
Kind: act.Kind,
Text: act.Text,
Payload: act.PayloadRaw,
Metadata: nil,
ResolvedBy: null.IntFromPtr(act.ResolvedBy),
Resolved: null.IntFromPtr(act.Resolved),
ID: act.ID,
Version: act.Version,
CreatedBy: act.CreatedBy,
Created: act.Created,
Updated: act.Updated,
Edited: act.Edited,
Deleted: null.IntFromPtr(act.Deleted),
ParentID: null.IntFromPtr(act.ParentID),
RepoID: act.RepoID,
PullReqID: act.PullReqID,
Order: act.Order,
SubOrder: act.SubOrder,
ReplySeq: act.ReplySeq,
Type: act.Type,
Kind: act.Kind,
Text: act.Text,
Payload: act.PayloadRaw,
Metadata: nil,
ResolvedBy: null.IntFromPtr(act.ResolvedBy),
Resolved: null.IntFromPtr(act.Resolved),
Outdated: null.BoolFromPtr(act.Outdated),
CodeCommentMergeBaseSHA: null.StringFromPtr(act.CodeCommentMergeBaseSHA),
CodeCommentSourceSHA: null.StringFromPtr(act.CodeCommentSourceSHA),
CodeCommentPath: null.StringFromPtr(act.CodeCommentPath),
CodeCommentLineNew: null.IntFromPtr(act.CodeCommentLineNew),
CodeCommentSpanNew: null.IntFromPtr(act.CodeCommentSpanNew),
CodeCommentLineOld: null.IntFromPtr(act.CodeCommentLineOld),
CodeCommentSpanOld: null.IntFromPtr(act.CodeCommentSpanOld),
}
m.Metadata, _ = json.Marshal(act.Metadata)

View File

@ -26,6 +26,7 @@ var WireSet = wire.NewSet(
ProvideTokenStore,
ProvidePullReqStore,
ProvidePullReqActivityStore,
ProvideCodeCommentView,
ProvidePullReqReviewStore,
ProvidePullReqReviewerStore,
ProvideWebhookStore,
@ -88,6 +89,11 @@ func ProvidePullReqActivityStore(db *sqlx.DB,
return NewPullReqActivityStore(db, principalInfoCache)
}
// ProvideCodeCommentView provides a code comment view.
func ProvideCodeCommentView(db *sqlx.DB) store.CodeCommentView {
return NewCodeCommentView(db)
}
// ProvidePullReqReviewStore provides a pull request review store.
func ProvidePullReqReviewStore(db *sqlx.DB) store.PullReqReviewStore {
return NewPullReqReviewStore(db)

20
types/code_comment.go Normal file
View File

@ -0,0 +1,20 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package types
type CodeComment struct {
ID int64 `db:"pullreq_activity_id"`
Version int64 `db:"pullreq_activity_version"`
Updated int64 `db:"pullreq_activity_updated"`
Outdated bool `db:"pullreq_activity_outdated"`
MergeBaseSHA string `db:"pullreq_activity_code_comment_merge_base_sha"`
SourceSHA string `db:"pullreq_activity_code_comment_source_sha"`
Path string `db:"pullreq_activity_code_comment_path"`
LineNew int `db:"pullreq_activity_code_comment_line_new"`
SpanNew int `db:"pullreq_activity_code_comment_span_new"`
LineOld int `db:"pullreq_activity_code_comment_line_old"`
SpanOld int `db:"pullreq_activity_code_comment_span_old"`
}

View File

@ -100,15 +100,15 @@ func GetAllPullReqActivityKinds() ([]PullReqActivityKind, PullReqActivityKind) {
// PullReqActivityKind enumeration.
const (
PullReqActivityKindSystem PullReqActivityKind = "system"
PullReqActivityKindComment PullReqActivityKind = "comment"
PullReqActivityKindCodeComment PullReqActivityKind = "code"
PullReqActivityKindSystem PullReqActivityKind = "system"
PullReqActivityKindComment PullReqActivityKind = "comment"
PullReqActivityKindChangeComment PullReqActivityKind = "change-comment"
)
var pullReqActivityKinds = sortEnum([]PullReqActivityKind{
PullReqActivityKindSystem,
PullReqActivityKindComment,
PullReqActivityKindCodeComment,
PullReqActivityKindChangeComment,
})
// PullReqReviewDecision defines state of a pull request review.

View File

@ -52,6 +52,46 @@ type PullReqActivity struct {
Author PrincipalInfo `json:"author"`
Resolver *PrincipalInfo `json:"resolver"`
Outdated *bool `json:"outdated"`
CodeCommentMergeBaseSHA *string `json:"code_comment_merge_base_sha"`
CodeCommentSourceSHA *string `json:"code_comment_source_sha"`
CodeCommentPath *string `json:"code_comment_path"`
CodeCommentLineNew *int64 `json:"code_comment_line_new"`
CodeCommentSpanNew *int64 `json:"code_comment_span_new"`
CodeCommentLineOld *int64 `json:"code_comment_line_old"`
CodeCommentSpanOld *int64 `json:"code_comment_span_old"`
}
func (a *PullReqActivity) IsValidCodeComment() bool {
return a.Type == enum.PullReqActivityTypeCodeComment &&
a.Kind == enum.PullReqActivityKindChangeComment &&
a.CodeCommentMergeBaseSHA != nil &&
a.CodeCommentSourceSHA != nil &&
a.CodeCommentPath != nil &&
a.CodeCommentLineNew != nil &&
a.CodeCommentSpanNew != nil &&
a.CodeCommentLineOld != nil &&
a.CodeCommentSpanOld != nil
}
func (a *PullReqActivity) AsCodeComment() *CodeComment {
if !a.IsValidCodeComment() {
return &CodeComment{}
}
return &CodeComment{
ID: a.ID,
Version: a.Version,
Updated: a.Updated,
Outdated: *a.Outdated,
MergeBaseSHA: *a.CodeCommentMergeBaseSHA,
SourceSHA: *a.CodeCommentSourceSHA,
Path: *a.CodeCommentPath,
LineNew: int(*a.CodeCommentLineNew),
SpanNew: int(*a.CodeCommentSpanNew),
LineOld: int(*a.CodeCommentLineOld),
SpanOld: int(*a.CodeCommentSpanOld),
}
}
func (a *PullReqActivity) IsReplyable() bool {
@ -130,14 +170,16 @@ type activityPayloadFactoryMethod func() PullReqActivityPayload
// allPullReqActivityPayloads is a map that contains the payload factory methods for all activity types with payload.
var allPullReqActivityPayloads = func(
factoryMethods []activityPayloadFactoryMethod) map[enum.PullReqActivityType]activityPayloadFactoryMethod {
factoryMethods []activityPayloadFactoryMethod,
) map[enum.PullReqActivityType]activityPayloadFactoryMethod {
payloadMap := make(map[enum.PullReqActivityType]activityPayloadFactoryMethod)
for _, factoryMethod := range factoryMethods {
payloadMap[factoryMethod().ActivityType()] = factoryMethod
}
return payloadMap
}([]activityPayloadFactoryMethod{
func() PullReqActivityPayload { return &PullRequestActivityPayloadComment{} },
func() PullReqActivityPayload { return PullRequestActivityPayloadComment{} },
func() PullReqActivityPayload { return &PullRequestActivityPayloadCodeComment{} },
func() PullReqActivityPayload { return &PullRequestActivityPayloadMerge{} },
func() PullReqActivityPayload { return &PullRequestActivityPayloadStateChange{} },
func() PullReqActivityPayload { return &PullRequestActivityPayloadTitleChange{} },
@ -156,14 +198,22 @@ func newPayloadForActivity(t enum.PullReqActivityType) (PullReqActivityPayload,
return payloadFactoryMethod(), nil
}
// PullRequestActivityPayloadComment represents the payload for a comment.
// NOTE: Allow UI to store whatever needed for code comments until we have a proper solution.
type PullRequestActivityPayloadComment map[string]interface{}
type PullRequestActivityPayloadComment struct{}
func (a *PullRequestActivityPayloadComment) ActivityType() enum.PullReqActivityType {
func (a PullRequestActivityPayloadComment) ActivityType() enum.PullReqActivityType {
return enum.PullReqActivityTypeComment
}
type PullRequestActivityPayloadCodeComment struct {
Title string `json:"title"`
Lines []string `json:"lines"`
AnyNew bool `json:"any_new"`
}
func (a *PullRequestActivityPayloadCodeComment) ActivityType() enum.PullReqActivityType {
return enum.PullReqActivityTypeCodeComment
}
type PullRequestActivityPayloadMerge struct {
MergeMethod enum.MergeMethod `json:"merge_method"`
MergeSHA string `json:"merge_sha"`