gitserver: Add OctopusMergeBase RPC method (#63842)

This method can be used to find a common ancestor for many commit SHAs,
to be used by code intel for finding visible uploads.

Closes SRC-485

Test plan: Added unit tests for the several layers (client, grpc,
gitcli).
This commit is contained in:
Erik Seliger 2024-07-19 13:25:09 +02:00 committed by GitHub
parent 36d785dc4a
commit 175667db7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1646 additions and 403 deletions

View File

@ -27,7 +27,7 @@ var (
"ls-files": {"--with-tree", "-z"},
"for-each-ref": {"--format", "--points-at", "--contains", "--sort", "-creatordate", "-refname", "-HEAD"},
"tag": {"--list", "--sort", "-creatordate", "--format", "--points-at"},
"merge-base": {"--"},
"merge-base": {"--octopus", "--"},
"show-ref": {"--heads"},
"shortlog": {"--summary", "--numbered", "--email", "--no-merges", "--after", "--before"},
"cat-file": {"-p", "-t"},

View File

@ -7,6 +7,7 @@ import (
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/gitserver/gitdomain"
"github.com/sourcegraph/sourcegraph/internal/lazyregexp"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
@ -47,3 +48,52 @@ func (g *gitCLIBackend) MergeBase(ctx context.Context, baseRevspec, headRevspec
return api.CommitID(bytes.TrimSpace(stdout)), nil
}
func (g *gitCLIBackend) MergeBaseOctopus(ctx context.Context, revspecs ...string) (api.CommitID, error) {
if len(revspecs) < 2 {
return "", errors.New("at least two revspecs must be given")
}
args := make([]string, 0, len(revspecs)+3)
args = append(args, "merge-base", "--octopus", "--")
args = append(args, revspecs...)
out, err := g.NewCommand(
ctx,
WithArguments(args...),
)
if err != nil {
return "", err
}
defer out.Close()
stdout, err := io.ReadAll(out)
if err != nil {
// Exit code 1 and empty output most likely means that no common merge-base was found.
var e *commandFailedError
if errors.As(err, &e) {
if e.ExitStatus == 1 {
if len(e.Stderr) == 0 {
return "", nil
}
} else if e.ExitStatus == 128 && bytes.Contains(e.Stderr, []byte("fatal: Not a valid object name")) {
p := octopusNotAValidObjectRegexp.FindSubmatch(e.Stderr)
var spec string
if len(p) > 0 {
spec = string(p[1])
}
return "", &gitdomain.RevisionNotFoundError{
Repo: g.repoName,
Spec: spec,
}
}
}
return "", err
}
return api.CommitID(bytes.TrimSpace(stdout)), nil
}
var octopusNotAValidObjectRegexp = lazyregexp.New(`fatal: Not a valid object name ([^\s]+)`)

View File

@ -74,3 +74,99 @@ func TestGitCLIBackend_MergeBase(t *testing.T) {
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}
func TestGitCLIBackend_MergeBaseOctopus(t *testing.T) {
ctx := context.Background()
t.Run("resolves", func(t *testing.T) {
// Prepare repo state:
// Structure:
// 1 - 5 master
// \ - 2 b2
// \ - 3 - 4 b3
// Expected merge base of {master, b2, b3}: 1 (aka testbase)
backend := BackendWithRepoCommands(t,
"echo line1 > f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
"git tag testbase",
"git checkout -b b2",
"echo line2 >> f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
"git checkout master",
"git checkout -b b3",
"echo line3 >> f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
"echo line4 >> f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
"git checkout master",
"echo line2 > f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
)
wantSHA, err := backend.ResolveRevision(ctx, "testbase")
require.NoError(t, err)
require.NotEmpty(t, wantSHA)
base, err := backend.MergeBaseOctopus(ctx, "master", "b2", "b3")
require.NoError(t, err)
require.Equal(t, wantSHA, base)
})
t.Run("orphan branches", func(t *testing.T) {
// Prepare repo state:
backend := BackendWithRepoCommands(t,
"echo line1 > f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
"git checkout --orphan b2",
"echo line2 >> f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
"git checkout master",
"git checkout --orphan b3",
"echo line3 >> f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
"git checkout master",
)
base, err := backend.MergeBaseOctopus(ctx, "master", "b2", "b3")
require.NoError(t, err)
require.Equal(t, api.CommitID(""), base)
})
t.Run("not found revspec", func(t *testing.T) {
// Prepare repo state:
backend := BackendWithRepoCommands(t,
"echo line1 > f",
"git add f",
"git commit -m foo --author='Foo Author <foo@sourcegraph.com>'",
)
// Last revspec not found
_, err := backend.MergeBaseOctopus(ctx, "master", "notfound")
require.Error(t, err)
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
require.Equal(t, "notfound", err.(*gitdomain.RevisionNotFoundError).Spec)
// First revspec not found
_, err = backend.MergeBaseOctopus(ctx, "notfound", "master")
require.Error(t, err)
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
require.Equal(t, "notfound", err.(*gitdomain.RevisionNotFoundError).Spec)
})
t.Run("less than two revspecs", func(t *testing.T) {
// Prepare repo state:
backend := BackendWithRepoCommands(t)
_, err := backend.MergeBaseOctopus(ctx, "master")
require.Error(t, err)
require.ErrorContains(t, err, "at least two revspecs must be given")
})
}

View File

@ -162,6 +162,25 @@ type GitBackend interface {
// This value can be used to determine if a repository changed since the last
// time the hash has been computed.
RefHash(ctx context.Context) ([]byte, error)
// MergeBaseOctopus returns the octopus merge base commit sha for the specified
// revspecs.
// If no common merge base exists, an empty string is returned.
// See the following diagrams from git-merge-base docs on what octopus merge bases
// are:
// Given three commits A, B, and C, MergeBaseOctopus(A, B, C) will compute the
// best common ancestor of all commits.
// For example, with this topology:
// o---o---o---o---C
// /
// / o---o---o---B
// / /
// ---2---1---o---o---o---A
// The result of MergeBaseOctopus(A, B, C) is 2, because 2 is the
// best common ancestor of all commits.
//
// If one of the given revspecs does not exist, a RevisionNotFoundError is returned.
MergeBaseOctopus(ctx context.Context, revspecs ...string) (api.CommitID, error)
}
// CommitLogOrder is the order of the commits returned by CommitLog.

View File

@ -588,6 +588,9 @@ type MockGitBackend struct {
// MergeBaseFunc is an instance of a mock function object controlling
// the behavior of the method MergeBase.
MergeBaseFunc *GitBackendMergeBaseFunc
// MergeBaseOctopusFunc is an instance of a mock function object
// controlling the behavior of the method MergeBaseOctopus.
MergeBaseOctopusFunc *GitBackendMergeBaseOctopusFunc
// RawDiffFunc is an instance of a mock function object controlling the
// behavior of the method RawDiff.
RawDiffFunc *GitBackendRawDiffFunc
@ -686,6 +689,11 @@ func NewMockGitBackend() *MockGitBackend {
return
},
},
MergeBaseOctopusFunc: &GitBackendMergeBaseOctopusFunc{
defaultHook: func(context.Context, ...string) (r0 api.CommitID, r1 error) {
return
},
},
RawDiffFunc: &GitBackendRawDiffFunc{
defaultHook: func(context.Context, string, string, GitDiffComparisonType, RawDiffOpts, ...string) (r0 io.ReadCloser, r1 error) {
return
@ -803,6 +811,11 @@ func NewStrictMockGitBackend() *MockGitBackend {
panic("unexpected invocation of MockGitBackend.MergeBase")
},
},
MergeBaseOctopusFunc: &GitBackendMergeBaseOctopusFunc{
defaultHook: func(context.Context, ...string) (api.CommitID, error) {
panic("unexpected invocation of MockGitBackend.MergeBaseOctopus")
},
},
RawDiffFunc: &GitBackendRawDiffFunc{
defaultHook: func(context.Context, string, string, GitDiffComparisonType, RawDiffOpts, ...string) (io.ReadCloser, error) {
panic("unexpected invocation of MockGitBackend.RawDiff")
@ -894,6 +907,9 @@ func NewMockGitBackendFrom(i GitBackend) *MockGitBackend {
MergeBaseFunc: &GitBackendMergeBaseFunc{
defaultHook: i.MergeBase,
},
MergeBaseOctopusFunc: &GitBackendMergeBaseOctopusFunc{
defaultHook: i.MergeBaseOctopus,
},
RawDiffFunc: &GitBackendRawDiffFunc{
defaultHook: i.RawDiff,
},
@ -2339,6 +2355,121 @@ func (c GitBackendMergeBaseFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// GitBackendMergeBaseOctopusFunc describes the behavior when the
// MergeBaseOctopus method of the parent MockGitBackend instance is invoked.
type GitBackendMergeBaseOctopusFunc struct {
defaultHook func(context.Context, ...string) (api.CommitID, error)
hooks []func(context.Context, ...string) (api.CommitID, error)
history []GitBackendMergeBaseOctopusFuncCall
mutex sync.Mutex
}
// MergeBaseOctopus delegates to the next hook function in the queue and
// stores the parameter and result values of this invocation.
func (m *MockGitBackend) MergeBaseOctopus(v0 context.Context, v1 ...string) (api.CommitID, error) {
r0, r1 := m.MergeBaseOctopusFunc.nextHook()(v0, v1...)
m.MergeBaseOctopusFunc.appendCall(GitBackendMergeBaseOctopusFuncCall{v0, v1, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the MergeBaseOctopus
// method of the parent MockGitBackend instance is invoked and the hook
// queue is empty.
func (f *GitBackendMergeBaseOctopusFunc) SetDefaultHook(hook func(context.Context, ...string) (api.CommitID, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// MergeBaseOctopus method of the parent MockGitBackend instance invokes the
// hook at the front of the queue and discards it. After the queue is empty,
// the default hook function is invoked for any future action.
func (f *GitBackendMergeBaseOctopusFunc) PushHook(hook func(context.Context, ...string) (api.CommitID, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *GitBackendMergeBaseOctopusFunc) SetDefaultReturn(r0 api.CommitID, r1 error) {
f.SetDefaultHook(func(context.Context, ...string) (api.CommitID, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *GitBackendMergeBaseOctopusFunc) PushReturn(r0 api.CommitID, r1 error) {
f.PushHook(func(context.Context, ...string) (api.CommitID, error) {
return r0, r1
})
}
func (f *GitBackendMergeBaseOctopusFunc) nextHook() func(context.Context, ...string) (api.CommitID, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *GitBackendMergeBaseOctopusFunc) appendCall(r0 GitBackendMergeBaseOctopusFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of GitBackendMergeBaseOctopusFuncCall objects
// describing the invocations of this function.
func (f *GitBackendMergeBaseOctopusFunc) History() []GitBackendMergeBaseOctopusFuncCall {
f.mutex.Lock()
history := make([]GitBackendMergeBaseOctopusFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// GitBackendMergeBaseOctopusFuncCall is an object that describes an
// invocation of method MergeBaseOctopus on an instance of MockGitBackend.
type GitBackendMergeBaseOctopusFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is a slice containing the values of the variadic arguments
// passed to this method invocation.
Arg1 []string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 api.CommitID
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation. The variadic slice argument is flattened in this array such
// that one positional argument and three variadic arguments would result in
// a slice of four, not two.
func (c GitBackendMergeBaseOctopusFuncCall) Args() []interface{} {
trailing := []interface{}{}
for _, val := range c.Arg1 {
trailing = append(trailing, val)
}
return append([]interface{}{c.Arg0}, trailing...)
}
// Results returns an interface slice containing the results of this
// invocation.
func (c GitBackendMergeBaseOctopusFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// GitBackendRawDiffFunc describes the behavior when the RawDiff method of
// the parent MockGitBackend instance is invoked.
type GitBackendRawDiffFunc struct {

View File

@ -112,6 +112,16 @@ func (b *observableBackend) MergeBase(ctx context.Context, baseRevspec, headRevs
return b.backend.MergeBase(ctx, baseRevspec, headRevspec)
}
func (b *observableBackend) MergeBaseOctopus(ctx context.Context, revspecs ...string) (_ api.CommitID, err error) {
ctx, _, endObservation := b.operations.mergeBaseOctopus.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
concurrentOps.WithLabelValues("MergeBaseOctopus").Inc()
defer concurrentOps.WithLabelValues("MergeBaseOctopus").Dec()
return b.backend.MergeBaseOctopus(ctx, revspecs...)
}
func (b *observableBackend) Blame(ctx context.Context, commit api.CommitID, path string, opt BlameOptions) (_ BlameHunkReader, err error) {
ctx, errCollector, endObservation := b.operations.blame.WithErrors(ctx, &err, observation.Args{})
ctx, cancel := context.WithCancel(ctx)
@ -536,6 +546,7 @@ type operations struct {
latestCommitTimestamp *observation.Operation
refHash *observation.Operation
commitLog *observation.Operation
mergeBaseOctopus *observation.Operation
}
func newOperations(observationCtx *observation.Context) *operations {
@ -588,6 +599,7 @@ func newOperations(observationCtx *observation.Context) *operations {
latestCommitTimestamp: op("latest-commit-timestamp"),
refHash: op("ref-hash"),
commitLog: op("commit-log"),
mergeBaseOctopus: op("merge-base-octopus"),
}
}

View File

@ -687,6 +687,54 @@ func (gs *grpcServer) MergeBase(ctx context.Context, req *proto.MergeBaseRequest
}, nil
}
func (gs *grpcServer) MergeBaseOctopus(ctx context.Context, req *proto.MergeBaseOctopusRequest) (*proto.MergeBaseOctopusResponse, error) {
revspecs := byteSlicesToStrings(req.GetRevspecs())
accesslog.Record(
ctx,
req.GetRepoName(),
log.Int("revspecs", len(revspecs)),
)
if req.GetRepoName() == "" {
return nil, status.New(codes.InvalidArgument, "repo must be specified").Err()
}
if len(revspecs) < 2 {
return nil, status.New(codes.InvalidArgument, "at least 2 revspecs must be specified").Err()
}
repoName := api.RepoName(req.GetRepoName())
repoDir := gs.fs.RepoDir(repoName)
if err := gs.checkRepoExists(repoName); err != nil {
return nil, err
}
backend := gs.gitBackendSource(repoDir, repoName)
sha, err := backend.MergeBaseOctopus(ctx, revspecs...)
if err != nil {
var e *gitdomain.RevisionNotFoundError
if errors.As(err, &e) {
s, err := status.New(codes.NotFound, "revision not found").WithDetails(&proto.RevisionNotFoundPayload{
Repo: req.GetRepoName(),
Spec: e.Spec,
})
if err != nil {
return nil, err
}
return nil, s.Err()
}
gs.svc.LogIfCorrupt(ctx, repoName, err)
return nil, err
}
return &proto.MergeBaseOctopusResponse{
MergeBaseCommitSha: string(sha),
}, nil
}
func (gs *grpcServer) GetCommit(ctx context.Context, req *proto.GetCommitRequest) (*proto.GetCommitResponse, error) {
accesslog.Record(
ctx,

View File

@ -1096,6 +1096,33 @@ func commitLogRequestToLogFields(req *proto.CommitLogRequest) []log.Field {
}
}
func (l *loggingGRPCServer) MergeBaseOctopus(ctx context.Context, request *proto.MergeBaseOctopusRequest) (response *proto.MergeBaseOctopusResponse, err error) {
start := time.Now()
defer func() {
elapsed := time.Since(start)
doLog(
l.logger,
proto.GitserverService_MergeBaseOctopus_FullMethodName,
status.Code(err),
trace.Context(ctx).TraceID,
elapsed,
mergeBaseOctopusRequestToLogFields(request)...,
)
}()
return l.base.MergeBaseOctopus(ctx, request)
}
func mergeBaseOctopusRequestToLogFields(req *proto.MergeBaseOctopusRequest) []log.Field {
return []log.Field{
log.String("repoName", req.GetRepoName()),
log.Strings("revspecs", byteSlicesToStrings(req.GetRevspecs())),
}
}
type loggingRepositoryServiceServer struct {
base proto.GitserverRepositoryServiceServer
logger log.Logger

View File

@ -1570,6 +1570,81 @@ func TestGRPCServer_CommitLog(t *testing.T) {
})
}
func TestGRPCServer_MergeBaseOctopus(t *testing.T) {
ctx := context.Background()
t.Run("argument validation", func(t *testing.T) {
gs := &grpcServer{}
_, err := gs.MergeBaseOctopus(ctx, &v1.MergeBaseOctopusRequest{RepoName: ""})
require.ErrorContains(t, err, "repo must be specified")
assertGRPCStatusCode(t, err, codes.InvalidArgument)
_, err = gs.MergeBaseOctopus(ctx, &v1.MergeBaseOctopusRequest{RepoName: "therepo", Revspecs: [][]byte{}})
require.ErrorContains(t, err, "at least 2 revspecs must be specified")
assertGRPCStatusCode(t, err, codes.InvalidArgument)
_, err = gs.MergeBaseOctopus(ctx, &v1.MergeBaseOctopusRequest{RepoName: "therepo", Revspecs: [][]byte{[]byte("onlyone")}})
require.ErrorContains(t, err, "at least 2 revspecs must be specified")
assertGRPCStatusCode(t, err, codes.InvalidArgument)
})
t.Run("checks for uncloned repo", func(t *testing.T) {
fs := gitserverfs.NewMockFS()
fs.RepoClonedFunc.SetDefaultReturn(false, nil)
locker := NewMockRepositoryLocker()
locker.StatusFunc.SetDefaultReturn("cloning", true)
gs := &grpcServer{svc: NewMockService(), fs: fs, locker: locker}
_, err := gs.MergeBaseOctopus(ctx, &v1.MergeBaseOctopusRequest{RepoName: "therepo", Revspecs: [][]byte{[]byte("one"), []byte("two"), []byte("three")}})
require.Error(t, err)
assertGRPCStatusCode(t, err, codes.NotFound)
assertHasGRPCErrorDetailOfType(t, err, &proto.RepoNotFoundPayload{})
require.Contains(t, err.Error(), "repo not found")
mockassert.Called(t, fs.RepoClonedFunc)
mockassert.Called(t, locker.StatusFunc)
})
t.Run("revision not found", func(t *testing.T) {
fs := gitserverfs.NewMockFS()
// Repo is cloned, proceed!
fs.RepoClonedFunc.SetDefaultReturn(true, nil)
gs := &grpcServer{
svc: NewMockService(),
fs: fs,
gitBackendSource: func(common.GitDir, api.RepoName) git.GitBackend {
b := git.NewMockGitBackend()
b.MergeBaseOctopusFunc.SetDefaultReturn("", &gitdomain.RevisionNotFoundError{Repo: "therepo"})
return b
},
}
_, err := gs.MergeBaseOctopus(ctx, &v1.MergeBaseOctopusRequest{RepoName: "therepo", Revspecs: [][]byte{[]byte("one"), []byte("two"), []byte("three")}})
require.Error(t, err)
assertGRPCStatusCode(t, err, codes.NotFound)
assertHasGRPCErrorDetailOfType(t, err, &proto.RevisionNotFoundPayload{})
require.Contains(t, err.Error(), "revision not found")
})
t.Run("e2e", func(t *testing.T) {
fs := gitserverfs.NewMockFS()
// Repo is cloned, proceed!
fs.RepoClonedFunc.SetDefaultReturn(true, nil)
b := git.NewMockGitBackend()
b.MergeBaseOctopusFunc.SetDefaultReturn("deadbeef", nil)
gs := &grpcServer{
svc: NewMockService(),
fs: fs,
gitBackendSource: func(common.GitDir, api.RepoName) git.GitBackend {
return b
},
}
cli := spawnServer(t, gs)
res, err := cli.MergeBaseOctopus(ctx, &v1.MergeBaseOctopusRequest{
RepoName: "therepo",
Revspecs: [][]byte{[]byte("one"), []byte("two"), []byte("three")},
})
require.NoError(t, err)
if diff := cmp.Diff(&proto.MergeBaseOctopusResponse{
MergeBaseCommitSha: "deadbeef",
}, res, cmpopts.IgnoreUnexported(proto.MergeBaseOctopusResponse{})); diff != "" {
t.Fatalf("unexpected response (-want +got):\n%s", diff)
}
})
}
func assertGRPCStatusCode(t *testing.T, err error, want codes.Code) {
t.Helper()
s, ok := status.FromError(err)

View File

@ -11281,6 +11281,9 @@ type MockGitserverClient struct {
// MergeBaseFunc is an instance of a mock function object controlling
// the behavior of the method MergeBase.
MergeBaseFunc *GitserverClientMergeBaseFunc
// MergeBaseOctopusFunc is an instance of a mock function object
// controlling the behavior of the method MergeBaseOctopus.
MergeBaseOctopusFunc *GitserverClientMergeBaseOctopusFunc
// NewFileReaderFunc is an instance of a mock function object
// controlling the behavior of the method NewFileReader.
NewFileReaderFunc *GitserverClientNewFileReaderFunc
@ -11430,6 +11433,11 @@ func NewMockGitserverClient() *MockGitserverClient {
return
},
},
MergeBaseOctopusFunc: &GitserverClientMergeBaseOctopusFunc{
defaultHook: func(context.Context, api.RepoName, ...string) (r0 api.CommitID, r1 error) {
return
},
},
NewFileReaderFunc: &GitserverClientNewFileReaderFunc{
defaultHook: func(context.Context, api.RepoName, api.CommitID, string) (r0 io.ReadCloser, r1 error) {
return
@ -11612,6 +11620,11 @@ func NewStrictMockGitserverClient() *MockGitserverClient {
panic("unexpected invocation of MockGitserverClient.MergeBase")
},
},
MergeBaseOctopusFunc: &GitserverClientMergeBaseOctopusFunc{
defaultHook: func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
panic("unexpected invocation of MockGitserverClient.MergeBaseOctopus")
},
},
NewFileReaderFunc: &GitserverClientNewFileReaderFunc{
defaultHook: func(context.Context, api.RepoName, api.CommitID, string) (io.ReadCloser, error) {
panic("unexpected invocation of MockGitserverClient.NewFileReader")
@ -11757,6 +11770,9 @@ func NewMockGitserverClientFrom(i gitserver.Client) *MockGitserverClient {
MergeBaseFunc: &GitserverClientMergeBaseFunc{
defaultHook: i.MergeBase,
},
MergeBaseOctopusFunc: &GitserverClientMergeBaseOctopusFunc{
defaultHook: i.MergeBaseOctopus,
},
NewFileReaderFunc: &GitserverClientNewFileReaderFunc{
defaultHook: i.NewFileReader,
},
@ -13923,6 +13939,127 @@ func (c GitserverClientMergeBaseFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// GitserverClientMergeBaseOctopusFunc describes the behavior when the
// MergeBaseOctopus method of the parent MockGitserverClient instance is
// invoked.
type GitserverClientMergeBaseOctopusFunc struct {
defaultHook func(context.Context, api.RepoName, ...string) (api.CommitID, error)
hooks []func(context.Context, api.RepoName, ...string) (api.CommitID, error)
history []GitserverClientMergeBaseOctopusFuncCall
mutex sync.Mutex
}
// MergeBaseOctopus delegates to the next hook function in the queue and
// stores the parameter and result values of this invocation.
func (m *MockGitserverClient) MergeBaseOctopus(v0 context.Context, v1 api.RepoName, v2 ...string) (api.CommitID, error) {
r0, r1 := m.MergeBaseOctopusFunc.nextHook()(v0, v1, v2...)
m.MergeBaseOctopusFunc.appendCall(GitserverClientMergeBaseOctopusFuncCall{v0, v1, v2, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the MergeBaseOctopus
// method of the parent MockGitserverClient instance is invoked and the hook
// queue is empty.
func (f *GitserverClientMergeBaseOctopusFunc) SetDefaultHook(hook func(context.Context, api.RepoName, ...string) (api.CommitID, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// MergeBaseOctopus method of the parent MockGitserverClient instance
// invokes the hook at the front of the queue and discards it. After the
// queue is empty, the default hook function is invoked for any future
// action.
func (f *GitserverClientMergeBaseOctopusFunc) PushHook(hook func(context.Context, api.RepoName, ...string) (api.CommitID, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *GitserverClientMergeBaseOctopusFunc) SetDefaultReturn(r0 api.CommitID, r1 error) {
f.SetDefaultHook(func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *GitserverClientMergeBaseOctopusFunc) PushReturn(r0 api.CommitID, r1 error) {
f.PushHook(func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
return r0, r1
})
}
func (f *GitserverClientMergeBaseOctopusFunc) nextHook() func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *GitserverClientMergeBaseOctopusFunc) appendCall(r0 GitserverClientMergeBaseOctopusFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of GitserverClientMergeBaseOctopusFuncCall
// objects describing the invocations of this function.
func (f *GitserverClientMergeBaseOctopusFunc) History() []GitserverClientMergeBaseOctopusFuncCall {
f.mutex.Lock()
history := make([]GitserverClientMergeBaseOctopusFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// GitserverClientMergeBaseOctopusFuncCall is an object that describes an
// invocation of method MergeBaseOctopus on an instance of
// MockGitserverClient.
type GitserverClientMergeBaseOctopusFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 api.RepoName
// Arg2 is a slice containing the values of the variadic arguments
// passed to this method invocation.
Arg2 []string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 api.CommitID
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation. The variadic slice argument is flattened in this array such
// that one positional argument and three variadic arguments would result in
// a slice of four, not two.
func (c GitserverClientMergeBaseOctopusFuncCall) Args() []interface{} {
trailing := []interface{}{}
for _, val := range c.Arg2 {
trailing = append(trailing, val)
}
return append([]interface{}{c.Arg0, c.Arg1}, trailing...)
}
// Results returns an interface slice containing the results of this
// invocation.
func (c GitserverClientMergeBaseOctopusFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// GitserverClientNewFileReaderFunc describes the behavior when the
// NewFileReader method of the parent MockGitserverClient instance is
// invoked.

View File

@ -387,6 +387,9 @@ type Client interface {
// MergeBase returns the merge base commit sha for the specified revspecs.
MergeBase(ctx context.Context, repo api.RepoName, base, head string) (api.CommitID, error)
// MergeBaseOctopus returns the octopus merge base commit sha for the specified revspecs.
MergeBaseOctopus(ctx context.Context, repo api.RepoName, revspecs ...string) (api.CommitID, error)
RepoCloneProgress(context.Context, api.RepoName) (*protocol.RepoCloneProgress, error)
// ResolveRevision will return the absolute commit for a commit-ish spec. If spec is empty, HEAD is

View File

@ -714,6 +714,31 @@ func (c *clientImplementor) MergeBase(ctx context.Context, repo api.RepoName, ba
return api.CommitID(res.GetMergeBaseCommitSha()), nil
}
func (c *clientImplementor) MergeBaseOctopus(ctx context.Context, repo api.RepoName, revspecs ...string) (_ api.CommitID, err error) {
ctx, _, endObservation := c.operations.mergeBaseOctopus.With(ctx, &err, observation.Args{
MetricLabelValues: []string{c.scope},
Attrs: []attribute.KeyValue{
attribute.StringSlice("revspecs", revspecs),
},
})
defer endObservation(1, observation.Args{})
client, err := c.clientSource.ClientForRepo(ctx, repo)
if err != nil {
return "", err
}
res, err := client.MergeBaseOctopus(ctx, &proto.MergeBaseOctopusRequest{
RepoName: string(repo),
Revspecs: stringsToByteSlices(revspecs),
})
if err != nil {
return "", err
}
return api.CommitID(res.GetMergeBaseCommitSha()), nil
}
// BehindAhead returns the behind/ahead commit counts information for right vs. left (both Git
// revspecs).
func (c *clientImplementor) BehindAhead(ctx context.Context, repo api.RepoName, left, right string) (_ *gitdomain.BehindAhead, err error) {

View File

@ -2098,6 +2098,56 @@ func TestClient_Commits(t *testing.T) {
})
}
func TestClient_MergeBaseOctopus(t *testing.T) {
t.Run("correctly returns server response", func(t *testing.T) {
source := NewTestClientSource(t, []string{"gitserver"}, func(o *TestClientSourceOptions) {
o.ClientFunc = func(cc *grpc.ClientConn) proto.GitserverServiceClient {
c := NewMockGitserverServiceClient()
c.MergeBaseOctopusFunc.SetDefaultReturn(&proto.MergeBaseOctopusResponse{MergeBaseCommitSha: "deadbeef"}, nil)
return c
}
})
c := NewTestClient(t).WithClientSource(source)
sha, err := c.MergeBaseOctopus(context.Background(), "repo", "master", "b2")
require.NoError(t, err)
require.Equal(t, api.CommitID("deadbeef"), sha)
})
t.Run("returns empty for empty merge base", func(t *testing.T) {
source := NewTestClientSource(t, []string{"gitserver"}, func(o *TestClientSourceOptions) {
o.ClientFunc = func(cc *grpc.ClientConn) proto.GitserverServiceClient {
c := NewMockGitserverServiceClient()
c.MergeBaseOctopusFunc.SetDefaultReturn(&proto.MergeBaseOctopusResponse{MergeBaseCommitSha: ""}, nil)
return c
}
})
c := NewTestClient(t).WithClientSource(source)
sha, err := c.MergeBaseOctopus(context.Background(), "repo", "master", "b2")
require.NoError(t, err)
require.Equal(t, api.CommitID(""), sha)
})
t.Run("revision not found", func(t *testing.T) {
source := NewTestClientSource(t, []string{"gitserver"}, func(o *TestClientSourceOptions) {
o.ClientFunc = func(cc *grpc.ClientConn) proto.GitserverServiceClient {
c := NewMockGitserverServiceClient()
s, err := status.New(codes.NotFound, "bad revision").WithDetails(&proto.RevisionNotFoundPayload{Repo: "repo"})
require.NoError(t, err)
c.MergeBaseOctopusFunc.SetDefaultReturn(nil, s.Err())
return c
}
})
c := NewTestClient(t).WithClientSource(source)
_, err := c.MergeBaseOctopus(context.Background(), "repo", "master", "b2")
require.Error(t, err)
require.True(t, errors.HasType[*gitdomain.RevisionNotFoundError](err))
})
}
func mustParseTime(layout, value string) time.Time {
tm, err := time.Parse(layout, value)
if err != nil {

View File

@ -362,4 +362,9 @@ func (r *errorTranslatingCommitLogClient) Recv() (*proto.CommitLogResponse, erro
return res, convertGRPCErrorToGitDomainError(err)
}
func (r *errorTranslatingClient) MergeBaseOctopus(ctx context.Context, in *proto.MergeBaseOctopusRequest, opts ...grpc.CallOption) (*proto.MergeBaseOctopusResponse, error) {
res, err := r.base.MergeBaseOctopus(ctx, in, opts...)
return res, convertGRPCErrorToGitDomainError(err)
}
var _ proto.GitserverServiceClient = &errorTranslatingClient{}

View File

@ -78,6 +78,9 @@ type MockGitserverServiceClient struct {
// MergeBaseFunc is an instance of a mock function object controlling
// the behavior of the method MergeBase.
MergeBaseFunc *GitserverServiceClientMergeBaseFunc
// MergeBaseOctopusFunc is an instance of a mock function object
// controlling the behavior of the method MergeBaseOctopus.
MergeBaseOctopusFunc *GitserverServiceClientMergeBaseOctopusFunc
// PerforceGetChangelistFunc is an instance of a mock function object
// controlling the behavior of the method PerforceGetChangelist.
PerforceGetChangelistFunc *GitserverServiceClientPerforceGetChangelistFunc
@ -219,6 +222,11 @@ func NewMockGitserverServiceClient() *MockGitserverServiceClient {
return
},
},
MergeBaseOctopusFunc: &GitserverServiceClientMergeBaseOctopusFunc{
defaultHook: func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (r0 *v1.MergeBaseOctopusResponse, r1 error) {
return
},
},
PerforceGetChangelistFunc: &GitserverServiceClientPerforceGetChangelistFunc{
defaultHook: func(context.Context, *v1.PerforceGetChangelistRequest, ...grpc.CallOption) (r0 *v1.PerforceGetChangelistResponse, r1 error) {
return
@ -387,6 +395,11 @@ func NewStrictMockGitserverServiceClient() *MockGitserverServiceClient {
panic("unexpected invocation of MockGitserverServiceClient.MergeBase")
},
},
MergeBaseOctopusFunc: &GitserverServiceClientMergeBaseOctopusFunc{
defaultHook: func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error) {
panic("unexpected invocation of MockGitserverServiceClient.MergeBaseOctopus")
},
},
PerforceGetChangelistFunc: &GitserverServiceClientPerforceGetChangelistFunc{
defaultHook: func(context.Context, *v1.PerforceGetChangelistRequest, ...grpc.CallOption) (*v1.PerforceGetChangelistResponse, error) {
panic("unexpected invocation of MockGitserverServiceClient.PerforceGetChangelist")
@ -517,6 +530,9 @@ func NewMockGitserverServiceClientFrom(i v1.GitserverServiceClient) *MockGitserv
MergeBaseFunc: &GitserverServiceClientMergeBaseFunc{
defaultHook: i.MergeBase,
},
MergeBaseOctopusFunc: &GitserverServiceClientMergeBaseOctopusFunc{
defaultHook: i.MergeBaseOctopus,
},
PerforceGetChangelistFunc: &GitserverServiceClientPerforceGetChangelistFunc{
defaultHook: i.PerforceGetChangelist,
},
@ -2856,6 +2872,128 @@ func (c GitserverServiceClientMergeBaseFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// GitserverServiceClientMergeBaseOctopusFunc describes the behavior when
// the MergeBaseOctopus method of the parent MockGitserverServiceClient
// instance is invoked.
type GitserverServiceClientMergeBaseOctopusFunc struct {
defaultHook func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error)
hooks []func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error)
history []GitserverServiceClientMergeBaseOctopusFuncCall
mutex sync.Mutex
}
// MergeBaseOctopus delegates to the next hook function in the queue and
// stores the parameter and result values of this invocation.
func (m *MockGitserverServiceClient) MergeBaseOctopus(v0 context.Context, v1 *v1.MergeBaseOctopusRequest, v2 ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error) {
r0, r1 := m.MergeBaseOctopusFunc.nextHook()(v0, v1, v2...)
m.MergeBaseOctopusFunc.appendCall(GitserverServiceClientMergeBaseOctopusFuncCall{v0, v1, v2, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the MergeBaseOctopus
// method of the parent MockGitserverServiceClient instance is invoked and
// the hook queue is empty.
func (f *GitserverServiceClientMergeBaseOctopusFunc) SetDefaultHook(hook func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// MergeBaseOctopus method of the parent MockGitserverServiceClient instance
// invokes the hook at the front of the queue and discards it. After the
// queue is empty, the default hook function is invoked for any future
// action.
func (f *GitserverServiceClientMergeBaseOctopusFunc) PushHook(hook func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *GitserverServiceClientMergeBaseOctopusFunc) SetDefaultReturn(r0 *v1.MergeBaseOctopusResponse, r1 error) {
f.SetDefaultHook(func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *GitserverServiceClientMergeBaseOctopusFunc) PushReturn(r0 *v1.MergeBaseOctopusResponse, r1 error) {
f.PushHook(func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error) {
return r0, r1
})
}
func (f *GitserverServiceClientMergeBaseOctopusFunc) nextHook() func(context.Context, *v1.MergeBaseOctopusRequest, ...grpc.CallOption) (*v1.MergeBaseOctopusResponse, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *GitserverServiceClientMergeBaseOctopusFunc) appendCall(r0 GitserverServiceClientMergeBaseOctopusFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of
// GitserverServiceClientMergeBaseOctopusFuncCall objects describing the
// invocations of this function.
func (f *GitserverServiceClientMergeBaseOctopusFunc) History() []GitserverServiceClientMergeBaseOctopusFuncCall {
f.mutex.Lock()
history := make([]GitserverServiceClientMergeBaseOctopusFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// GitserverServiceClientMergeBaseOctopusFuncCall is an object that
// describes an invocation of method MergeBaseOctopus on an instance of
// MockGitserverServiceClient.
type GitserverServiceClientMergeBaseOctopusFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 *v1.MergeBaseOctopusRequest
// Arg2 is a slice containing the values of the variadic arguments
// passed to this method invocation.
Arg2 []grpc.CallOption
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 *v1.MergeBaseOctopusResponse
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation. The variadic slice argument is flattened in this array such
// that one positional argument and three variadic arguments would result in
// a slice of four, not two.
func (c GitserverServiceClientMergeBaseOctopusFuncCall) Args() []interface{} {
trailing := []interface{}{}
for _, val := range c.Arg2 {
trailing = append(trailing, val)
}
return append([]interface{}{c.Arg0, c.Arg1}, trailing...)
}
// Results returns an interface slice containing the results of this
// invocation.
func (c GitserverServiceClientMergeBaseOctopusFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// GitserverServiceClientPerforceGetChangelistFunc describes the behavior
// when the PerforceGetChangelist method of the parent
// MockGitserverServiceClient instance is invoked.

View File

@ -81,6 +81,9 @@ type MockClient struct {
// MergeBaseFunc is an instance of a mock function object controlling
// the behavior of the method MergeBase.
MergeBaseFunc *ClientMergeBaseFunc
// MergeBaseOctopusFunc is an instance of a mock function object
// controlling the behavior of the method MergeBaseOctopus.
MergeBaseOctopusFunc *ClientMergeBaseOctopusFunc
// NewFileReaderFunc is an instance of a mock function object
// controlling the behavior of the method NewFileReader.
NewFileReaderFunc *ClientNewFileReaderFunc
@ -230,6 +233,11 @@ func NewMockClient() *MockClient {
return
},
},
MergeBaseOctopusFunc: &ClientMergeBaseOctopusFunc{
defaultHook: func(context.Context, api.RepoName, ...string) (r0 api.CommitID, r1 error) {
return
},
},
NewFileReaderFunc: &ClientNewFileReaderFunc{
defaultHook: func(context.Context, api.RepoName, api.CommitID, string) (r0 io.ReadCloser, r1 error) {
return
@ -412,6 +420,11 @@ func NewStrictMockClient() *MockClient {
panic("unexpected invocation of MockClient.MergeBase")
},
},
MergeBaseOctopusFunc: &ClientMergeBaseOctopusFunc{
defaultHook: func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
panic("unexpected invocation of MockClient.MergeBaseOctopus")
},
},
NewFileReaderFunc: &ClientNewFileReaderFunc{
defaultHook: func(context.Context, api.RepoName, api.CommitID, string) (io.ReadCloser, error) {
panic("unexpected invocation of MockClient.NewFileReader")
@ -556,6 +569,9 @@ func NewMockClientFrom(i Client) *MockClient {
MergeBaseFunc: &ClientMergeBaseFunc{
defaultHook: i.MergeBase,
},
MergeBaseOctopusFunc: &ClientMergeBaseOctopusFunc{
defaultHook: i.MergeBaseOctopus,
},
NewFileReaderFunc: &ClientNewFileReaderFunc{
defaultHook: i.NewFileReader,
},
@ -2687,6 +2703,124 @@ func (c ClientMergeBaseFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// ClientMergeBaseOctopusFunc describes the behavior when the
// MergeBaseOctopus method of the parent MockClient instance is invoked.
type ClientMergeBaseOctopusFunc struct {
defaultHook func(context.Context, api.RepoName, ...string) (api.CommitID, error)
hooks []func(context.Context, api.RepoName, ...string) (api.CommitID, error)
history []ClientMergeBaseOctopusFuncCall
mutex sync.Mutex
}
// MergeBaseOctopus delegates to the next hook function in the queue and
// stores the parameter and result values of this invocation.
func (m *MockClient) MergeBaseOctopus(v0 context.Context, v1 api.RepoName, v2 ...string) (api.CommitID, error) {
r0, r1 := m.MergeBaseOctopusFunc.nextHook()(v0, v1, v2...)
m.MergeBaseOctopusFunc.appendCall(ClientMergeBaseOctopusFuncCall{v0, v1, v2, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the MergeBaseOctopus
// method of the parent MockClient instance is invoked and the hook queue is
// empty.
func (f *ClientMergeBaseOctopusFunc) SetDefaultHook(hook func(context.Context, api.RepoName, ...string) (api.CommitID, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// MergeBaseOctopus method of the parent MockClient instance invokes the
// hook at the front of the queue and discards it. After the queue is empty,
// the default hook function is invoked for any future action.
func (f *ClientMergeBaseOctopusFunc) PushHook(hook func(context.Context, api.RepoName, ...string) (api.CommitID, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *ClientMergeBaseOctopusFunc) SetDefaultReturn(r0 api.CommitID, r1 error) {
f.SetDefaultHook(func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *ClientMergeBaseOctopusFunc) PushReturn(r0 api.CommitID, r1 error) {
f.PushHook(func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
return r0, r1
})
}
func (f *ClientMergeBaseOctopusFunc) nextHook() func(context.Context, api.RepoName, ...string) (api.CommitID, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *ClientMergeBaseOctopusFunc) appendCall(r0 ClientMergeBaseOctopusFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of ClientMergeBaseOctopusFuncCall objects
// describing the invocations of this function.
func (f *ClientMergeBaseOctopusFunc) History() []ClientMergeBaseOctopusFuncCall {
f.mutex.Lock()
history := make([]ClientMergeBaseOctopusFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// ClientMergeBaseOctopusFuncCall is an object that describes an invocation
// of method MergeBaseOctopus on an instance of MockClient.
type ClientMergeBaseOctopusFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 api.RepoName
// Arg2 is a slice containing the values of the variadic arguments
// passed to this method invocation.
Arg2 []string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 api.CommitID
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation. The variadic slice argument is flattened in this array such
// that one positional argument and three variadic arguments would result in
// a slice of four, not two.
func (c ClientMergeBaseOctopusFuncCall) Args() []interface{} {
trailing := []interface{}{}
for _, val := range c.Arg2 {
trailing = append(trailing, val)
}
return append([]interface{}{c.Arg0, c.Arg1}, trailing...)
}
// Results returns an interface slice containing the results of this
// invocation.
func (c ClientMergeBaseOctopusFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// ClientNewFileReaderFunc describes the behavior when the NewFileReader
// method of the parent MockClient instance is invoked.
type ClientNewFileReaderFunc struct {

View File

@ -46,6 +46,7 @@ type operations struct {
getDefaultBranch *observation.Operation
diff *observation.Operation
changedFiles *observation.Operation
mergeBaseOctopus *observation.Operation
}
func newOperations(observationCtx *observation.Context) *operations {
@ -127,6 +128,7 @@ func newOperations(observationCtx *observation.Context) *operations {
getDefaultBranch: op("GetDefaultBranch"),
diff: op("Diff"),
changedFiles: op("ChangedFiles"),
mergeBaseOctopus: op("MergeBaseOctopus"),
}
}

View File

@ -185,4 +185,9 @@ func (r *automaticRetryClient) CommitLog(ctx context.Context, in *proto.CommitLo
return r.base.CommitLog(ctx, in, opts...)
}
func (r *automaticRetryClient) MergeBaseOctopus(ctx context.Context, in *proto.MergeBaseOctopusRequest, opts ...grpc.CallOption) (*proto.MergeBaseOctopusResponse, error) {
opts = append(defaults.RetryPolicy, opts...)
return r.base.MergeBaseOctopus(ctx, in, opts...)
}
var _ proto.GitserverServiceClient = &automaticRetryClient{}

File diff suppressed because it is too large Load Diff

View File

@ -344,6 +344,27 @@ service GitserverService {
rpc CommitLog(CommitLogRequest) returns (stream CommitLogResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
// MergeBaseOctopus returns the octopus merge base commit sha for the specified
// revspecs.
// If no common merge base exists, an empty string is returned.
// See the following diagrams from git-merge-base docs on what octopus merge bases
// are:
// Given three commits A, B, and C, MergeBaseOctopus(A, B, C) will compute the
// best common ancestor of all commits.
// For example, with this topology:
// o---o---o---o---C
// /
// / o---o---o---B
// / /
// ---2---1---o---o---o---A
// The result of MergeBaseOctopus(A, B, C) is 2, because 2 is the
// best common ancestor of all commits.
//
// If the given repo is not cloned, it will be enqueued for cloning and a
// NotFound error will be returned, with a RepoNotFoundPayload in the details.
rpc MergeBaseOctopus(MergeBaseOctopusRequest) returns (MergeBaseOctopusResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
}
message CommitLogRequest {
@ -1265,6 +1286,22 @@ message MergeBaseResponse {
string merge_base_commit_sha = 1;
}
// MergeBaseOctopusRequest is a request to find the octopus merge base of revspecs.
message MergeBaseOctopusRequest {
// repo_name is the name of the repo to get the octopus merge base for.
// Note: We use field ID 2 here to reserve 1 for a future repo int32 field.
string repo_name = 2;
// revspecs are the revspecs to consider for merge-base. For now, we allow non-utf8
// revspecs.
repeated bytes revspecs = 3;
}
// MergeBaseOctopusResponse is the response from finding the octopus merge base of
// the given revspecs.
message MergeBaseOctopusResponse {
string merge_base_commit_sha = 1;
}
// FirstEverCommitRequest is a request to get the first ever commit in a repo.
message FirstEverCommitRequest {
// repo_name is the name of the repo to get the first ever commit for.

View File

@ -226,6 +226,7 @@ const (
GitserverService_Stat_FullMethodName = "/gitserver.v1.GitserverService/Stat"
GitserverService_ReadDir_FullMethodName = "/gitserver.v1.GitserverService/ReadDir"
GitserverService_CommitLog_FullMethodName = "/gitserver.v1.GitserverService/CommitLog"
GitserverService_MergeBaseOctopus_FullMethodName = "/gitserver.v1.GitserverService/MergeBaseOctopus"
)
// GitserverServiceClient is the client API for GitserverService service.
@ -439,6 +440,36 @@ type GitserverServiceClient interface {
// If one of the given ranges doesn't exist, an error with a ReversionNotFoundPayload
// is returned.
CommitLog(ctx context.Context, in *CommitLogRequest, opts ...grpc.CallOption) (GitserverService_CommitLogClient, error)
// MergeBaseOctopus returns the octopus merge base commit sha for the specified
// revspecs.
// If no common merge base exists, an empty string is returned.
// See the following diagrams from git-merge-base docs on what octopus merge bases
// are:
// Given three commits A, B, and C, git merge-base A B C will compute the merge base between A and a hypothetical commit M, which is a merge between B and C. For example, with this topology:
//
// o---o---o---o---C
// /
//
// / o---o---o---B
// / /
// ---2---1---o---o---o---A
//
// the result of git merge-base A B C is 1. This is because the equivalent topology with a merge commit M between B and C is:
//
// o---o---o---o---o
// / \
//
// / o---o---o---o---M
// / /
// ---2---1---o---o---o---A
//
// and the result of git merge-base A M is 1. Commit 2 is also a common ancestor between A and M, but 1 is a better common ancestor, because 2 is an ancestor of 1. Hence, 2 is not a merge base.
//
// The result of git merge-base --octopus A B C is 2, because 2 is the best common ancestor of all commits.
//
// If the given repo is not cloned, it will be enqueued for cloning and a
// NotFound error will be returned, with a RepoNotFoundPayload in the details.
MergeBaseOctopus(ctx context.Context, in *MergeBaseOctopusRequest, opts ...grpc.CallOption) (*MergeBaseOctopusResponse, error)
}
type gitserverServiceClient struct {
@ -969,6 +1000,15 @@ func (x *gitserverServiceCommitLogClient) Recv() (*CommitLogResponse, error) {
return m, nil
}
func (c *gitserverServiceClient) MergeBaseOctopus(ctx context.Context, in *MergeBaseOctopusRequest, opts ...grpc.CallOption) (*MergeBaseOctopusResponse, error) {
out := new(MergeBaseOctopusResponse)
err := c.cc.Invoke(ctx, GitserverService_MergeBaseOctopus_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GitserverServiceServer is the server API for GitserverService service.
// All implementations must embed UnimplementedGitserverServiceServer
// for forward compatibility
@ -1180,6 +1220,36 @@ type GitserverServiceServer interface {
// If one of the given ranges doesn't exist, an error with a ReversionNotFoundPayload
// is returned.
CommitLog(*CommitLogRequest, GitserverService_CommitLogServer) error
// MergeBaseOctopus returns the octopus merge base commit sha for the specified
// revspecs.
// If no common merge base exists, an empty string is returned.
// See the following diagrams from git-merge-base docs on what octopus merge bases
// are:
// Given three commits A, B, and C, git merge-base A B C will compute the merge base between A and a hypothetical commit M, which is a merge between B and C. For example, with this topology:
//
// o---o---o---o---C
// /
//
// / o---o---o---B
// / /
// ---2---1---o---o---o---A
//
// the result of git merge-base A B C is 1. This is because the equivalent topology with a merge commit M between B and C is:
//
// o---o---o---o---o
// / \
//
// / o---o---o---o---M
// / /
// ---2---1---o---o---o---A
//
// and the result of git merge-base A M is 1. Commit 2 is also a common ancestor between A and M, but 1 is a better common ancestor, because 2 is an ancestor of 1. Hence, 2 is not a merge base.
//
// The result of git merge-base --octopus A B C is 2, because 2 is the best common ancestor of all commits.
//
// If the given repo is not cloned, it will be enqueued for cloning and a
// NotFound error will be returned, with a RepoNotFoundPayload in the details.
MergeBaseOctopus(context.Context, *MergeBaseOctopusRequest) (*MergeBaseOctopusResponse, error)
mustEmbedUnimplementedGitserverServiceServer()
}
@ -1283,6 +1353,9 @@ func (UnimplementedGitserverServiceServer) ReadDir(*ReadDirRequest, GitserverSer
func (UnimplementedGitserverServiceServer) CommitLog(*CommitLogRequest, GitserverService_CommitLogServer) error {
return status.Errorf(codes.Unimplemented, "method CommitLog not implemented")
}
func (UnimplementedGitserverServiceServer) MergeBaseOctopus(context.Context, *MergeBaseOctopusRequest) (*MergeBaseOctopusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MergeBaseOctopus not implemented")
}
func (UnimplementedGitserverServiceServer) mustEmbedUnimplementedGitserverServiceServer() {}
// UnsafeGitserverServiceServer may be embedded to opt out of forward compatibility for this service.
@ -1907,6 +1980,24 @@ func (x *gitserverServiceCommitLogServer) Send(m *CommitLogResponse) error {
return x.ServerStream.SendMsg(m)
}
func _GitserverService_MergeBaseOctopus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MergeBaseOctopusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GitserverServiceServer).MergeBaseOctopus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: GitserverService_MergeBaseOctopus_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GitserverServiceServer).MergeBaseOctopus(ctx, req.(*MergeBaseOctopusRequest))
}
return interceptor(ctx, in, info, handler)
}
// GitserverService_ServiceDesc is the grpc.ServiceDesc for GitserverService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -2002,6 +2093,10 @@ var GitserverService_ServiceDesc = grpc.ServiceDesc{
MethodName: "Stat",
Handler: _GitserverService_Stat_Handler,
},
{
MethodName: "MergeBaseOctopus",
Handler: _GitserverService_MergeBaseOctopus_Handler,
},
},
Streams: []grpc.StreamDesc{
{