diff --git a/internal/git/localrepo/remote.go b/internal/git/localrepo/remote.go index ac3f4b39bef25d1fee01924d49effd7779a2eb8d..023aa50419608deef333e2b84aab1c6e2609aeff 100644 --- a/internal/git/localrepo/remote.go +++ b/internal/git/localrepo/remote.go @@ -215,6 +215,12 @@ type FetchOpts struct { Tags FetchOptsTags // Stderr if set it would be used to redirect stderr stream into it. Stderr io.Writer + // RefSpecs specifies which refs to fetch and which local refs to update + // https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt-ltrefspecgt + RefSpecs []string + // Atomic makes local ref updates use an atomic transaction + // https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---atomic + Atomic bool } // FetchRemote fetches changes from the specified remote. @@ -234,7 +240,7 @@ func (repo *Repo) FetchRemote(ctx context.Context, remoteName string, opts Fetch git.SubCmd{ Name: "fetch", Flags: opts.buildFlags(), - Args: []string{remoteName}, + Args: append([]string{remoteName}, opts.RefSpecs...), }, commandOptions..., ) @@ -264,6 +270,10 @@ func (opts FetchOpts) buildFlags() []git.Option { flags = append(flags, git.Flag{Name: opts.Tags.String()}) } + if opts.Atomic { + flags = append(flags, git.Flag{Name: "--atomic"}) + } + return flags } diff --git a/internal/git/localrepo/remote_test.go b/internal/git/localrepo/remote_test.go index bd424ecdf105fdf08884ffe88b9b2c2e2bf190c8..0e49197d34080cec36343d1f6be06fd7bd1b92d3 100644 --- a/internal/git/localrepo/remote_test.go +++ b/internal/git/localrepo/remote_test.go @@ -435,6 +435,21 @@ func TestRepo_FetchRemote(t *testing.T) { require.NoError(t, err) require.False(t, containsTags) }) + + t.Run("with refspec", func(t *testing.T) { + repo, _, cleanup := initBareWithRemote(t, "origin") + defer cleanup() + + require.NoError(t, repo.FetchRemote(ctx, "origin", FetchOpts{RefSpecs: []string{"refs/heads/master"}})) + + containsBranch, err := repo.HasRevision(ctx, git.Revision("'test'")) + require.NoError(t, err) + require.False(t, containsBranch) + + sha, err := repo.ResolveRevision(ctx, git.Revision("refs/remotes/origin/master^{commit}")) + require.NoError(t, err, "the object from remote should exists in local after fetch done") + require.Equal(t, git.ObjectID("1e292f8fedd741b75372e19097c76d327140c312"), sha) + }) } func TestRepo_Push(t *testing.T) { diff --git a/internal/gitaly/service/remote/fetch_internal_remote.go b/internal/gitaly/service/remote/fetch_internal_remote.go index d5050a52f4fa0f1ae0305b925745dae4ef1909d4..fd14fd4309e551197978ee6ddad87e070c73ce3a 100644 --- a/internal/gitaly/service/remote/fetch_internal_remote.go +++ b/internal/gitaly/service/remote/fetch_internal_remote.go @@ -8,6 +8,7 @@ import ( "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/internal/git" + "gitlab.com/gitlab-org/gitaly/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/ref" "gitlab.com/gitlab-org/gitaly/internal/gitalyssh" "gitlab.com/gitlab-org/gitaly/internal/helper" @@ -32,30 +33,20 @@ func (s *server) FetchInternalRemote(ctx context.Context, req *gitalypb.FetchInt return nil, fmt.Errorf("upload pack environment: %w", err) } + repo := localrepo.New(s.gitCmdFactory, req.GetRepository(), s.cfg) stderr := &bytes.Buffer{} - flags := []git.Option{ - git.Flag{Name: "--prune"}, - git.Flag{Name: "--atomic"}, - } - options := []git.CmdOpt{ - git.WithEnv(env...), - git.WithStderr(stderr), - git.WithRefTxHook(ctx, req.Repository, s.cfg), - } - - cmd, err := s.gitCmdFactory.New(ctx, req.Repository, - git.SubCmd{ - Name: "fetch", - Flags: flags, - Args: []string{gitalyssh.GitalyInternalURL, mirrorRefSpec}, + err = repo.FetchRemote(ctx, gitalyssh.GitalyInternalURL, localrepo.FetchOpts{ + Env: env, + Stderr: stderr, + Prune: true, + Atomic: true, + RefSpecs: []string{mirrorRefSpec}, + CommandOptions: []git.CmdOpt{ + git.WithRefTxHook(ctx, req.Repository, s.cfg), }, - options..., - ) + }) if err != nil { - return nil, fmt.Errorf("create git fetch: %w", err) - } - if err := cmd.Wait(); err != nil { if featureflag.IsDisabled(ctx, featureflag.FetchInternalRemoteErrors) { // Design quirk: if the fetch fails, this RPC returns Result: false, but no error. ctxlogrus.Extract(ctx).WithError(err).WithField("stderr", stderr.String()).Warn("git fetch failed")