[feat] initial work on merging PR (#170)

* initial work on merging PR

* code refactored based on requested changes

* requested changes
This commit is contained in:
Enver Bisevac 2023-01-10 02:09:43 +01:00 committed by GitHub
parent 1a84e19ce4
commit 63de576d08
43 changed files with 2649 additions and 38 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.DS_Store
NOTES*
__debug_bin
_research
.env
*.sqlite

View File

@ -7,6 +7,7 @@ package server
import (
"context"
"github.com/harness/gitness/events"
"github.com/harness/gitness/gitrpc"
server2 "github.com/harness/gitness/gitrpc/server"
@ -106,7 +107,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
pullReqActivityStore := database.ProvidePullReqActivityStore(db)
pullReqReviewStore := database.ProvidePullReqReviewStore(db)
pullReqReviewerStore := database.ProvidePullReqReviewerStore(db)
pullreqController := pullreq.ProvideController(db, authorizer, pullReqStore, pullReqActivityStore, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface)
pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface)
webhookStore := database.ProvideWebhookStore(db)
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
webhookConfig := ProvideWebhookConfig(config)

View File

@ -7,6 +7,7 @@ package server
import (
"context"
"github.com/harness/gitness/events"
"github.com/harness/gitness/gitrpc"
server2 "github.com/harness/gitness/gitrpc/server"
@ -67,7 +68,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
pullReqActivityStore := database.ProvidePullReqActivityStore(db)
pullReqReviewStore := database.ProvidePullReqReviewStore(db)
pullReqReviewerStore := database.ProvidePullReqReviewerStore(db)
pullreqController := pullreq.ProvideController(db, authorizer, pullReqStore, pullReqActivityStore, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface)
pullreqController := pullreq.ProvideController(db, provider, authorizer, pullReqStore, pullReqActivityStore, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, gitrpcInterface)
webhookStore := database.ProvideWebhookStore(db)
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
webhookConfig := ProvideWebhookConfig(config)

View File

@ -18,6 +18,7 @@ type Client struct {
httpService rpc.SmartHTTPServiceClient
commitFilesService rpc.CommitFilesServiceClient
diffService rpc.DiffServiceClient
mergeService rpc.MergeServiceClient
}
func New(remoteAddr string) (*Client, error) {
@ -47,5 +48,6 @@ func New(remoteAddr string) (*Client, error) {
httpService: rpc.NewSmartHTTPServiceClient(conn),
commitFilesService: rpc.NewCommitFilesServiceClient(conn),
diffService: rpc.NewDiffServiceClient(conn),
mergeService: rpc.NewMergeServiceClient(conn),
}, nil
}

View File

@ -38,4 +38,9 @@ type Interface interface {
* Diff services
*/
RawDiff(ctx context.Context, in *RawDiffParams, w io.Writer) error
/*
* Merge services
*/
MergeBranch(ctx context.Context, in *MergeBranchParams) (string, error)
}

View File

@ -0,0 +1,260 @@
// 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 gitea
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/harness/gitness/gitrpc/internal/tempdir"
"github.com/harness/gitness/gitrpc/internal/types"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
)
// CreateTemporaryRepo creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch
// it also create a second base branch called "original_base".
//
//nolint:funlen,gocognit // need refactor
func (g Adapter) CreateTemporaryRepoForPR(
ctx context.Context,
reposTempPath string,
pr *types.PullRequest,
) (string, error) {
if pr.BaseRepoPath == "" && pr.HeadRepoPath != "" {
pr.BaseRepoPath = pr.HeadRepoPath
}
if pr.HeadRepoPath == "" && pr.BaseRepoPath != "" {
pr.HeadRepoPath = pr.BaseRepoPath
}
if pr.BaseBranch == "" {
return "", errors.New("empty base branch")
}
if pr.HeadBranch == "" {
return "", errors.New("empty head branch")
}
baseRepoPath := pr.BaseRepoPath
headRepoPath := pr.HeadRepoPath
// Clone base repo.
tmpBasePath, err := tempdir.CreateTemporaryPath(reposTempPath, "pull")
if err != nil {
return "", err
}
if err = g.InitRepository(ctx, tmpBasePath, false); err != nil {
// log.Error("git init tmpBasePath: %v", err)
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
return "", err
}
remoteRepoName := "head_repo"
baseBranch := "base"
// Add head repo remote.
addCacheRepo := func(staging, cache string) error {
var f *os.File
alternates := filepath.Join(staging, ".git", "objects", "info", "alternates")
f, err = os.OpenFile(alternates, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return err
}
defer f.Close()
data := filepath.Join(cache, "objects")
if _, err = fmt.Fprintln(f, data); err != nil {
return err
}
return nil
}
if err = addCacheRepo(tmpBasePath, baseRepoPath); err != nil {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
return "", fmt.Errorf("unable to add base repository to temporary repo [%s -> tmpBasePath]: %w", pr.BaseRepoPath, err)
}
var outbuf, errbuf strings.Builder
if err = git.NewCommand(ctx, "remote", "add", "-t", pr.BaseBranch, "-m", pr.BaseBranch, "origin", baseRepoPath).
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
return "", fmt.Errorf("unable to add base repository as origin "+
"[%s -> tmpBasePath]: %w\n%s\n%s", pr.BaseRepoPath, err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
if err = git.NewCommand(ctx, "fetch", "origin", "--no-tags", "--",
pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch).
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
return "", fmt.Errorf("unable to fetch origin base branch "+
"[%s:%s -> base, original_base in tmpBasePath]: %w\n%s\n%s",
pr.BaseRepoPath, pr.BaseBranch, err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
if err = git.NewCommand(ctx, "symbolic-ref", "HEAD", git.BranchPrefix+baseBranch).
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
return "", fmt.Errorf("unable to set HEAD as base "+
"branch [tmpBasePath]: %w\n%s\n%s", err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
if err = addCacheRepo(tmpBasePath, headRepoPath); err != nil {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
return "", fmt.Errorf("unable to head base repository "+
"to temporary repo [%s -> tmpBasePath]: %w", pr.HeadRepoPath, err)
}
if err = git.NewCommand(ctx, "remote", "add", remoteRepoName, headRepoPath).
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
return "", fmt.Errorf("unable to add head repository as head_repo "+
"[%s -> tmpBasePath]: %w\n%s\n%s", pr.HeadRepoPath, err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
trackingBranch := "tracking"
headBranch := git.BranchPrefix + pr.HeadBranch
if err = git.NewCommand(ctx, "fetch", "--no-tags", remoteRepoName, headBranch+":"+trackingBranch).
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
if !git.IsBranchExist(ctx, pr.HeadRepoPath, headBranch) {
return "", models.ErrBranchDoesNotExist{
BranchName: headBranch,
}
}
return "", fmt.Errorf("unable to fetch head_repo head branch "+
"[%s:%s -> tracking in tmpBasePath]: %w\n%s\n%s",
pr.HeadRepoPath, headBranch, err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
return tmpBasePath, nil
}
func (g Adapter) Merge(
ctx context.Context,
pr *types.PullRequest,
mergeMethod string,
trackingBranch string,
tmpBasePath string,
env []string,
) error {
var outbuf, errbuf strings.Builder
cmd := git.NewCommand(ctx, "merge", "--no-ff", trackingBranch)
if err := cmd.Run(&git.RunOpts{
Env: env,
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
// Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil {
// We have a merge conflict error
return types.MergeConflictsError{
Method: mergeMethod,
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
} else if strings.Contains(errbuf.String(), "refusing to merge unrelated histories") {
return types.MergeUnrelatedHistoriesError{
Method: mergeMethod,
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
}
return fmt.Errorf("git merge [%s -> %s]: %w\n%s\n%s",
pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String())
}
return nil
}
func (g Adapter) GetDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) (string, error) {
getDiffTreeFromBranch := func(repoPath, baseBranch, headBranch string) (string, error) {
var outbuf, errbuf strings.Builder
// Compute the diff-tree for sparse-checkout
if err := git.NewCommand(ctx, "diff-tree", "--no-commit-id",
"--name-only", "-r", "-z", "--root", baseBranch, headBranch, "--").
Run(&git.RunOpts{
Dir: repoPath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
return "", fmt.Errorf("git diff-tree [%s base:%s head:%s]: %s", repoPath, baseBranch, headBranch, errbuf.String())
}
return outbuf.String(), nil
}
scanNullTerminatedStrings := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\x00'); i >= 0 {
return i + 1, data[0:i], nil
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
list, err := getDiffTreeFromBranch(repoPath, baseBranch, headBranch)
if err != nil {
return "", err
}
// Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
out := bytes.Buffer{}
scanner := bufio.NewScanner(strings.NewReader(list))
scanner.Split(scanNullTerminatedStrings)
for scanner.Scan() {
filepath := scanner.Text()
// escape '*', '?', '[', spaces and '!' prefix
filepath = escapedSymbols.ReplaceAllString(filepath, `\$1`)
// no necessary to escape the first '#' symbol because the first symbol is '/'
fmt.Fprintf(&out, "/%s\n", filepath)
}
return out.String(), nil
}

View File

@ -0,0 +1,11 @@
// 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 gitea
import "regexp"
var (
escapedSymbols = regexp.MustCompile(`([*[?! \\])`)
)

View File

@ -38,4 +38,8 @@ type GitAdapter interface {
GetCommitDivergences(ctx context.Context, repoPath string,
requests []types.CommitDivergenceRequest, max int32) ([]types.CommitDivergence, error)
GetRef(ctx context.Context, repoPath string, name string, refType types.RefType) (string, error)
CreateTemporaryRepoForPR(ctx context.Context, reposTempPath string, pr *types.PullRequest) (string, error)
Merge(ctx context.Context, pr *types.PullRequest, mergeMethod string, trackingBranch string,
tmpBasePath string, env []string) error
GetDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) (string, error)
}

View File

@ -0,0 +1,247 @@
// 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 service
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/harness/gitness/gitrpc/internal/tempdir"
"github.com/harness/gitness/gitrpc/internal/types"
"github.com/harness/gitness/gitrpc/rpc"
"code.gitea.io/gitea/modules/git"
)
type MergeService struct {
rpc.UnimplementedMergeServiceServer
adapter GitAdapter
reposRoot string
reposTempDir string
}
var _ rpc.MergeServiceServer = (*MergeService)(nil)
func NewMergeService(adapter GitAdapter, reposRoot, reposTempDir string) (*MergeService, error) {
return &MergeService{
adapter: adapter,
reposRoot: reposRoot,
reposTempDir: reposTempDir,
}, nil
}
//nolint:funlen // needs refactor when all merge methods are implemented
func (s MergeService) MergeBranch(
ctx context.Context,
request *rpc.MergeBranchRequest,
) (*rpc.MergeBranchResponse, error) {
if err := validateMergeBranchRequest(request); err != nil {
return nil, err
}
repoPath := getFullPathForRepo(s.reposRoot, request.GetBase().GetRepoUid())
pr := &types.PullRequest{
BaseRepoPath: repoPath,
BaseBranch: request.GetBranch(),
HeadBranch: request.GetHeadBranch(),
}
// Clone base repo.
tmpBasePath, err := s.adapter.CreateTemporaryRepoForPR(ctx, s.reposTempDir, pr)
if err != nil {
return nil, err
}
defer func() {
_ = tempdir.RemoveTemporaryPath(tmpBasePath)
}()
var outbuf, errbuf strings.Builder
baseBranch := "base"
trackingBranch := "tracking"
// Enable sparse-checkout
sparseCheckoutList, err := s.adapter.GetDiffTree(ctx, tmpBasePath, baseBranch, trackingBranch)
if err != nil {
return nil, fmt.Errorf("getDiffTree: %w", err)
}
infoPath := filepath.Join(tmpBasePath, ".git", "info")
if err = os.MkdirAll(infoPath, 0o700); err != nil {
return nil, fmt.Errorf("unable to create .git/info in tmpBasePath: %w", err)
}
sparseCheckoutListPath := filepath.Join(infoPath, "sparse-checkout")
if err = os.WriteFile(sparseCheckoutListPath, []byte(sparseCheckoutList), 0o600); err != nil {
return nil, fmt.Errorf("unable to write .git/info/sparse-checkout file in tmpBasePath: %w", err)
}
gitConfigCommand := func() *git.Command {
return git.NewCommand(ctx, "config", "--local")
}
// Switch off LFS process (set required, clean and smudge here also)
if err = gitConfigCommand().AddArguments("filter.lfs.process", "").
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
return nil, fmt.Errorf("git config [filter.lfs.process -> <> ]: %w\n%s\n%s",
err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
if err = gitConfigCommand().AddArguments("filter.lfs.required", "false").
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
return nil, fmt.Errorf("git config [filter.lfs.required -> <false> ]: %w\n%s\n%s",
err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
if err = gitConfigCommand().AddArguments("filter.lfs.clean", "").
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
return nil, fmt.Errorf("git config [filter.lfs.clean -> <> ]: %w\n%s\n%s",
err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
if err = gitConfigCommand().AddArguments("filter.lfs.smudge", "").
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
return nil, fmt.Errorf("git config [filter.lfs.smudge -> <> ]: %w\n%s\n%s", err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
if err = gitConfigCommand().AddArguments("core.sparseCheckout", "true").
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
return nil, fmt.Errorf("git config [core.sparsecheckout -> true]: %w\n%s\n%s", err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
// Read base branch index
if err = git.NewCommand(ctx, "read-tree", "HEAD").
Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
return nil, fmt.Errorf("unable to read base branch in to the index: %w\n%s\n%s",
err, outbuf.String(), errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
sig := &git.Signature{
Name: request.GetBase().GetActor().GetName(),
Email: request.GetBase().GetActor().GetEmail(),
}
committer := sig
commitTimeStr := time.Now().Format(time.RFC3339)
// Because this may call hooks we should pass in the environment
env := append(CreateEnvironmentForPush(ctx, request.GetBase()),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+committer.Name,
"GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
if err = s.adapter.Merge(ctx, pr, "merge", trackingBranch, tmpBasePath, env); err != nil {
return nil, err
}
mergeCommitID, err := git.GetFullCommitID(ctx, tmpBasePath, baseBranch)
if err != nil {
return nil, fmt.Errorf("failed to get full commit id for the new merge: %w", err)
}
pushCmd := git.NewCommand(ctx, "push", "origin", baseBranch+":"+git.BranchPrefix+pr.BaseBranch)
if err = pushCmd.Run(&git.RunOpts{
Env: env,
Dir: tmpBasePath,
Stdout: &outbuf,
Stderr: &errbuf,
}); err != nil {
if strings.Contains(errbuf.String(), "non-fast-forward") {
return nil, &git.ErrPushOutOfDate{
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
} else if strings.Contains(errbuf.String(), "! [remote rejected]") {
err := &git.ErrPushRejected{
StdOut: outbuf.String(),
StdErr: errbuf.String(),
Err: err,
}
err.GenerateMessage()
return nil, err
}
return nil, fmt.Errorf("git push: %s", errbuf.String())
}
outbuf.Reset()
errbuf.Reset()
return &rpc.MergeBranchResponse{
CommitId: mergeCommitID,
}, nil
}
func validateMergeBranchRequest(request *rpc.MergeBranchRequest) error {
base := request.GetBase()
if base == nil {
return types.ErrBaseCannotBeEmpty
}
author := base.GetActor()
if author == nil {
return fmt.Errorf("empty user")
}
if len(author.Email) == 0 {
return fmt.Errorf("empty user email")
}
if len(author.Name) == 0 {
return fmt.Errorf("empty user name")
}
if len(request.Branch) == 0 {
return fmt.Errorf("empty branch name")
}
if request.HeadBranch == "" {
return fmt.Errorf("empty head branch name")
}
return nil
}

View File

@ -4,7 +4,10 @@
package types
import "errors"
import (
"errors"
"fmt"
)
var (
ErrAlreadyExists = errors.New("already exists")
@ -20,3 +23,27 @@ var (
ErrEmptyLeftCommitID = errors.New("empty LeftCommitId")
ErrEmptyRightCommitID = errors.New("empty RightCommitId")
)
// MergeConflictsError represents an error if merging fails with a conflict.
type MergeConflictsError struct {
Method string
StdOut string
StdErr string
Err error
}
func (err MergeConflictsError) Error() string {
return fmt.Sprintf("Merge Conflict Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}
// MergeUnrelatedHistoriesError represents an error if merging fails due to unrelated histories.
type MergeUnrelatedHistoriesError struct {
Method string
StdOut string
StdErr string
Err error
}
func (err MergeUnrelatedHistoriesError) Error() string {
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
}

View File

@ -245,3 +245,11 @@ type CommitDivergence struct {
// Behind is the count of commits the 'From' ref is behind the 'To' ref.
Behind int32
}
type PullRequest struct {
BaseRepoPath string
HeadRepoPath string
BaseBranch string
HeadBranch string
}

38
gitrpc/merge.go Normal file
View File

@ -0,0 +1,38 @@
// 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 gitrpc
import (
"context"
"github.com/harness/gitness/gitrpc/rpc"
)
type MergeBranchParams struct {
WriteParams
BaseBranch string
HeadRepoUID string
HeadBranch string
Force bool
DeleteBranch bool
}
func (c *Client) MergeBranch(ctx context.Context, params *MergeBranchParams) (string, error) {
if params == nil {
return "", ErrNoParamsProvided
}
resp, err := c.mergeService.MergeBranch(ctx, &rpc.MergeBranchRequest{
Base: mapToRPCWriteRequest(params.WriteParams),
Branch: params.BaseBranch,
HeadBranch: params.HeadBranch,
Force: params.Force,
Delete: params.DeleteBranch,
})
if err != nil {
return "", err
}
return resp.CommitId, nil
}

36
gitrpc/proto/merge.proto Normal file
View File

@ -0,0 +1,36 @@
syntax = "proto3";
package rpc;
option go_package = "github.com/harness/gitness/gitrpc/rpc";
import "shared.proto";
// DiffService is a service which provides RPCs to inspect differences
// introduced between a set of commits.
service MergeService {
rpc MergeBranch(MergeBranchRequest) returns (MergeBranchResponse) {}
}
message MergeBranchRequest {
WriteRequest base = 1;
// head_branch is the source branch we want to merge
string head_branch = 2;
// branch is the branch into which the given commit shall be merged and whose
// reference is going to be updated.
string branch = 3;
// title is the title to use for the merge commit.
string title = 4;
// message is the message to use for the merge commit.
string message = 5;
// force merge
bool force = 6;
// delete branch after merge
bool delete = 7;
}
// This comment is left unintentionally blank.
message MergeBranchResponse {
// The merge commit the branch will be updated to. The caller can still abort the merge.
string commit_id = 1;
}

View File

@ -67,4 +67,4 @@ message CommitFilesRequest {
message CommitFilesResponse {
string commit_id = 1;
string branch = 2;
}
}

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.11
// protoc v3.21.9
// source: diff.proto
package rpc

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.11
// - protoc v3.21.9
// source: diff.proto
package rpc

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.11
// protoc v3.21.9
// source: http.proto
package rpc
@ -151,6 +151,7 @@ type ServicePackRequest struct {
// Depending on the service the matching base type has to be passed
//
// Types that are assignable to Base:
//
// *ServicePackRequest_ReadBase
// *ServicePackRequest_WriteBase
Base isServicePackRequest_Base `protobuf_oneof:"base"`

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.11
// - protoc v3.21.9
// source: http.proto
package rpc

285
gitrpc/rpc/merge.pb.go Normal file
View File

@ -0,0 +1,285 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.9
// source: merge.proto
package rpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type MergeBranchRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Base *WriteRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
// head_branch is the source branch we want to merge
HeadBranch string `protobuf:"bytes,2,opt,name=head_branch,json=headBranch,proto3" json:"head_branch,omitempty"`
// branch is the branch into which the given commit shall be merged and whose
// reference is going to be updated.
Branch string `protobuf:"bytes,3,opt,name=branch,proto3" json:"branch,omitempty"`
// title is the title to use for the merge commit.
Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"`
// message is the message to use for the merge commit.
Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"`
// force merge
Force bool `protobuf:"varint,6,opt,name=force,proto3" json:"force,omitempty"`
// delete branch after merge
Delete bool `protobuf:"varint,7,opt,name=delete,proto3" json:"delete,omitempty"`
}
func (x *MergeBranchRequest) Reset() {
*x = MergeBranchRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_merge_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MergeBranchRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MergeBranchRequest) ProtoMessage() {}
func (x *MergeBranchRequest) ProtoReflect() protoreflect.Message {
mi := &file_merge_proto_msgTypes[0]
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 MergeBranchRequest.ProtoReflect.Descriptor instead.
func (*MergeBranchRequest) Descriptor() ([]byte, []int) {
return file_merge_proto_rawDescGZIP(), []int{0}
}
func (x *MergeBranchRequest) GetBase() *WriteRequest {
if x != nil {
return x.Base
}
return nil
}
func (x *MergeBranchRequest) GetHeadBranch() string {
if x != nil {
return x.HeadBranch
}
return ""
}
func (x *MergeBranchRequest) GetBranch() string {
if x != nil {
return x.Branch
}
return ""
}
func (x *MergeBranchRequest) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *MergeBranchRequest) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *MergeBranchRequest) GetForce() bool {
if x != nil {
return x.Force
}
return false
}
func (x *MergeBranchRequest) GetDelete() bool {
if x != nil {
return x.Delete
}
return false
}
// This comment is left unintentionally blank.
type MergeBranchResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The merge commit the branch will be updated to. The caller can still abort the merge.
CommitId string `protobuf:"bytes,1,opt,name=commit_id,json=commitId,proto3" json:"commit_id,omitempty"`
}
func (x *MergeBranchResponse) Reset() {
*x = MergeBranchResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_merge_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MergeBranchResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MergeBranchResponse) ProtoMessage() {}
func (x *MergeBranchResponse) ProtoReflect() protoreflect.Message {
mi := &file_merge_proto_msgTypes[1]
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 MergeBranchResponse.ProtoReflect.Descriptor instead.
func (*MergeBranchResponse) Descriptor() ([]byte, []int) {
return file_merge_proto_rawDescGZIP(), []int{1}
}
func (x *MergeBranchResponse) GetCommitId() string {
if x != nil {
return x.CommitId
}
return ""
}
var File_merge_proto protoreflect.FileDescriptor
var file_merge_proto_rawDesc = []byte{
0x0a, 0x0b, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x72,
0x70, 0x63, 0x1a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x22, 0xd2, 0x01, 0x0a, 0x12, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1f,
0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x65, 0x61, 0x64, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12,
0x16, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65,
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x32, 0x0a, 0x13, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x72,
0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09,
0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x64, 0x32, 0x52, 0x0a, 0x0c, 0x4d, 0x65, 0x72,
0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x42, 0x0a, 0x0b, 0x4d, 0x65, 0x72,
0x67, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x17, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4d,
0x65, 0x72, 0x67, 0x65, 0x42, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x72, 0x61,
0x6e, 0x63, 0x68, 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 (
file_merge_proto_rawDescOnce sync.Once
file_merge_proto_rawDescData = file_merge_proto_rawDesc
)
func file_merge_proto_rawDescGZIP() []byte {
file_merge_proto_rawDescOnce.Do(func() {
file_merge_proto_rawDescData = protoimpl.X.CompressGZIP(file_merge_proto_rawDescData)
})
return file_merge_proto_rawDescData
}
var file_merge_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_merge_proto_goTypes = []interface{}{
(*MergeBranchRequest)(nil), // 0: rpc.MergeBranchRequest
(*MergeBranchResponse)(nil), // 1: rpc.MergeBranchResponse
(*WriteRequest)(nil), // 2: rpc.WriteRequest
}
var file_merge_proto_depIdxs = []int32{
2, // 0: rpc.MergeBranchRequest.base:type_name -> rpc.WriteRequest
0, // 1: rpc.MergeService.MergeBranch:input_type -> rpc.MergeBranchRequest
1, // 2: rpc.MergeService.MergeBranch:output_type -> rpc.MergeBranchResponse
2, // [2:3] is the sub-list for method output_type
1, // [1:2] 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
}
func init() { file_merge_proto_init() }
func file_merge_proto_init() {
if File_merge_proto != nil {
return
}
file_shared_proto_init()
if !protoimpl.UnsafeEnabled {
file_merge_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MergeBranchRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_merge_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MergeBranchResponse); 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{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_merge_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_merge_proto_goTypes,
DependencyIndexes: file_merge_proto_depIdxs,
MessageInfos: file_merge_proto_msgTypes,
}.Build()
File_merge_proto = out.File
file_merge_proto_rawDesc = nil
file_merge_proto_goTypes = nil
file_merge_proto_depIdxs = nil
}

105
gitrpc/rpc/merge_grpc.pb.go Normal file
View File

@ -0,0 +1,105 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.9
// source: merge.proto
package rpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// MergeServiceClient is the client API for MergeService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MergeServiceClient interface {
MergeBranch(ctx context.Context, in *MergeBranchRequest, opts ...grpc.CallOption) (*MergeBranchResponse, error)
}
type mergeServiceClient struct {
cc grpc.ClientConnInterface
}
func NewMergeServiceClient(cc grpc.ClientConnInterface) MergeServiceClient {
return &mergeServiceClient{cc}
}
func (c *mergeServiceClient) MergeBranch(ctx context.Context, in *MergeBranchRequest, opts ...grpc.CallOption) (*MergeBranchResponse, error) {
out := new(MergeBranchResponse)
err := c.cc.Invoke(ctx, "/rpc.MergeService/MergeBranch", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// MergeServiceServer is the server API for MergeService service.
// All implementations must embed UnimplementedMergeServiceServer
// for forward compatibility
type MergeServiceServer interface {
MergeBranch(context.Context, *MergeBranchRequest) (*MergeBranchResponse, error)
mustEmbedUnimplementedMergeServiceServer()
}
// UnimplementedMergeServiceServer must be embedded to have forward compatible implementations.
type UnimplementedMergeServiceServer struct {
}
func (UnimplementedMergeServiceServer) MergeBranch(context.Context, *MergeBranchRequest) (*MergeBranchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MergeBranch not implemented")
}
func (UnimplementedMergeServiceServer) mustEmbedUnimplementedMergeServiceServer() {}
// UnsafeMergeServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MergeServiceServer will
// result in compilation errors.
type UnsafeMergeServiceServer interface {
mustEmbedUnimplementedMergeServiceServer()
}
func RegisterMergeServiceServer(s grpc.ServiceRegistrar, srv MergeServiceServer) {
s.RegisterService(&MergeService_ServiceDesc, srv)
}
func _MergeService_MergeBranch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MergeBranchRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MergeServiceServer).MergeBranch(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/rpc.MergeService/MergeBranch",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MergeServiceServer).MergeBranch(ctx, req.(*MergeBranchRequest))
}
return interceptor(ctx, in, info, handler)
}
// MergeService_ServiceDesc is the grpc.ServiceDesc for MergeService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MergeService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "rpc.MergeService",
HandlerType: (*MergeServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "MergeBranch",
Handler: _MergeService_MergeBranch_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "merge.proto",
}

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.11
// protoc v3.21.9
// source: operations.proto
package rpc
@ -239,6 +239,7 @@ type CommitFilesAction struct {
unknownFields protoimpl.UnknownFields
// Types that are assignable to Payload:
//
// *CommitFilesAction_Header
// *CommitFilesAction_Content
Payload isCommitFilesAction_Payload `protobuf_oneof:"payload"`
@ -322,6 +323,7 @@ type CommitFilesRequest struct {
unknownFields protoimpl.UnknownFields
// Types that are assignable to Payload:
//
// *CommitFilesRequest_Header
// *CommitFilesRequest_Action
Payload isCommitFilesRequest_Payload `protobuf_oneof:"payload"`

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.11
// - protoc v3.21.9
// source: operations.proto
package rpc

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.11
// protoc v3.21.9
// source: ref.proto
package rpc

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.11
// - protoc v3.21.9
// source: ref.proto
package rpc

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.11
// protoc v3.21.9
// source: repo.proto
package rpc
@ -130,6 +130,7 @@ type CreateRepositoryRequest struct {
unknownFields protoimpl.UnknownFields
// Types that are assignable to Data:
//
// *CreateRepositoryRequest_Header
// *CreateRepositoryRequest_File
Data isCreateRepositoryRequest_Data `protobuf_oneof:"data"`

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.11
// - protoc v3.21.9
// source: repo.proto
package rpc

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.11
// protoc v3.21.9
// source: shared.proto
package rpc
@ -240,6 +240,7 @@ type FileUpload struct {
unknownFields protoimpl.UnknownFields
// Types that are assignable to Data:
//
// *FileUpload_Header
// *FileUpload_Chunk
Data isFileUpload_Data `protobuf_oneof:"data"`

View File

@ -86,6 +86,10 @@ func NewServer(config Config) (*Server, error) {
if err != nil {
return nil, err
}
mergeService, err := service.NewMergeService(adapter, reposRoot, config.TmpDir)
if err != nil {
return nil, err
}
// register services
rpc.RegisterRepositoryServiceServer(s, repoService)
@ -93,6 +97,7 @@ func NewServer(config Config) (*Server, error) {
rpc.RegisterSmartHTTPServiceServer(s, httpService)
rpc.RegisterCommitFilesServiceServer(s, commitFilesService)
rpc.RegisterDiffServiceServer(s, diffService)
rpc.RegisterMergeServiceServer(s, mergeService)
return &Server{
Server: s,

106
go.mod
View File

@ -31,13 +31,15 @@ require (
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-sqlite3 v1.14.12
github.com/pkg/errors v0.9.1
github.com/rs/xid v1.4.0
github.com/rs/zerolog v1.26.0
github.com/stretchr/testify v1.8.1
github.com/swaggest/openapi-go v0.2.23
github.com/swaggest/swgui v1.4.2
github.com/unrolled/secure v1.0.8
go.uber.org/multierr v1.8.0
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
golang.org/x/crypto v0.1.0
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a
golang.org/x/sync v0.1.0
golang.org/x/term v0.2.0
google.golang.org/grpc v1.43.0
@ -46,58 +48,152 @@ require (
)
require (
cloud.google.com/go v0.99.0 // indirect
gitea.com/go-chi/cache v0.2.0 // indirect
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cloudflare/cfssl v1.6.1 // indirect
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/djherbis/buffer v1.2.0 // indirect
github.com/djherbis/nio/v3 v3.0.1 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/duo-labs/webauthn v0.0.0-20220330035159-03696f3d4499 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane v0.10.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/fullstorydev/grpcurl v1.8.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-enry/go-enry/v2 v2.8.2 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/certificate-transparency-go v1.1.2-0.20210511102531-373a877eec92 // indirect
github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3 // indirect
github.com/google/subcommands v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-version v1.4.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/jackc/pgx/v4 v4.12.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jhump/protoreflect v1.8.2 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 // indirect
github.com/klauspost/compress v1.15.3 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.19 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.26 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/oliamb/cutter v0.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/pquerna/otp v1.3.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/cobra v1.3.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/swaggest/jsonschema-go v0.3.40 // indirect
github.com/swaggest/refl v1.1.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/urfave/cli v1.22.9 // indirect
github.com/vearutop/statigz v1.1.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
github.com/yohcop/openid-go v1.0.0 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.5.1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
go.etcd.io/etcd/client/v2 v2.305.1 // indirect
go.etcd.io/etcd/client/v3 v3.5.0-alpha.0 // indirect
go.etcd.io/etcd/etcdctl/v3 v3.5.0-alpha.0 // indirect
go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 // indirect
go.etcd.io/etcd/raft/v3 v3.5.0-alpha.0 // indirect
go.etcd.io/etcd/server/v3 v3.5.0-alpha.0 // indirect
go.etcd.io/etcd/tests/v3 v3.5.0-alpha.0 // indirect
go.etcd.io/etcd/v3 v3.5.0-alpha.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mvdan.cc/xurls/v2 v2.4.0 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 // indirect
xorm.io/builder v0.3.11 // indirect
xorm.io/xorm v1.3.2-0.20220714055524-c3bce556200f // indirect
)

1160
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@ import (
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/url"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -24,6 +25,7 @@ import (
type Controller struct {
db *sqlx.DB
urlProvider *url.Provider
authorizer authz.Authorizer
pullreqStore store.PullReqStore
activityStore store.PullReqActivityStore
@ -36,6 +38,7 @@ type Controller struct {
func NewController(
db *sqlx.DB,
urlProvider *url.Provider,
authorizer authz.Authorizer,
pullreqStore store.PullReqStore,
pullreqActivityStore store.PullReqActivityStore,
@ -47,6 +50,7 @@ func NewController(
) *Controller {
return &Controller{
db: db,
urlProvider: urlProvider,
authorizer: authorizer,
pullreqStore: pullreqStore,
activityStore: pullreqActivityStore,

View File

@ -0,0 +1,163 @@
// 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"
"errors"
"fmt"
"time"
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/internal/api/controller"
"github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
"golang.org/x/exp/slices"
)
type MergeInput struct {
Method enum.MergeMethod `json:"method"`
Force bool `json:"force,omitempty"`
DeleteBranch bool `json:"delete_branch,omitempty"`
}
// Create creates a new pull request.
//
//nolint:gocognit // no need to refactor
func (c *Controller) Merge(
ctx context.Context,
session *auth.Session,
repoRef string,
pullreqNum int64,
in *MergeInput,
) (types.MergeResponse, error) {
var (
sha string
pr *types.PullReq
activity *types.PullReqActivity
)
if in.Method == "" {
in.Method = enum.MergeMethodMerge
}
validMethods := []enum.MergeMethod{enum.MergeMethodMerge, enum.MergeMethodRebase, enum.MergeMethodSquash}
if !slices.Contains(validMethods, in.Method) {
return types.MergeResponse{}, usererror.BadRequest(
fmt.Sprintf("wrong merge method type: %s", in.Method))
}
now := time.Now().UnixMilli()
targetRepo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return types.MergeResponse{}, usererror.BadRequest(
fmt.Sprintf("failed to acquire access to target repo: %s", err))
}
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
// pesimistic lock for no other user can merge the same pr
pr, err = c.pullreqStore.FindByNumberWithLock(ctx, targetRepo.ID, pullreqNum, true)
if err != nil {
return fmt.Errorf("failed to get pull request by number: %w", err)
}
if pr.Merged != nil {
return errors.New("pull request already merged")
}
if pr.State != enum.PullReqStateOpen {
return fmt.Errorf("pull request state cannot be %v", pr.State)
}
sourceRepo := targetRepo
if pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err = c.repoStore.Find(ctx, pr.SourceRepoID)
if err != nil {
return fmt.Errorf("failed to get source repository: %w", err)
}
}
var writeParams gitrpc.WriteParams
writeParams, err = controller.CreateRPCWriteParams(ctx, c.urlProvider, session, targetRepo)
if err != nil {
return fmt.Errorf("failed to create RPC write params: %w", err)
}
sha, err = c.gitRPCClient.MergeBranch(ctx, &gitrpc.MergeBranchParams{
WriteParams: writeParams,
BaseBranch: pr.TargetBranch,
HeadRepoUID: sourceRepo.GitUID,
HeadBranch: pr.SourceBranch,
Force: in.Force,
DeleteBranch: in.DeleteBranch,
})
if err != nil {
return err
}
activity = getMergeActivity(session, pr, in, sha)
pr.MergeStrategy = &in.Method
pr.Merged = &now
pr.MergedBy = &session.Principal.ID
pr.State = enum.PullReqStateMerged
err = c.pullreqStore.Update(ctx, pr)
if err != nil {
return fmt.Errorf("failed to update pull request: %w", err)
}
return nil
})
if err != nil {
return types.MergeResponse{}, err
}
err = c.writeActivity(ctx, pr, activity)
if err != nil {
log.Err(err).Msg("failed to write pull req activity")
}
return types.MergeResponse{
SHA: sha,
}, nil
}
func getMergeActivity(session *auth.Session, pr *types.PullReq, in *MergeInput, sha string) *types.PullReqActivity {
now := time.Now().UnixMilli()
act := &types.PullReqActivity{
ID: 0, // Will be populated in the data layer
Version: 0,
CreatedBy: session.Principal.ID,
Created: now,
Updated: now,
Edited: now,
Deleted: nil,
RepoID: pr.TargetRepoID,
PullReqID: pr.ID,
Order: 0, // Will be filled in writeActivity
SubOrder: 0,
ReplySeq: 0,
Type: enum.PullReqActivityTypeMerge,
Kind: enum.PullReqActivityKindSystem,
Text: "",
Payload: map[string]interface{}{
"merge_method": in.Method,
"sha": sha,
},
Metadata: nil,
ResolvedBy: nil,
Resolved: nil,
}
return act
}

View File

@ -8,6 +8,7 @@ import (
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/internal/url"
"github.com/google/wire"
"github.com/jmoiron/sqlx"
@ -18,12 +19,12 @@ var WireSet = wire.NewSet(
ProvideController,
)
func ProvideController(db *sqlx.DB, authorizer authz.Authorizer,
func ProvideController(db *sqlx.DB, urlProvider *url.Provider, authorizer authz.Authorizer,
pullReqStore store.PullReqStore, pullReqActivityStore store.PullReqActivityStore,
pullReqReviewStore store.PullReqReviewStore, pullReqReviewerStore store.PullReqReviewerStore,
repoStore store.RepoStore, principalStore store.PrincipalStore,
rpcClient gitrpc.Interface) *Controller {
return NewController(db, authorizer,
return NewController(db, urlProvider, authorizer,
pullReqStore, pullReqActivityStore,
pullReqReviewStore, pullReqReviewerStore,
repoStore, principalStore,

View File

@ -0,0 +1,52 @@
// 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 controller
import (
"context"
"fmt"
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/internal/githook"
"github.com/harness/gitness/internal/url"
"github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
// CreateRPCWriteParams creates base write parameters for gitrpc write operations.
// IMPORTANT: session & repo are assumed to be not nil!
// TODO: this is duplicate function from repo controller, we need to see where this
// function will be best fit.
func CreateRPCWriteParams(ctx context.Context, urlProvider *url.Provider,
session *auth.Session, repo *types.Repository) (gitrpc.WriteParams, error) {
requestID, ok := request.RequestIDFrom(ctx)
if !ok {
// best effort retrieving of requestID - log in case we can't find it but don't fail operation.
log.Ctx(ctx).Warn().Msg("operation doesn't have a requestID in the context.")
}
// generate envars (add everything githook CLI needs for execution)
envVars, err := githook.GenerateEnvironmentVariables(&githook.Payload{
BaseURL: urlProvider.GetAPIBaseURLInternal(),
RepoID: repo.ID,
PrincipalID: session.Principal.ID,
RequestID: requestID,
})
if err != nil {
return gitrpc.WriteParams{}, fmt.Errorf("failed to generate git hook environment variables: %w", err)
}
return gitrpc.WriteParams{
Actor: gitrpc.Identity{
Name: session.Principal.DisplayName,
Email: session.Principal.Email,
},
RepoUID: repo.GitUID,
EnvVars: envVars,
}, nil
}

View File

@ -0,0 +1,51 @@
// 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 (
"encoding/json"
"errors"
"io"
"net/http"
"github.com/harness/gitness/internal/api/controller/pullreq"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
)
// HandleCreate returns a http.HandlerFunc that creates a new pull request.
func HandleMerge(pullreqCtrl *pullreq.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
in := new(pullreq.MergeInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil && !errors.Is(err, io.EOF) { // allow empty body
render.BadRequestf(w, "Invalid Request Body: %s.", err)
return
}
pullreqNumber, err := request.GetPullReqNumberFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
pr, err := pullreqCtrl.Merge(ctx, session, repoRef, pullreqNumber, in)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.JSON(w, http.StatusOK, pr)
}
}

View File

@ -44,6 +44,11 @@ type listPullReqActivitiesRequest struct {
pullReqRequest
}
type mergePullReq struct {
pullReqRequest
pullreq.MergeInput
}
type commentCreatePullReqRequest struct {
pullReqRequest
pullreq.CommentCreateInput
@ -395,4 +400,18 @@ func pullReqOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&reviewSubmit, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost,
"/repos/{repo_ref}/pullreq/{pullreq_number}/review", reviewSubmit)
mergePullReqOp := openapi3.Operation{}
mergePullReqOp.WithTags("pullreq")
mergePullReqOp.WithMapOfAnything(map[string]interface{}{"operationId": "mergePullReqOp"})
_ = reflector.SetRequest(&mergePullReqOp, new(mergePullReq), http.MethodPost)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(types.MergeResponse), http.StatusOK)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(usererror.Error), http.StatusNotFound)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(usererror.Error), http.StatusMethodNotAllowed)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(usererror.Error), http.StatusConflict)
_ = reflector.SetJSONResponse(&mergePullReqOp, new(usererror.Error), http.StatusUnprocessableEntity)
_ = reflector.Spec.AddOperation(http.MethodPost,
"/repos/{repo_ref}/pullreq/{pullreq_number}/comments/{pullreq_comment_id}", mergePullReqOp)
}

View File

@ -247,6 +247,7 @@ func SetupPullReq(r chi.Router, pullreqCtrl *pullreq.Controller) {
r.Route("/reviews", func(r chi.Router) {
r.Post("/", handlerpullreq.HandleReviewSubmit(pullreqCtrl))
})
r.Post("/merge", handlerpullreq.HandleMerge(pullreqCtrl))
})
})
}

View File

@ -6,6 +6,7 @@ package database
import (
"context"
"strings"
"time"
"github.com/harness/gitness/internal/store"
@ -119,10 +120,19 @@ func (s *PullReqStore) Find(ctx context.Context, id int64) (*types.PullReq, erro
}
// FindByNumber finds the pull request by repo ID and pull request number.
func (s *PullReqStore) FindByNumber(ctx context.Context, repoID, number int64) (*types.PullReq, error) {
const sqlQuery = pullReqSelectBase + `
func (s *PullReqStore) FindByNumberWithLock(
ctx context.Context,
repoID,
number int64,
lock bool,
) (*types.PullReq, error) {
sqlQuery := pullReqSelectBase + `
WHERE pullreq_target_repo_id = $1 AND pullreq_number = $2`
if lock && !strings.HasPrefix(s.db.DriverName(), "sqlite") {
sqlQuery += "\nFOR UPDATE"
}
db := dbtx.GetAccessor(ctx, s.db)
dst := &pullReq{}
@ -133,6 +143,11 @@ func (s *PullReqStore) FindByNumber(ctx context.Context, repoID, number int64) (
return mapPullReq(dst), nil
}
// FindByNumber finds the pull request by repo ID and pull request number.
func (s *PullReqStore) FindByNumber(ctx context.Context, repoID, number int64) (*types.PullReq, error) {
return s.FindByNumberWithLock(ctx, repoID, number, false)
}
// Create creates a new pull request.
func (s *PullReqStore) Create(ctx context.Context, pr *types.PullReq) error {
const sqlQuery = `
@ -402,7 +417,7 @@ func mapPullReq(pr *pullReq) *types.PullReq {
ActivitySeq: pr.ActivitySeq,
MergedBy: pr.MergedBy.Ptr(),
Merged: pr.Merged.Ptr(),
MergeStrategy: pr.MergeStrategy.Ptr(),
MergeStrategy: (*enum.MergeMethod)(pr.MergeStrategy.Ptr()),
Author: types.PrincipalInfo{},
Merger: nil,
}
@ -443,7 +458,7 @@ func mapInternalPullReq(pr *types.PullReq) *pullReq {
ActivitySeq: pr.ActivitySeq,
MergedBy: null.IntFromPtr(pr.MergedBy),
Merged: null.IntFromPtr(pr.Merged),
MergeStrategy: null.StringFromPtr(pr.MergeStrategy),
MergeStrategy: null.StringFromPtr((*string)(pr.MergeStrategy)),
}
return m

View File

@ -231,6 +231,9 @@ type (
// Find the pull request by id.
Find(ctx context.Context, id int64) (*types.PullReq, error)
// FindByNumber finds the pull request by repo ID and the pull request number.
FindByNumberWithLock(ctx context.Context, repoID, number int64, lock bool) (*types.PullReq, error)
// FindByNumber finds the pull request by repo ID and the pull request number.
FindByNumber(ctx context.Context, repoID, number int64) (*types.PullReq, error)

View File

@ -8,9 +8,10 @@ import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
user "github.com/harness/gitness/internal/api/controller/user"
types "github.com/harness/gitness/types"
gomock "github.com/golang/mock/gomock"
)
// MockClient is a mock of Client interface.

View File

@ -8,9 +8,10 @@ import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
types "github.com/harness/gitness/types"
enum "github.com/harness/gitness/types/enum"
gomock "github.com/golang/mock/gomock"
)
// MockSystemStore is a mock of SystemStore interface.

View File

@ -71,6 +71,7 @@ const (
PullReqActivityTypeCodeComment PullReqActivityType = "code-comment"
PullReqActivityTypeTitleChange PullReqActivityType = "title-change"
PullReqActivityTypeReviewSubmit PullReqActivityType = "review-submit"
PullReqActivityTypeMerge PullReqActivityType = "merge"
)
func GetAllPullReqActivityTypes() []PullReqActivityType {
@ -179,3 +180,15 @@ func ParsePullReqReviewerType(s string) (PullReqReviewerType, bool) {
}
return "", false
}
// MergeMethod represents the approach to merge commits into base branch.
type MergeMethod string
const (
// MergeMethodMerge create merge commit.
MergeMethodMerge MergeMethod = "merge"
// MergeMethodSquash squash commits into single commit before merging.
MergeMethodSquash MergeMethod = "squash"
// MergeMethodRebase rebase before merging.
MergeMethodRebase MergeMethod = "rebase"
)

View File

@ -31,9 +31,9 @@ type PullReq struct {
ActivitySeq int64 `json:"-"` // not returned, because it's a server's internal field
MergedBy *int64 `json:"-"` // not returned, because the merger info is in the Merger field
Merged *int64 `json:"merged"`
MergeStrategy *string `json:"merge_strategy"`
MergedBy *int64 `json:"-"` // not returned, because the merger info is in the Merger field
Merged *int64 `json:"merged"`
MergeStrategy *enum.MergeMethod `json:"merge_strategy"`
Author PrincipalInfo `json:"author"`
Merger *PrincipalInfo `json:"merger"`
@ -139,3 +139,7 @@ type PullReqReviewer struct {
Reviewer PrincipalInfo `json:"reviewer"`
AddedBy PrincipalInfo `json:"added_by"`
}
type MergeResponse struct {
SHA string
}