aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config/config.go8
-rw-r--r--internal/url/url.go6
-rwxr-xr-xinternal/url/url_test.go107
-rw-r--r--options.go8
-rw-r--r--plumbing/transport/common_test.go20
-rw-r--r--repository.go14
-rw-r--r--repository_test.go29
7 files changed, 153 insertions, 39 deletions
diff --git a/config/config.go b/config/config.go
index 83629fc..60dfca4 100644
--- a/config/config.go
+++ b/config/config.go
@@ -264,6 +264,7 @@ const (
defaultBranchKey = "defaultBranch"
repositoryFormatVersionKey = "repositoryformatversion"
objectFormat = "objectformat"
+ mirrorKey = "mirror"
// DefaultPackWindow holds the number of previous objects used to
// generate deltas. The value 10 is the same used by git command.
@@ -578,6 +579,8 @@ type RemoteConfig struct {
// URLs the URLs of a remote repository. It must be non-empty. Fetch will
// always use the first URL, while push will use all of them.
URLs []string
+ // Mirror indicates that the repository is a mirror of remote.
+ Mirror bool
// insteadOfRulesApplied have urls been modified
insteadOfRulesApplied bool
@@ -631,6 +634,7 @@ func (c *RemoteConfig) unmarshal(s *format.Subsection) error {
c.Name = c.raw.Name
c.URLs = append([]string(nil), c.raw.Options.GetAll(urlKey)...)
c.Fetch = fetch
+ c.Mirror = c.raw.Options.Get(mirrorKey) == "true"
return nil
}
@@ -663,6 +667,10 @@ func (c *RemoteConfig) marshal() *format.Subsection {
c.raw.SetOption(fetchKey, values...)
}
+ if c.Mirror {
+ c.raw.SetOption(mirrorKey, strconv.FormatBool(c.Mirror))
+ }
+
return c.raw
}
diff --git a/internal/url/url.go b/internal/url/url.go
index 14cf133..2662448 100644
--- a/internal/url/url.go
+++ b/internal/url/url.go
@@ -5,8 +5,10 @@ import (
)
var (
- isSchemeRegExp = regexp.MustCompile(`^[^:]+://`)
- scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5})(?:\/|:))?(?P<path>[^\\].*\/[^\\].*)$`)
+ isSchemeRegExp = regexp.MustCompile(`^[^:]+://`)
+
+ // Ref: https://github.com/git/git/blob/master/Documentation/urls.txt#L37
+ scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5}):)?(?P<path>[^\\].*)$`)
)
// MatchesScheme returns true if the given string matches a URL-like
diff --git a/internal/url/url_test.go b/internal/url/url_test.go
index d168db6..29c3f3e 100755
--- a/internal/url/url_test.go
+++ b/internal/url/url_test.go
@@ -13,11 +13,27 @@ type URLSuite struct{}
var _ = Suite(&URLSuite{})
func (s *URLSuite) TestMatchesScpLike(c *C) {
+ // See https://github.com/git/git/blob/master/Documentation/urls.txt#L37
examples := []string{
+ // Most-extended case
"git@github.com:james/bond",
- "git@github.com:007/bond",
+ // Most-extended case with port
"git@github.com:22:james/bond",
+ // Most-extended case with numeric path
+ "git@github.com:007/bond",
+ // Most-extended case with port and numeric "username"
"git@github.com:22:007/bond",
+ // Single repo path
+ "git@github.com:bond",
+ // Single repo path with port
+ "git@github.com:22:bond",
+ // Single repo path with port and numeric repo
+ "git@github.com:22:007",
+ // Repo path ending with .git and starting with _
+ "git@github.com:22:_007.git",
+ "git@github.com:_007.git",
+ "git@github.com:_james.git",
+ "git@github.com:_james/bond.git",
}
for _, url := range examples {
@@ -26,35 +42,68 @@ func (s *URLSuite) TestMatchesScpLike(c *C) {
}
func (s *URLSuite) TestFindScpLikeComponents(c *C) {
- url := "git@github.com:james/bond"
- user, host, port, path := FindScpLikeComponents(url)
-
- c.Check(user, Equals, "git")
- c.Check(host, Equals, "github.com")
- c.Check(port, Equals, "")
- c.Check(path, Equals, "james/bond")
-
- url = "git@github.com:007/bond"
- user, host, port, path = FindScpLikeComponents(url)
-
- c.Check(user, Equals, "git")
- c.Check(host, Equals, "github.com")
- c.Check(port, Equals, "")
- c.Check(path, Equals, "007/bond")
-
- url = "git@github.com:22:james/bond"
- user, host, port, path = FindScpLikeComponents(url)
+ testCases := []struct {
+ url, user, host, port, path string
+ }{
+ {
+ // Most-extended case
+ url: "git@github.com:james/bond", user: "git", host: "github.com", port: "", path: "james/bond",
+ },
+ {
+ // Most-extended case with port
+ url: "git@github.com:22:james/bond", user: "git", host: "github.com", port: "22", path: "james/bond",
+ },
+ {
+ // Most-extended case with numeric path
+ url: "git@github.com:007/bond", user: "git", host: "github.com", port: "", path: "007/bond",
+ },
+ {
+ // Most-extended case with port and numeric path
+ url: "git@github.com:22:007/bond", user: "git", host: "github.com", port: "22", path: "007/bond",
+ },
+ {
+ // Single repo path
+ url: "git@github.com:bond", user: "git", host: "github.com", port: "", path: "bond",
+ },
+ {
+ // Single repo path with port
+ url: "git@github.com:22:bond", user: "git", host: "github.com", port: "22", path: "bond",
+ },
+ {
+ // Single repo path with port and numeric path
+ url: "git@github.com:22:007", user: "git", host: "github.com", port: "22", path: "007",
+ },
+ {
+ // Repo path ending with .git and starting with _
+ url: "git@github.com:22:_007.git", user: "git", host: "github.com", port: "22", path: "_007.git",
+ },
+ {
+ // Repo path ending with .git and starting with _
+ url: "git@github.com:_007.git", user: "git", host: "github.com", port: "", path: "_007.git",
+ },
+ {
+ // Repo path ending with .git and starting with _
+ url: "git@github.com:_james.git", user: "git", host: "github.com", port: "", path: "_james.git",
+ },
+ {
+ // Repo path ending with .git and starting with _
+ url: "git@github.com:_james/bond.git", user: "git", host: "github.com", port: "", path: "_james/bond.git",
+ },
+ }
- c.Check(user, Equals, "git")
- c.Check(host, Equals, "github.com")
- c.Check(port, Equals, "22")
- c.Check(path, Equals, "james/bond")
+ for _, tc := range testCases {
+ user, host, port, path := FindScpLikeComponents(tc.url)
- url = "git@github.com:22:007/bond"
- user, host, port, path = FindScpLikeComponents(url)
+ logf := func(ok bool) {
+ if ok {
+ return
+ }
+ c.Logf("%q check failed", tc.url)
+ }
- c.Check(user, Equals, "git")
- c.Check(host, Equals, "github.com")
- c.Check(port, Equals, "22")
- c.Check(path, Equals, "007/bond")
+ logf(c.Check(user, Equals, tc.user))
+ logf(c.Check(host, Equals, tc.host))
+ logf(c.Check(port, Equals, tc.port))
+ logf(c.Check(path, Equals, tc.path))
+ }
}
diff --git a/options.go b/options.go
index 738c1ce..6a38ad9 100644
--- a/options.go
+++ b/options.go
@@ -46,6 +46,14 @@ type CloneOptions struct {
ReferenceName plumbing.ReferenceName
// Fetch only ReferenceName if true.
SingleBranch bool
+ // Mirror clones the repository as a mirror.
+ //
+ // Compared to a bare clone, mirror not only maps local branches of the
+ // source to local branches of the target, it maps all refs (including
+ // remote-tracking branches, notes etc.) and sets up a refspec configuration
+ // such that all these refs are overwritten by a git remote update in the
+ // target repository.
+ Mirror bool
// No checkout of HEAD after clone if true.
NoCheckout bool
// Limit fetching to the specified number of commits.
diff --git a/plumbing/transport/common_test.go b/plumbing/transport/common_test.go
index 0c5a01a..db11303 100644
--- a/plumbing/transport/common_test.go
+++ b/plumbing/transport/common_test.go
@@ -95,16 +95,28 @@ func (s *SuiteCommon) TestNewEndpointSCPLike(c *C) {
c.Assert(e.String(), Equals, "ssh://git@github.com/user/repository.git")
}
-func (s *SuiteCommon) TestNewEndpointSCPLikeWithPort(c *C) {
+func (s *SuiteCommon) TestNewEndpointSCPLikeWithNumericPath(c *C) {
e, err := NewEndpoint("git@github.com:9999/user/repository.git")
c.Assert(err, IsNil)
c.Assert(e.Protocol, Equals, "ssh")
c.Assert(e.User, Equals, "git")
c.Assert(e.Password, Equals, "")
c.Assert(e.Host, Equals, "github.com")
- c.Assert(e.Port, Equals, 9999)
- c.Assert(e.Path, Equals, "user/repository.git")
- c.Assert(e.String(), Equals, "ssh://git@github.com:9999/user/repository.git")
+ c.Assert(e.Port, Equals, 22)
+ c.Assert(e.Path, Equals, "9999/user/repository.git")
+ c.Assert(e.String(), Equals, "ssh://git@github.com/9999/user/repository.git")
+}
+
+func (s *SuiteCommon) TestNewEndpointSCPLikeWithPort(c *C) {
+ e, err := NewEndpoint("git@github.com:8080:9999/user/repository.git")
+ c.Assert(err, IsNil)
+ c.Assert(e.Protocol, Equals, "ssh")
+ c.Assert(e.User, Equals, "git")
+ c.Assert(e.Password, Equals, "")
+ c.Assert(e.Host, Equals, "github.com")
+ c.Assert(e.Port, Equals, 8080)
+ c.Assert(e.Path, Equals, "9999/user/repository.git")
+ c.Assert(e.String(), Equals, "ssh://git@github.com:8080/9999/user/repository.git")
}
func (s *SuiteCommon) TestNewEndpointFileAbs(c *C) {
diff --git a/repository.go b/repository.go
index 56ae976..e009c5d 100644
--- a/repository.go
+++ b/repository.go
@@ -444,6 +444,9 @@ func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOp
return nil, err
}
+ if o.Mirror {
+ isBare = true
+ }
r, err := PlainInit(path, isBare)
if err != nil {
return nil, err
@@ -851,9 +854,10 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
}
c := &config.RemoteConfig{
- Name: o.RemoteName,
- URLs: []string{o.URL},
- Fetch: r.cloneRefSpec(o),
+ Name: o.RemoteName,
+ URLs: []string{o.URL},
+ Fetch: r.cloneRefSpec(o),
+ Mirror: o.Mirror,
}
if _, err := r.CreateRemote(c); err != nil {
@@ -906,7 +910,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
return err
}
- if ref.Name().IsBranch() {
+ if !o.Mirror && ref.Name().IsBranch() {
branchRef := ref.Name()
branchName := strings.Split(string(branchRef), "refs/heads/")[1]
@@ -937,6 +941,8 @@ const (
func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec {
switch {
+ case o.Mirror:
+ return []config.RefSpec{"+refs/*:refs/*"}
case o.ReferenceName.IsTag():
return []config.RefSpec{
config.RefSpec(fmt.Sprintf(refspecTag, o.ReferenceName.Short())),
diff --git a/repository_test.go b/repository_test.go
index 468ce33..ed3e7e6 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -189,6 +189,35 @@ func (s *RepositorySuite) TestCloneContext(c *C) {
c.Assert(err, Equals, context.Canceled)
}
+func (s *RepositorySuite) TestCloneMirror(c *C) {
+ r, err := Clone(memory.NewStorage(), nil, &CloneOptions{
+ URL: fixtures.Basic().One().URL,
+ Mirror: true,
+ })
+
+ c.Assert(err, IsNil)
+
+ refs, err := r.References()
+ var count int
+ refs.ForEach(func(r *plumbing.Reference) error { c.Log(r); count++; return nil })
+ c.Assert(err, IsNil)
+ // 6 refs total from github.com/git-fixtures/basic.git:
+ // - HEAD
+ // - refs/heads/master
+ // - refs/heads/branch
+ // - refs/pull/1/head
+ // - refs/pull/2/head
+ // - refs/pull/2/merge
+ c.Assert(count, Equals, 6)
+
+ cfg, err := r.Config()
+ c.Assert(err, IsNil)
+
+ c.Assert(cfg.Core.IsBare, Equals, true)
+ c.Assert(cfg.Remotes[DefaultRemoteName].Validate(), IsNil)
+ c.Assert(cfg.Remotes[DefaultRemoteName].Mirror, Equals, true)
+}
+
func (s *RepositorySuite) TestCloneWithTags(c *C) {
url := s.GetLocalRepositoryURL(
fixtures.ByURL("https://github.com/git-fixtures/tags.git").One(),