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:
authorPaul Okstad <pokstad@gitlab.com>2020-11-02 20:43:16 +0300
committerPaul Okstad <pokstad@gitlab.com>2020-11-02 20:43:16 +0300
commit38542329a8d1e62410e184beb3347b3916ed8b03 (patch)
tree8d73bf4844ea0038fbc461f801e79e8c00264c24
parentd2ce8e3feb84b1a74121a52be4bb7d20fc860391 (diff)
parente51ae2232edaa0201c65b94b66e437249156cd06 (diff)
Merge branch 'ps-repository-fetch-cmd' into 'master'
Extending of git.Repository with fetch command See merge request gitlab-org/gitaly!2721
-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)
+ })
+}