Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <psteinhardt@gitlab.com>2021-01-26 13:07:14 +0300
committerPatrick Steinhardt <psteinhardt@gitlab.com>2021-01-26 13:07:14 +0300
commit361d5fec80187f93473f8a9f2f1271096aa6c893 (patch)
tree52bce6301626649b24830b67f55c3f9eccc0b4bd
parente284266de14df4b2cc7609a3f6b53516972ed9ce (diff)
parentefbc9154a9f9b89fc2d8044fd214e9374df1b53f (diff)
Merge branch 'pks-fetch-remote-fix-pruning' into 'master'
repository: Fix regressions in FetchRemote See merge request gitlab-org/gitaly!3043
-rw-r--r--changelogs/unreleased/pks-fetch-remote-fix-pruning.yml5
-rw-r--r--internal/gitaly/service/repository/fetch_remote.go25
-rw-r--r--internal/gitaly/service/repository/fetch_remote_test.go160
-rw-r--r--proto/go/gitalypb/repository-service.pb.go65
-rw-r--r--proto/repository-service.proto32
-rw-r--r--ruby/proto/gitaly/repository-service_services_pb.rb2
6 files changed, 264 insertions, 25 deletions
diff --git a/changelogs/unreleased/pks-fetch-remote-fix-pruning.yml b/changelogs/unreleased/pks-fetch-remote-fix-pruning.yml
new file mode 100644
index 000000000..19d1411ee
--- /dev/null
+++ b/changelogs/unreleased/pks-fetch-remote-fix-pruning.yml
@@ -0,0 +1,5 @@
+---
+title: 'repository: Fix regressions in FetchRemote'
+merge_request: 3043
+author:
+type: fixed
diff --git a/internal/gitaly/service/repository/fetch_remote.go b/internal/gitaly/service/repository/fetch_remote.go
index 29f7d56de..bfc351802 100644
--- a/internal/gitaly/service/repository/fetch_remote.go
+++ b/internal/gitaly/service/repository/fetch_remote.go
@@ -29,17 +29,22 @@ func (s *server) FetchRemote(ctx context.Context, req *gitalypb.FetchRemoteReque
}
var stderr bytes.Buffer
- opts := git.FetchOpts{Stderr: &stderr, Force: req.Force, Prune: true, Tags: git.FetchOptsTagsAll, Verbose: req.GetCheckTagsChanged()}
+ opts := git.FetchOpts{
+ Stderr: &stderr,
+ Force: req.Force,
+ Prune: !req.NoPrune,
+ Tags: git.FetchOptsTagsAll,
+ Verbose: req.GetCheckTagsChanged(),
+ }
if req.GetNoTags() {
opts.Tags = git.FetchOptsTagsNone
}
repo := git.NewRepository(req.GetRepository(), s.cfg)
- params := req.GetRemoteParams()
remoteName := req.GetRemote()
- if params != nil {
+ if params := req.GetRemoteParams(); params != nil {
remoteName = params.GetName()
remoteURL := params.GetUrl()
refspecs := s.getRefspecs(params.GetMirrorRefmaps())
@@ -66,12 +71,6 @@ func (s *server) FetchRemote(ctx context.Context, req *gitalypb.FetchRemoteReque
opts.Global = append(opts.Global, git.ConfigPair{Key: "remote." + remoteName + ".fetch", Value: refspec})
}
- opts.Global = append(opts.Global,
- git.ConfigPair{Key: "remote." + remoteName + ".mirror", Value: "true"},
- git.ConfigPair{Key: "remote." + remoteName + ".prune", Value: "true"},
- git.ConfigPair{Key: "http.followRedirects", Value: "false"},
- )
-
if params.GetHttpAuthorizationHeader() != "" {
client, err := s.ruby.RepositoryServiceClient(ctx)
if err != nil {
@@ -129,6 +128,10 @@ func (s *server) FetchRemote(ctx context.Context, req *gitalypb.FetchRemoteReque
defer cancel()
}
+ opts.Global = append(opts.Global,
+ git.ConfigPair{Key: "http.followRedirects", Value: "false"},
+ )
+
if err := repo.FetchRemote(ctx, remoteName, opts); err != nil {
if _, ok := status.FromError(err); ok {
// this check is used because of internal call to alternates.PathAndEnv
@@ -201,6 +204,10 @@ func (s *server) validateFetchRemoteRequest(req *gitalypb.FetchRemoteRequest) er
}
func (s *server) getRefspecs(refmaps []string) []string {
+ if len(refmaps) == 0 {
+ return []string{"+refs/*:refs/*"}
+ }
+
refspecs := make([]string, 0, len(refmaps))
for _, refmap := range refmaps {
diff --git a/internal/gitaly/service/repository/fetch_remote_test.go b/internal/gitaly/service/repository/fetch_remote_test.go
index c0577df6a..84c049c5d 100644
--- a/internal/gitaly/service/repository/fetch_remote_test.go
+++ b/internal/gitaly/service/repository/fetch_remote_test.go
@@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/git"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/internal/helper/text"
"gitlab.com/gitlab-org/gitaly/internal/storage"
@@ -73,6 +74,165 @@ func TestFetchRemoteSuccess(t *testing.T) {
require.Equal(t, resp.TagsChanged, true)
}
+func TestFetchRemote_withDefaultRefmaps(t *testing.T) {
+ locator := config.NewLocator(config.Config)
+ serverSocketPath, stop := runRepoServer(t, locator, testhelper.WithInternalSocket(config.Config))
+ defer stop()
+
+ client, conn := newRepositoryClient(t, serverSocketPath)
+ defer conn.Close()
+
+ sourceRepoProto, sourceRepoPath, cleanup := testhelper.NewTestRepo(t)
+ defer cleanup()
+ sourceRepo := git.NewRepository(sourceRepoProto, config.Config)
+
+ targetRepoProto, targetRepoPath := copyRepoWithNewRemote(t, sourceRepoProto, locator, "my-remote")
+ defer func() {
+ require.NoError(t, os.RemoveAll(targetRepoPath))
+ }()
+ targetRepo := git.NewRepository(targetRepoProto, config.Config)
+
+ port, stopGitServer := testhelper.GitServer(t, config.Config, sourceRepoPath, nil)
+ defer func() { require.NoError(t, stopGitServer()) }()
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ require.NoError(t, sourceRepo.UpdateRef(ctx, "refs/heads/foobar", "refs/heads/master", ""))
+
+ // With no refmap given, FetchRemote should fall back to
+ // "refs/heads/*:refs/heads/*" and thus mirror what's in the source
+ // repository.
+ resp, err := client.FetchRemote(ctx, &gitalypb.FetchRemoteRequest{
+ Repository: targetRepoProto,
+ RemoteParams: &gitalypb.Remote{
+ Url: fmt.Sprintf("http://127.0.0.1:%d/%s", port, filepath.Base(sourceRepoPath)),
+ Name: "my-remote",
+ },
+ })
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+
+ sourceRefs, err := sourceRepo.GetReferences(ctx, "")
+ require.NoError(t, err)
+ targetRefs, err := targetRepo.GetReferences(ctx, "")
+ require.NoError(t, err)
+ require.Equal(t, sourceRefs, targetRefs)
+}
+
+func TestFetchRemote_prune(t *testing.T) {
+ locator := config.NewLocator(config.Config)
+ serverSocketPath, stop := runRepoServer(t, locator, testhelper.WithInternalSocket(config.Config))
+ defer stop()
+
+ client, conn := newRepositoryClient(t, serverSocketPath)
+ defer conn.Close()
+
+ sourceRepo, sourceRepoPath, cleanup := testhelper.NewTestRepo(t)
+ defer cleanup()
+
+ port, stopGitServer := testhelper.GitServer(t, config.Config, sourceRepoPath, nil)
+ defer func() { require.NoError(t, stopGitServer()) }()
+
+ remoteURL := fmt.Sprintf("http://127.0.0.1:%d/%s", port, filepath.Base(sourceRepoPath))
+
+ for _, tc := range []struct {
+ desc string
+ request *gitalypb.FetchRemoteRequest
+ ref git.ReferenceName
+ shouldExist bool
+ }{
+ {
+ desc: "NoPrune=true should not delete reference matching remote's refspec",
+ request: &gitalypb.FetchRemoteRequest{
+ Remote: "my-remote",
+ NoPrune: true,
+ },
+ ref: "refs/remotes/my-remote/nonexistent",
+ shouldExist: true,
+ },
+ {
+ desc: "NoPrune=false should delete reference matching remote's refspec",
+ request: &gitalypb.FetchRemoteRequest{
+ Remote: "my-remote",
+ NoPrune: false,
+ },
+ ref: "refs/remotes/my-remote/nonexistent",
+ shouldExist: false,
+ },
+ {
+ desc: "NoPrune=false should not delete ref outside of remote's refspec",
+ request: &gitalypb.FetchRemoteRequest{
+ Remote: "my-remote",
+ NoPrune: false,
+ },
+ ref: "refs/heads/nonexistent",
+ shouldExist: true,
+ },
+ {
+ desc: "NoPrune=true with explicit Remote should not delete reference",
+ request: &gitalypb.FetchRemoteRequest{
+ RemoteParams: &gitalypb.Remote{
+ Url: remoteURL,
+ Name: "my-remote",
+ },
+ NoPrune: true,
+ },
+ ref: "refs/heads/nonexistent",
+ shouldExist: true,
+ },
+ {
+ desc: "NoPrune=false with explicit Remote should delete reference",
+ request: &gitalypb.FetchRemoteRequest{
+ RemoteParams: &gitalypb.Remote{
+ Url: remoteURL,
+ Name: "my-remote",
+ },
+ NoPrune: false,
+ },
+ ref: "refs/heads/nonexistent",
+ shouldExist: false,
+ },
+ {
+ desc: "NoPrune=false with explicit Remote should not delete reference outside of refspec",
+ request: &gitalypb.FetchRemoteRequest{
+ RemoteParams: &gitalypb.Remote{
+ Url: remoteURL,
+ Name: "my-remote",
+ MirrorRefmaps: []string{
+ "refs/heads/*:refs/remotes/my-remote/*",
+ },
+ },
+ NoPrune: false,
+ },
+ ref: "refs/heads/nonexistent",
+ shouldExist: true,
+ },
+ } {
+ t.Run(tc.desc, func(t *testing.T) {
+ targetRepoProto, targetRepoPath := copyRepoWithNewRemote(t, sourceRepo, locator, "my-remote")
+ defer func() {
+ require.NoError(t, os.RemoveAll(targetRepoPath))
+ }()
+ targetRepo := git.NewRepository(targetRepoProto, config.Config)
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ require.NoError(t, targetRepo.UpdateRef(ctx, tc.ref, "refs/heads/master", ""))
+
+ tc.request.Repository = targetRepoProto
+ resp, err := client.FetchRemote(ctx, tc.request)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+
+ hasRevision, err := targetRepo.HasRevision(ctx, tc.ref.Revision())
+ require.NoError(t, err)
+ require.Equal(t, tc.shouldExist, hasRevision)
+ })
+ }
+}
+
func TestFetchRemoteFailure(t *testing.T) {
repo, _, cleanup := testhelper.NewTestRepo(t)
defer cleanup()
diff --git a/proto/go/gitalypb/repository-service.pb.go b/proto/go/gitalypb/repository-service.pb.go
index b2834ab21..00f18bfef 100644
--- a/proto/go/gitalypb/repository-service.pb.go
+++ b/proto/go/gitalypb/repository-service.pb.go
@@ -780,15 +780,26 @@ func (m *ApplyGitattributesResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_ApplyGitattributesResponse proto.InternalMessageInfo
type FetchRemoteRequest struct {
- Repository *Repository `protobuf:"bytes,1,opt,name=repository,proto3" json:"repository,omitempty"`
- Remote string `protobuf:"bytes,2,opt,name=remote,proto3" json:"remote,omitempty"`
- Force bool `protobuf:"varint,3,opt,name=force,proto3" json:"force,omitempty"`
- NoTags bool `protobuf:"varint,4,opt,name=no_tags,json=noTags,proto3" json:"no_tags,omitempty"`
- Timeout int32 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"`
- SshKey string `protobuf:"bytes,6,opt,name=ssh_key,json=sshKey,proto3" json:"ssh_key,omitempty"`
- KnownHosts string `protobuf:"bytes,7,opt,name=known_hosts,json=knownHosts,proto3" json:"known_hosts,omitempty"`
- NoPrune bool `protobuf:"varint,9,opt,name=no_prune,json=noPrune,proto3" json:"no_prune,omitempty"`
- RemoteParams *Remote `protobuf:"bytes,10,opt,name=remote_params,json=remoteParams,proto3" json:"remote_params,omitempty"`
+ Repository *Repository `protobuf:"bytes,1,opt,name=repository,proto3" json:"repository,omitempty"`
+ // remote is the name of the remote that shall be fetched. This remote must
+ // exist in the repository's configuration already. This parameter is
+ // deprecated in favor of remote_params.
+ Remote string `protobuf:"bytes,2,opt,name=remote,proto3" json:"remote,omitempty"`
+ // force determines if references should be force-updated in case they have
+ // diverged.
+ Force bool `protobuf:"varint,3,opt,name=force,proto3" json:"force,omitempty"`
+ // no_tags determines whether tags should be fetched.
+ NoTags bool `protobuf:"varint,4,opt,name=no_tags,json=noTags,proto3" json:"no_tags,omitempty"`
+ // timeout specifies a timeout for the fetch.
+ Timeout int32 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"`
+ SshKey string `protobuf:"bytes,6,opt,name=ssh_key,json=sshKey,proto3" json:"ssh_key,omitempty"`
+ KnownHosts string `protobuf:"bytes,7,opt,name=known_hosts,json=knownHosts,proto3" json:"known_hosts,omitempty"`
+ // no_prune will the fetch to not prune remote references which do not exist
+ // in the remote repository anymore.
+ NoPrune bool `protobuf:"varint,9,opt,name=no_prune,json=noPrune,proto3" json:"no_prune,omitempty"`
+ // remote_params specifies the remote repository which should be fetched
+ // from.
+ RemoteParams *Remote `protobuf:"bytes,10,opt,name=remote_params,json=remoteParams,proto3" json:"remote_params,omitempty"`
// If check_tags_changed is true, the FetchRemote RPC will check whether any
// tags were modified, returning the result in the tags_changed field of
// FetchRemoteResponse
@@ -3335,14 +3346,32 @@ func (m *SearchFilesByContentResponse) GetEndOfMatch() bool {
return false
}
+// Remote represents a git remote repository.
type Remote struct {
- Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
- Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
- HttpAuthorizationHeader string `protobuf:"bytes,3,opt,name=http_authorization_header,json=httpAuthorizationHeader,proto3" json:"http_authorization_header,omitempty"`
- MirrorRefmaps []string `protobuf:"bytes,4,rep,name=mirror_refmaps,json=mirrorRefmaps,proto3" json:"mirror_refmaps,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
+ // url is the URL of the remote repository.
+ Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
+ // name is the name of the remote repository. This is mainly used to
+ // determine where fetched references should end up, e.g. in
+ // `refs/remotes/$name/`.
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ // http_authorization_header is the HTTP header which should be added to
+ // the request in order to authenticate against the repository.
+ HttpAuthorizationHeader string `protobuf:"bytes,3,opt,name=http_authorization_header,json=httpAuthorizationHeader,proto3" json:"http_authorization_header,omitempty"`
+ // mirror_refmaps contains the refspecs which shall be fetched. Some special
+ // refspecs are accepted:
+ //
+ // - "all_refs" gets translated to "+refs/*:refs/*", which mirrors all
+ // references of the source repository.
+ // - "heads" gets translated to "+refs/heads/*:refs/heads/*", which mirrors
+ // all branches of the source repository.
+ // - "tags" gets translated to "+refs/tags/*:refs/tags/*", which mirrors all
+ // tags of the source repository.
+ //
+ // If no refspecs are given, this defaults to "all_refs".
+ MirrorRefmaps []string `protobuf:"bytes,4,rep,name=mirror_refmaps,json=mirrorRefmaps,proto3" json:"mirror_refmaps,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
}
func (m *Remote) Reset() { *m = Remote{} }
@@ -4257,6 +4286,8 @@ type RepositoryServiceClient interface {
WriteCommitGraph(ctx context.Context, in *WriteCommitGraphRequest, opts ...grpc.CallOption) (*WriteCommitGraphResponse, error)
RepositorySize(ctx context.Context, in *RepositorySizeRequest, opts ...grpc.CallOption) (*RepositorySizeResponse, error)
ApplyGitattributes(ctx context.Context, in *ApplyGitattributesRequest, opts ...grpc.CallOption) (*ApplyGitattributesResponse, error)
+ // FetchRemote fetches references from a remote repository into the local
+ // repository.
FetchRemote(ctx context.Context, in *FetchRemoteRequest, opts ...grpc.CallOption) (*FetchRemoteResponse, error)
CreateRepository(ctx context.Context, in *CreateRepositoryRequest, opts ...grpc.CallOption) (*CreateRepositoryResponse, error)
GetArchive(ctx context.Context, in *GetArchiveRequest, opts ...grpc.CallOption) (RepositoryService_GetArchiveClient, error)
@@ -4925,6 +4956,8 @@ type RepositoryServiceServer interface {
WriteCommitGraph(context.Context, *WriteCommitGraphRequest) (*WriteCommitGraphResponse, error)
RepositorySize(context.Context, *RepositorySizeRequest) (*RepositorySizeResponse, error)
ApplyGitattributes(context.Context, *ApplyGitattributesRequest) (*ApplyGitattributesResponse, error)
+ // FetchRemote fetches references from a remote repository into the local
+ // repository.
FetchRemote(context.Context, *FetchRemoteRequest) (*FetchRemoteResponse, error)
CreateRepository(context.Context, *CreateRepositoryRequest) (*CreateRepositoryResponse, error)
GetArchive(*GetArchiveRequest, RepositoryService_GetArchiveServer) error
diff --git a/proto/repository-service.proto b/proto/repository-service.proto
index ed32e64f5..3f7b3f405 100644
--- a/proto/repository-service.proto
+++ b/proto/repository-service.proto
@@ -48,6 +48,9 @@ service RepositoryService {
op: MUTATOR
};
}
+
+ // FetchRemote fetches references from a remote repository into the local
+ // repository.
rpc FetchRemote(FetchRemoteRequest) returns (FetchRemoteResponse) {
option (op_type) = {
op: MUTATOR
@@ -294,14 +297,25 @@ message ApplyGitattributesResponse {}
message FetchRemoteRequest {
Repository repository = 1 [(target_repository)=true];
+ // remote is the name of the remote that shall be fetched. This remote must
+ // exist in the repository's configuration already. This parameter is
+ // deprecated in favor of remote_params.
string remote = 2;
+ // force determines if references should be force-updated in case they have
+ // diverged.
bool force = 3;
+ // no_tags determines whether tags should be fetched.
bool no_tags = 4;
+ // timeout specifies a timeout for the fetch.
int32 timeout = 5;
string ssh_key = 6;
string known_hosts = 7;
reserved 8;
+ // no_prune will the fetch to not prune remote references which do not exist
+ // in the remote repository anymore.
bool no_prune = 9;
+ // remote_params specifies the remote repository which should be fetched
+ // from.
Remote remote_params = 10;
// If check_tags_changed is true, the FetchRemote RPC will check whether any
// tags were modified, returning the result in the tags_changed field of
@@ -607,10 +621,28 @@ message SearchFilesByContentResponse {
bool end_of_match = 3;
}
+// Remote represents a git remote repository.
message Remote {
+ // url is the URL of the remote repository.
string url = 1;
+ // name is the name of the remote repository. This is mainly used to
+ // determine where fetched references should end up, e.g. in
+ // `refs/remotes/$name/`.
string name = 2;
+ // http_authorization_header is the HTTP header which should be added to
+ // the request in order to authenticate against the repository.
string http_authorization_header = 3;
+ // mirror_refmaps contains the refspecs which shall be fetched. Some special
+ // refspecs are accepted:
+ //
+ // - "all_refs" gets translated to "+refs/*:refs/*", which mirrors all
+ // references of the source repository.
+ // - "heads" gets translated to "+refs/heads/*:refs/heads/*", which mirrors
+ // all branches of the source repository.
+ // - "tags" gets translated to "+refs/tags/*:refs/tags/*", which mirrors all
+ // tags of the source repository.
+ //
+ // If no refspecs are given, this defaults to "all_refs".
repeated string mirror_refmaps = 4;
}
diff --git a/ruby/proto/gitaly/repository-service_services_pb.rb b/ruby/proto/gitaly/repository-service_services_pb.rb
index 3fc818dc5..b67eaf506 100644
--- a/ruby/proto/gitaly/repository-service_services_pb.rb
+++ b/ruby/proto/gitaly/repository-service_services_pb.rb
@@ -22,6 +22,8 @@ module Gitaly
rpc :WriteCommitGraph, Gitaly::WriteCommitGraphRequest, Gitaly::WriteCommitGraphResponse
rpc :RepositorySize, Gitaly::RepositorySizeRequest, Gitaly::RepositorySizeResponse
rpc :ApplyGitattributes, Gitaly::ApplyGitattributesRequest, Gitaly::ApplyGitattributesResponse
+ # FetchRemote fetches references from a remote repository into the local
+ # repository.
rpc :FetchRemote, Gitaly::FetchRemoteRequest, Gitaly::FetchRemoteResponse
rpc :CreateRepository, Gitaly::CreateRepositoryRequest, Gitaly::CreateRepositoryResponse
rpc :GetArchive, Gitaly::GetArchiveRequest, stream(Gitaly::GetArchiveResponse)