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:
authorPavlo Strokov <pstrokov@gitlab.com>2020-11-02 20:43:15 +0300
committerPaul Okstad <pokstad@gitlab.com>2020-11-02 20:43:15 +0300
commite51ae2232edaa0201c65b94b66e437249156cd06 (patch)
treee79fc287f7ae72ab6fd1597af49a29d7ead7dcfe
parent98fe9f19fcecf4b9d27ab60dfaf6d127447a0350 (diff)
Extending of git.Repository with fetch command
In order to support migration of functionality from Ruby to Go implementation it makes sense to extend existing git.Repository interface with new methods in order to re-use them during code porting. This change adds partial implementation of the git fetch sub-command. Currently it is used only in tests, but would be in use in the future to omit code duplication. Part of: https://gitlab.com/gitlab-org/gitaly/-/issues/1466
-rw-r--r--internal/git/repository.go82
-rw-r--r--internal/git/repository_test.go150
2 files changed, 232 insertions, 0 deletions
diff --git a/internal/git/repository.go b/internal/git/repository.go
index 2157b3cc6..a8a23099b 100644
--- a/internal/git/repository.go
+++ b/internal/git/repository.go
@@ -32,6 +32,61 @@ var (
ErrNotFound = errors.New("not found")
)
+// FetchOptsTags controls what tags needs to be imported on fetch.
+type FetchOptsTags string
+
+func (t FetchOptsTags) String() string {
+ return string(t)
+}
+
+var (
+ // FetchOptsTagsDefault enables importing of tags only on fetched branches.
+ FetchOptsTagsDefault = FetchOptsTags("")
+ // FetchOptsTagsAll enables importing of every tag from the remote repository.
+ FetchOptsTagsAll = FetchOptsTags("--tags")
+ // FetchOptsTagsNone disables importing of tags from the remote repository.
+ FetchOptsTagsNone = FetchOptsTags("--no-tags")
+)
+
+// FetchOpts is used to configure invocation of the 'FetchRemote' command.
+type FetchOpts struct {
+ // Env is a list of env vars to pass to the cmd.
+ Env []string
+ // Global is a list of global flags to use with 'git' command.
+ Global []Option
+ // Prune if set fetch removes any remote-tracking references that no longer exist on the remote.
+ // https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---prune
+ Prune bool
+ // Force if set fetch overrides local references with values from remote that's
+ // doesn't have the previous commit as an ancestor.
+ // https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---force
+ Force bool
+ // Tags controls whether tags will be fetched as part of the remote or not.
+ // https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---tags
+ // https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---no-tags
+ Tags FetchOptsTags
+ // Stderr if set it would be used to redirect stderr stream into it.
+ Stderr io.Writer
+}
+
+func (opts FetchOpts) buildFlags() []Option {
+ flags := []Option{Flag{Name: "--quiet"}}
+
+ if opts.Prune {
+ flags = append(flags, Flag{Name: "--prune"})
+ }
+
+ if opts.Force {
+ flags = append(flags, Flag{Name: "--force"})
+ }
+
+ if opts.Tags != FetchOptsTagsDefault {
+ flags = append(flags, Flag{Name: opts.Tags.String()})
+ }
+
+ return flags
+}
+
// Repository represents a Git repository.
type Repository interface {
// ResolveRef resolves the given refish to its object ID. This uses the
@@ -77,6 +132,9 @@ type Repository interface {
// is returned if the oid does not refer to a valid object.
ReadObject(ctx context.Context, oid string) ([]byte, error)
+ // FetchRemote fetches changes from the specified remote.
+ FetchRemote(ctx context.Context, remoteName string, opts FetchOpts) error
+
// Config returns executor of the 'config' sub-command.
Config() Config
@@ -128,6 +186,10 @@ func (UnimplementedRepo) ReadObject(context.Context, string) ([]byte, error) {
return nil, ErrUnimplemented
}
+func (UnimplementedRepo) FetchRemote(context.Context, string, FetchOpts) error {
+ return ErrUnimplemented
+}
+
func (UnimplementedRepo) Remote() Remote {
return UnimplementedRemote{}
}
@@ -341,6 +403,26 @@ func (repo *localRepository) UpdateRef(ctx context.Context, reference, newrev, o
return nil
}
+func (repo *localRepository) FetchRemote(ctx context.Context, remoteName string, opts FetchOpts) error {
+ if err := validateNotBlank(remoteName, "remoteName"); err != nil {
+ return err
+ }
+
+ cmd, err := SafeCmdWithEnv(ctx, opts.Env, repo.repo, opts.Global,
+ SubCmd{
+ Name: "fetch",
+ Flags: opts.buildFlags(),
+ Args: []string{remoteName},
+ },
+ WithStderr(opts.Stderr),
+ )
+ if err != nil {
+ return err
+ }
+
+ return cmd.Wait()
+}
+
func (repo *localRepository) Config() Config {
return RepositoryConfig{repo: repo.repo}
}
diff --git a/internal/git/repository_test.go b/internal/git/repository_test.go
index 78ee145ff..fbd5c6b7b 100644
--- a/internal/git/repository_test.go
+++ b/internal/git/repository_test.go
@@ -1,11 +1,13 @@
package git_test
import (
+ "bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
+ "os/exec"
"path/filepath"
"strings"
"testing"
@@ -13,6 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitaly/client"
+ "gitlab.com/gitlab-org/gitaly/internal/command"
"gitlab.com/gitlab-org/gitaly/internal/git"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/internal/helper"
@@ -514,3 +517,150 @@ func TestLocalRepository_UpdateRef(t *testing.T) {
})
}
}
+
+func TestLocalRepository_FetchRemote(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ initBareWithRemote := func(t *testing.T, remote string) (git.Repository, string, testhelper.Cleanup) {
+ t.Helper()
+
+ testRepo, testRepoPath, cleanup := testhelper.InitBareRepo(t)
+
+ cmd := exec.Command(command.GitPath(), "-C", testRepoPath, "remote", "add", remote, testhelper.GitlabTestStoragePath()+"/gitlab-test.git")
+ err := cmd.Run()
+ if err != nil {
+ cleanup()
+ t.Log(err)
+ t.FailNow()
+ }
+
+ return git.NewRepository(testRepo), testRepoPath, cleanup
+ }
+
+ t.Run("invalid name", func(t *testing.T) {
+ repo := git.NewRepository(nil)
+
+ err := repo.FetchRemote(ctx, " ", git.FetchOpts{})
+ require.True(t, errors.Is(err, git.ErrInvalidArg))
+ require.Contains(t, err.Error(), `"remoteName" is blank or empty`)
+ })
+
+ t.Run("unknown remote", func(t *testing.T) {
+ testRepo, _, cleanup := testhelper.InitBareRepo(t)
+ defer cleanup()
+
+ repo := git.NewRepository(testRepo)
+ var stderr bytes.Buffer
+ err := repo.FetchRemote(ctx, "stub", git.FetchOpts{Stderr: &stderr})
+ require.Error(t, err)
+ require.Contains(t, stderr.String(), "'stub' does not appear to be a git repository")
+ })
+
+ t.Run("ok", func(t *testing.T) {
+ repo, testRepoPath, cleanup := initBareWithRemote(t, "origin")
+ defer cleanup()
+
+ var stderr bytes.Buffer
+ require.NoError(t, repo.FetchRemote(ctx, "origin", git.FetchOpts{Stderr: &stderr}))
+
+ require.Empty(t, stderr.String(), "it should not produce output as it is called with --quite flag by default")
+
+ fetchHeadData, err := ioutil.ReadFile(filepath.Join(testRepoPath, "FETCH_HEAD"))
+ require.NoError(t, err, "it should create FETCH_HEAD with info about fetch")
+
+ fetchHead := string(fetchHeadData)
+ require.Contains(t, fetchHead, "e56497bb5f03a90a51293fc6d516788730953899 not-for-merge branch ''test''")
+ require.Contains(t, fetchHead, "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b not-for-merge tag 'v1.1.0'")
+
+ sha, err := repo.ResolveRefish(ctx, "refs/remotes/origin/master^{commit}")
+ require.NoError(t, err, "the object from remote should exists in local after fetch done")
+ require.Equal(t, "1e292f8fedd741b75372e19097c76d327140c312", sha)
+ })
+
+ t.Run("with env", func(t *testing.T) {
+ _, sourceRepoPath, sourceCleanup := testhelper.NewTestRepo(t)
+ defer sourceCleanup()
+
+ testRepo, testRepoPath, testCleanup := testhelper.NewTestRepo(t)
+ defer testCleanup()
+
+ repo := git.NewRepository(testRepo)
+ testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "remote", "add", "source", sourceRepoPath)
+
+ var stderr bytes.Buffer
+ require.NoError(t, repo.FetchRemote(ctx, "source", git.FetchOpts{Stderr: &stderr, Env: []string{"GIT_TRACE=1"}}))
+ require.Contains(t, stderr.String(), "trace: built-in: git fetch --quiet source --end-of-options")
+ })
+
+ t.Run("with globals", func(t *testing.T) {
+ _, sourceRepoPath, sourceCleanup := testhelper.NewTestRepo(t)
+ defer sourceCleanup()
+
+ testRepo, testRepoPath, testCleanup := testhelper.NewTestRepo(t)
+ defer testCleanup()
+
+ repo := git.NewRepository(testRepo)
+ testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "remote", "add", "source", sourceRepoPath)
+
+ require.NoError(t, repo.FetchRemote(ctx, "source", git.FetchOpts{}))
+
+ testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "branch", "--track", "testing-fetch-prune", "refs/remotes/source/markdown")
+ testhelper.MustRunCommand(t, nil, "git", "-C", sourceRepoPath, "branch", "-D", "markdown")
+
+ require.NoError(t, repo.FetchRemote(
+ ctx,
+ "source",
+ git.FetchOpts{
+ Global: []git.Option{git.ValueFlag{Name: "-c", Value: "fetch.prune=true"}},
+ }),
+ )
+
+ contains, err := repo.ContainsRef(ctx, "refs/remotes/source/markdown")
+ require.NoError(t, err)
+ require.False(t, contains, "remote tracking branch should be pruned as it no longer exists on the remote")
+ })
+
+ t.Run("with prune", func(t *testing.T) {
+ _, sourceRepoPath, sourceCleanup := testhelper.NewTestRepo(t)
+ defer sourceCleanup()
+
+ testRepo, testRepoPath, testCleanup := testhelper.NewTestRepo(t)
+ defer testCleanup()
+
+ repo := git.NewRepository(testRepo)
+
+ testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "remote", "add", "source", sourceRepoPath)
+ require.NoError(t, repo.FetchRemote(ctx, "source", git.FetchOpts{}))
+
+ testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "branch", "--track", "testing-fetch-prune", "refs/remotes/source/markdown")
+ testhelper.MustRunCommand(t, nil, "git", "-C", sourceRepoPath, "branch", "-D", "markdown")
+
+ require.NoError(t, repo.FetchRemote(ctx, "source", git.FetchOpts{Prune: true}))
+
+ contains, err := repo.ContainsRef(ctx, "refs/remotes/source/markdown")
+ require.NoError(t, err)
+ require.False(t, contains, "remote tracking branch should be pruned as it no longer exists on the remote")
+ })
+
+ t.Run("with no tags", func(t *testing.T) {
+ repo, testRepoPath, cleanup := initBareWithRemote(t, "origin")
+ defer cleanup()
+
+ tagsBefore := testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "tag", "--list")
+ require.Empty(t, tagsBefore)
+
+ require.NoError(t, repo.FetchRemote(ctx, "origin", git.FetchOpts{Tags: git.FetchOptsTagsNone, Force: true}))
+
+ tagsAfter := testhelper.MustRunCommand(t, nil, "git", "-C", testRepoPath, "tag", "--list")
+ require.Empty(t, tagsAfter)
+
+ containsBranches, err := repo.ContainsRef(ctx, "'test'")
+ require.NoError(t, err)
+ require.False(t, containsBranches)
+
+ containsTags, err := repo.ContainsRef(ctx, "v1.1.0")
+ require.NoError(t, err)
+ require.False(t, containsTags)
+ })
+}