diff --git a/cli/server/harness.wire.go b/cli/server/harness.wire.go index 09e6c86c8..a7432a4b6 100644 --- a/cli/server/harness.wire.go +++ b/cli/server/harness.wire.go @@ -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 } diff --git a/cli/server/harness.wire_gen.go b/cli/server/harness.wire_gen.go index 50d081463..eb9c04c48 100644 --- a/cli/server/harness.wire_gen.go +++ b/cli/server/harness.wire_gen.go @@ -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 } diff --git a/cli/server/standalone.wire.go b/cli/server/standalone.wire.go index 6343bf0c6..a964f68e1 100644 --- a/cli/server/standalone.wire.go +++ b/cli/server/standalone.wire.go @@ -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 } diff --git a/cli/server/standalone.wire_gen.go b/cli/server/standalone.wire_gen.go index bfc2c0e77..7ea5fafff 100644 --- a/cli/server/standalone.wire_gen.go +++ b/cli/server/standalone.wire_gen.go @@ -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 } diff --git a/gitrpc/diff.go b/gitrpc/diff.go index d7d19d09e..1805519d0 100644 --- a/gitrpc/diff.go +++ b/gitrpc/diff.go @@ -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 +} diff --git a/gitrpc/interface.go b/gitrpc/interface.go index e1d0b6a4b..608b31d8d 100644 --- a/gitrpc/interface.go +++ b/gitrpc/interface.go @@ -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 */ diff --git a/gitrpc/internal/gitea/diff.go b/gitrpc/internal/gitea/diff.go index 1d5e272ad..4324e4f78 100644 --- a/gitrpc/internal/gitea/diff.go +++ b/gitrpc/internal/gitea/diff.go @@ -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) +} diff --git a/gitrpc/internal/parser/diff_cut.go b/gitrpc/internal/parser/diff_cut.go new file mode 100644 index 000000000..d7dfd146f --- /dev/null +++ b/gitrpc/internal/parser/diff_cut.go @@ -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 +} diff --git a/gitrpc/internal/parser/diff_cut_test.go b/gitrpc/internal/parser/diff_cut_test.go new file mode 100644 index 000000000..cb3559474 --- /dev/null +++ b/gitrpc/internal/parser/diff_cut_test.go @@ -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) + } + }) + } +} diff --git a/gitrpc/internal/parser/diff_headers.go b/gitrpc/internal/parser/diff_headers.go new file mode 100644 index 000000000..41e29508a --- /dev/null +++ b/gitrpc/internal/parser/diff_headers.go @@ -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 +} diff --git a/gitrpc/internal/parser/diff_headers_test.go b/gitrpc/internal/parser/diff_headers_test.go new file mode 100644 index 000000000..7caf57d21 --- /dev/null +++ b/gitrpc/internal/parser/diff_headers_test.go @@ -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) + } +} diff --git a/gitrpc/internal/parser/hunk.go b/gitrpc/internal/parser/hunk.go new file mode 100644 index 000000000..62b2630a3 --- /dev/null +++ b/gitrpc/internal/parser/hunk.go @@ -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 +} diff --git a/gitrpc/internal/service/diff.go b/gitrpc/internal/service/diff.go index cfa9e4be4..a03a8039c 100644 --- a/gitrpc/internal/service/diff.go +++ b/gitrpc/internal/service/diff.go @@ -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 +} diff --git a/gitrpc/internal/service/errors.go b/gitrpc/internal/service/errors.go index b2794ef1c..ea79295a8 100644 --- a/gitrpc/internal/service/errors.go +++ b/gitrpc/internal/service/errors.go @@ -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...) diff --git a/gitrpc/internal/service/interface.go b/gitrpc/internal/service/interface.go index d2a2f7f9f..32934b78b 100644 --- a/gitrpc/internal/service/interface.go +++ b/gitrpc/internal/service/interface.go @@ -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 } diff --git a/gitrpc/internal/service/mapping.go b/gitrpc/internal/service/mapping.go index 5accfb0df..f2423337f 100644 --- a/gitrpc/internal/service/mapping.go +++ b/gitrpc/internal/service/mapping.go @@ -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 +} diff --git a/gitrpc/internal/service/repo.go b/gitrpc/internal/service/repo.go index 7006990d5..a9276b6cc 100644 --- a/gitrpc/internal/service/repo.go +++ b/gitrpc/internal/service/repo.go @@ -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 { diff --git a/gitrpc/internal/types/errors.go b/gitrpc/internal/types/errors.go index 9eceddf6c..ce12eb7c3 100644 --- a/gitrpc/internal/types/errors.go +++ b/gitrpc/internal/types/errors.go @@ -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. diff --git a/gitrpc/internal/types/hunk.go b/gitrpc/internal/types/hunk.go new file mode 100644 index 000000000..e2ff7db02 --- /dev/null +++ b/gitrpc/internal/types/hunk.go @@ -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() +} diff --git a/gitrpc/internal/types/types.go b/gitrpc/internal/types/types.go index c81914d7e..fc28d63a7 100644 --- a/gitrpc/internal/types/types.go +++ b/gitrpc/internal/types/types.go @@ -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) } diff --git a/gitrpc/mapping.go b/gitrpc/mapping.go index ce8fa99c7..331026c65 100644 --- a/gitrpc/mapping.go +++ b/gitrpc/mapping.go @@ -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, + } +} diff --git a/gitrpc/proto/diff.proto b/gitrpc/proto/diff.proto index 04da44819..5e2e7d3bb 100644 --- a/gitrpc/proto/diff.proto +++ b/gitrpc/proto/diff.proto @@ -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 { @@ -31,4 +33,52 @@ message DiffShortStatResponse { int32 files = 1; int32 additions = 2; int32 deletions = 3; -} \ No newline at end of file +} + +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; +} diff --git a/gitrpc/rpc/diff.pb.go b/gitrpc/rpc/diff.pb.go index 06e973ec2..82bb7952a 100644 --- a/gitrpc/rpc/diff.pb.go +++ b/gitrpc/rpc/diff.pb.go @@ -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, }, diff --git a/gitrpc/rpc/diff_grpc.pb.go b/gitrpc/rpc/diff_grpc.pb.go index d37c57d49..a0ed66777 100644 --- a/gitrpc/rpc/diff_grpc.pb.go +++ b/gitrpc/rpc/diff_grpc.pb.go @@ -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{ { diff --git a/internal/api/controller/pullreq/comment_create.go b/internal/api/controller/pullreq/comment_create.go index 50f3a82c3..87c04f4d9 100644 --- a/internal/api/controller/pullreq/comment_create.go +++ b/internal/api/controller/pullreq/comment_create.go @@ -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 +} diff --git a/internal/api/controller/pullreq/comment_update.go b/internal/api/controller/pullreq/comment_update.go index 01fc867cd..c033b596d 100644 --- a/internal/api/controller/pullreq/comment_update.go +++ b/internal/api/controller/pullreq/comment_update.go @@ -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 { diff --git a/internal/api/controller/pullreq/controller.go b/internal/api/controller/pullreq/controller.go index 93beef690..434a5859a 100644 --- a/internal/api/controller/pullreq/controller.go +++ b/internal/api/controller/pullreq/controller.go @@ -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, } } diff --git a/internal/api/controller/pullreq/pr_commits.go b/internal/api/controller/pullreq/pr_commits.go index 579f675c3..a4e1827f1 100644 --- a/internal/api/controller/pullreq/pr_commits.go +++ b/internal/api/controller/pullreq/pr_commits.go @@ -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 } diff --git a/internal/api/controller/pullreq/pr_diff.go b/internal/api/controller/pullreq/pr_diff.go index 627cd854a..f048833de 100644 --- a/internal/api/controller/pullreq/pr_diff.go +++ b/internal/api/controller/pullreq/pr_diff.go @@ -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 } diff --git a/internal/api/controller/pullreq/pr_state.go b/internal/api/controller/pullreq/pr_state.go index 1c1ca40af..1070bf4a1 100644 --- a/internal/api/controller/pullreq/pr_state.go +++ b/internal/api/controller/pullreq/pr_state.go @@ -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 } diff --git a/internal/api/controller/pullreq/wire.go b/internal/api/controller/pullreq/wire.go index a8b174d84..8f6c3a757 100644 --- a/internal/api/controller/pullreq/wire.go +++ b/internal/api/controller/pullreq/wire.go @@ -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) } diff --git a/internal/api/controller/repo/create.go b/internal/api/controller/repo/create.go index b294ebc2d..b363e8180 100644 --- a/internal/api/controller/repo/create.go +++ b/internal/api/controller/repo/create.go @@ -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" ) diff --git a/internal/api/controller/repo/delete.go b/internal/api/controller/repo/delete.go index 9c99b471f..67116a0c1 100644 --- a/internal/api/controller/repo/delete.go +++ b/internal/api/controller/repo/delete.go @@ -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" ) diff --git a/internal/services/codecomments/migrator.go b/internal/services/codecomments/migrator.go new file mode 100644 index 000000000..0b4904a77 --- /dev/null +++ b/internal/services/codecomments/migrator.go @@ -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 +} diff --git a/internal/services/codecomments/wire.go b/internal/services/codecomments/wire.go new file mode 100644 index 000000000..959122aca --- /dev/null +++ b/internal/services/codecomments/wire.go @@ -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, + } +} diff --git a/internal/services/pullreq/handlers_branch.go b/internal/services/pullreq/handlers_branch.go index ce440ad0d..545c0baed 100644 --- a/internal/services/pullreq/handlers_branch.go +++ b/internal/services/pullreq/handlers_branch.go @@ -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 diff --git a/internal/services/pullreq/handlers_code_comments.go b/internal/services/pullreq/handlers_code_comments.go new file mode 100644 index 000000000..7fee56fbc --- /dev/null +++ b/internal/services/pullreq/handlers_code_comments.go @@ -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 +} diff --git a/internal/services/pullreq/handlers_mergeable.go b/internal/services/pullreq/handlers_mergeable.go index 10e1d9976..17e8d491a 100644 --- a/internal/services/pullreq/handlers_mergeable.go +++ b/internal/services/pullreq/handlers_mergeable.go @@ -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 } diff --git a/internal/services/pullreq/pullreq.go b/internal/services/pullreq/pullreq.go index 46ee48c77..978c4886e 100644 --- a/internal/services/pullreq/pullreq.go +++ b/internal/services/pullreq/pullreq.go @@ -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 } diff --git a/internal/services/pullreq/wire.go b/internal/services/pullreq/wire.go index 6a679b392..529b08d3a 100644 --- a/internal/services/pullreq/wire.go +++ b/internal/services/pullreq/wire.go @@ -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) } diff --git a/internal/services/services.go b/internal/services/services.go index ed15eab2f..70985dae3 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -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, diff --git a/internal/store/database.go b/internal/store/database.go index debff24f2..145fb7445 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -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. diff --git a/internal/store/database/code_comment.go b/internal/store/database/code_comment.go new file mode 100644 index 000000000..1a2fde9dc --- /dev/null +++ b/internal/store/database/code_comment.go @@ -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 +} diff --git a/internal/store/database/migrate/postgres/0013_create_table_pullreq_reviewers.down.sql b/internal/store/database/migrate/postgres/0013_create_table_pullreq_reviewers.down.sql new file mode 100644 index 000000000..8ef30bf98 --- /dev/null +++ b/internal/store/database/migrate/postgres/0013_create_table_pullreq_reviewers.down.sql @@ -0,0 +1,2 @@ +-- Can't migrate down from this point. +-- This file must be present here. \ No newline at end of file diff --git a/internal/store/database/migrate/postgres/0014_alter_pullreq_activity_code_comments.down.sql b/internal/store/database/migrate/postgres/0014_alter_pullreq_activity_code_comments.down.sql new file mode 100644 index 000000000..e816c57a9 --- /dev/null +++ b/internal/store/database/migrate/postgres/0014_alter_pullreq_activity_code_comments.down.sql @@ -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; diff --git a/internal/store/database/migrate/postgres/0014_alter_pullreq_activity_code_comments.up.sql b/internal/store/database/migrate/postgres/0014_alter_pullreq_activity_code_comments.up.sql new file mode 100644 index 000000000..5f16dfad8 --- /dev/null +++ b/internal/store/database/migrate/postgres/0014_alter_pullreq_activity_code_comments.up.sql @@ -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; \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0013_create_table_pullreq_reviewers.down.sql b/internal/store/database/migrate/sqlite/0013_create_table_pullreq_reviewers.down.sql new file mode 100644 index 000000000..8ef30bf98 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0013_create_table_pullreq_reviewers.down.sql @@ -0,0 +1,2 @@ +-- Can't migrate down from this point. +-- This file must be present here. \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0014_alter_pullreq_activity_code_comments.down.sql b/internal/store/database/migrate/sqlite/0014_alter_pullreq_activity_code_comments.down.sql new file mode 100644 index 000000000..87ee28469 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0014_alter_pullreq_activity_code_comments.down.sql @@ -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; diff --git a/internal/store/database/migrate/sqlite/0014_alter_pullreq_activity_code_comments.up.sql b/internal/store/database/migrate/sqlite/0014_alter_pullreq_activity_code_comments.up.sql new file mode 100644 index 000000000..3bddb0ce8 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0014_alter_pullreq_activity_code_comments.up.sql @@ -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; \ No newline at end of file diff --git a/internal/store/database/pullreq_activity.go b/internal/store/database/pullreq_activity.go index 565db5b96..ad5273345 100644 --- a/internal/store/database/pullreq_activity.go +++ b/internal/store/database/pullreq_activity.go @@ -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) diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index 93ab08421..fc3a26436 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -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) diff --git a/types/code_comment.go b/types/code_comment.go new file mode 100644 index 000000000..decd72a12 --- /dev/null +++ b/types/code_comment.go @@ -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"` +} diff --git a/types/enum/pullreq.go b/types/enum/pullreq.go index 030c5eea9..c8cf68348 100644 --- a/types/enum/pullreq.go +++ b/types/enum/pullreq.go @@ -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. diff --git a/types/pullreq_activity.go b/types/pullreq_activity.go index b8e3b553f..8d291293a 100644 --- a/types/pullreq_activity.go +++ b/types/pullreq_activity.go @@ -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"`