Merge pull request #501 from smola/config-multiple-urls

config: multiple values in RemoteConfig (URLs and Fetch)
diff --git a/_examples/remotes/main.go b/_examples/remotes/main.go
index 90817dc..7cae8bb 100644
--- a/_examples/remotes/main.go
+++ b/_examples/remotes/main.go
@@ -27,7 +27,7 @@
 	Info("git remote add example https://github.com/git-fixtures/basic.git")
 	_, err = r.CreateRemote(&config.RemoteConfig{
 		Name: "example",
-		URL:  "https://github.com/git-fixtures/basic.git",
+		URLs: []string{"https://github.com/git-fixtures/basic.git"},
 	})
 
 	CheckIfError(err)
diff --git a/config/config.go b/config/config.go
index bcea63e..cb10738 100644
--- a/config/config.go
+++ b/config/config.go
@@ -159,13 +159,22 @@
 
 func (c *Config) marshalRemotes() {
 	s := c.Raw.Section(remoteSection)
-	s.Subsections = make(format.Subsections, len(c.Remotes))
-
-	var i int
-	for _, r := range c.Remotes {
-		s.Subsections[i] = r.marshal()
-		i++
+	newSubsections := make(format.Subsections, 0, len(c.Remotes))
+	added := make(map[string]bool)
+	for _, subsection := range s.Subsections {
+		if remote, ok := c.Remotes[subsection.Name]; ok {
+			newSubsections = append(newSubsections, remote.marshal())
+			added[subsection.Name] = true
+		}
 	}
+
+	for name, remote := range c.Remotes {
+		if !added[name] {
+			newSubsections = append(newSubsections, remote.marshal())
+		}
+	}
+
+	s.Subsections = newSubsections
 }
 
 func (c *Config) marshalSubmodules() {
@@ -187,8 +196,9 @@
 type RemoteConfig struct {
 	// Name of the remote
 	Name string
-	// URL the URL of a remote repository
-	URL string
+	// 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
 	// Fetch the default set of "refspec" for fetch operation
 	Fetch []RefSpec
 
@@ -203,7 +213,7 @@
 		return ErrRemoteConfigEmptyName
 	}
 
-	if c.URL == "" {
+	if len(c.URLs) == 0 {
 		return ErrRemoteConfigEmptyURL
 	}
 
@@ -233,8 +243,13 @@
 		fetch = append(fetch, rs)
 	}
 
+	var urls []string
+	for _, f := range c.raw.Options.GetAll(urlKey) {
+		urls = append(urls, f)
+	}
+
 	c.Name = c.raw.Name
-	c.URL = c.raw.Option(urlKey)
+	c.URLs = urls
 	c.Fetch = fetch
 
 	return nil
@@ -246,9 +261,21 @@
 	}
 
 	c.raw.Name = c.Name
-	c.raw.SetOption(urlKey, c.URL)
-	for _, rs := range c.Fetch {
-		c.raw.SetOption(fetchKey, rs.String())
+	if len(c.URLs) == 0 {
+		c.raw.RemoveOption(urlKey)
+	} else {
+		c.raw.SetOption(urlKey, c.URLs...)
+	}
+
+	if len(c.Fetch) == 0 {
+		c.raw.RemoveOption(fetchKey)
+	} else {
+		var values []string
+		for _, rs := range c.Fetch {
+			values = append(values, rs.String())
+		}
+
+		c.raw.SetOption(fetchKey, values...)
 	}
 
 	return c.raw
diff --git a/config/config_test.go b/config/config_test.go
index cfab36d..97f4bbf 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -13,6 +13,11 @@
 [remote "origin"]
         url = git@github.com:mcuadros/go-git.git
         fetch = +refs/heads/*:refs/remotes/origin/*
+[remote "alt"]
+		url = git@github.com:mcuadros/go-git.git
+		url = git@github.com:src-d/go-git.git
+		fetch = +refs/heads/*:refs/remotes/origin/*
+		fetch = +refs/pull/*:refs/remotes/origin/pull/*
 [submodule "qux"]
         path = qux
         url = https://github.com/foo/qux.git
@@ -28,10 +33,13 @@
 
 	c.Assert(cfg.Core.IsBare, Equals, true)
 	c.Assert(cfg.Core.Worktree, Equals, "foo")
-	c.Assert(cfg.Remotes, HasLen, 1)
+	c.Assert(cfg.Remotes, HasLen, 2)
 	c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
-	c.Assert(cfg.Remotes["origin"].URL, Equals, "git@github.com:mcuadros/go-git.git")
+	c.Assert(cfg.Remotes["origin"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git"})
 	c.Assert(cfg.Remotes["origin"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*"})
+	c.Assert(cfg.Remotes["alt"].Name, Equals, "alt")
+	c.Assert(cfg.Remotes["alt"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"})
+	c.Assert(cfg.Remotes["alt"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"})
 	c.Assert(cfg.Submodules, HasLen, 1)
 	c.Assert(cfg.Submodules["qux"].Name, Equals, "qux")
 	c.Assert(cfg.Submodules["qux"].URL, Equals, "https://github.com/foo/qux.git")
@@ -45,6 +53,11 @@
 	worktree = bar
 [remote "origin"]
 	url = git@github.com:mcuadros/go-git.git
+[remote "alt"]
+	url = git@github.com:mcuadros/go-git.git
+	url = git@github.com:src-d/go-git.git
+	fetch = +refs/heads/*:refs/remotes/origin/*
+	fetch = +refs/pull/*:refs/remotes/origin/pull/*
 [submodule "qux"]
 	url = https://github.com/foo/qux.git
 `)
@@ -54,7 +67,13 @@
 	cfg.Core.Worktree = "bar"
 	cfg.Remotes["origin"] = &RemoteConfig{
 		Name: "origin",
-		URL:  "git@github.com:mcuadros/go-git.git",
+		URLs: []string{"git@github.com:mcuadros/go-git.git"},
+	}
+
+	cfg.Remotes["alt"] = &RemoteConfig{
+		Name:  "alt",
+		URLs:  []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"},
+		Fetch: []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"},
 	}
 
 	cfg.Submodules["qux"] = &Submodule{
@@ -88,7 +107,7 @@
 
 	output, err := cfg.Marshal()
 	c.Assert(err, IsNil)
-	c.Assert(output, DeepEquals, input)
+	c.Assert(string(output), DeepEquals, string(input))
 }
 
 func (s *ConfigSuite) TestValidateInvalidRemote(c *C) {
@@ -122,7 +141,7 @@
 }
 
 func (s *ConfigSuite) TestRemoteConfigValidateDefault(c *C) {
-	config := &RemoteConfig{Name: "foo", URL: "http://foo/bar"}
+	config := &RemoteConfig{Name: "foo", URLs: []string{"http://foo/bar"}}
 	c.Assert(config.Validate(), IsNil)
 
 	fetch := config.Fetch
diff --git a/example_test.go b/example_test.go
index 585b38a..1b369ba 100644
--- a/example_test.go
+++ b/example_test.go
@@ -91,7 +91,7 @@
 	// Add a new remote, with the default fetch refspec
 	_, err := r.CreateRemote(&config.RemoteConfig{
 		Name: "example",
-		URL:  "https://github.com/git-fixtures/basic.git",
+		URLs: []string{"https://github.com/git-fixtures/basic.git"},
 	})
 
 	if err != nil {
diff --git a/plumbing/format/config/decoder_test.go b/plumbing/format/config/decoder_test.go
index 412549f..0a8e92c 100644
--- a/plumbing/format/config/decoder_test.go
+++ b/plumbing/format/config/decoder_test.go
@@ -17,7 +17,10 @@
 		cfg := &Config{}
 		err := d.Decode(cfg)
 		c.Assert(err, IsNil, Commentf("decoder error for fixture: %d", idx))
-		c.Assert(cfg, DeepEquals, fixture.Config, Commentf("bad result for fixture: %d", idx))
+		buf := bytes.NewBuffer(nil)
+		e := NewEncoder(buf)
+		_ = e.Encode(cfg)
+		c.Assert(cfg, DeepEquals, fixture.Config, Commentf("bad result for fixture: %d, %s", idx, buf.String()))
 	}
 }
 
diff --git a/plumbing/format/config/fixtures_test.go b/plumbing/format/config/fixtures_test.go
index 12ff288..f3533df 100644
--- a/plumbing/format/config/fixtures_test.go
+++ b/plumbing/format/config/fixtures_test.go
@@ -87,4 +87,18 @@
 			AddOption("sect1", "subsect1", "opt2", "value2b").
 			AddOption("sect1", "subsect2", "opt2", "value2"),
 	},
+	{
+		Raw: `
+			[sect1]
+			opt1 = value1
+			opt1 = value2
+			`,
+		Text: `[sect1]
+	opt1 = value1
+	opt1 = value2
+`,
+		Config: New().
+			AddOption("sect1", "", "opt1", "value1").
+			AddOption("sect1", "", "opt1", "value2"),
+	},
 }
diff --git a/plumbing/format/config/option.go b/plumbing/format/config/option.go
index 3c391c6..d4775e4 100644
--- a/plumbing/format/config/option.go
+++ b/plumbing/format/config/option.go
@@ -1,6 +1,7 @@
 package config
 
 import (
+	"fmt"
 	"strings"
 )
 
@@ -21,6 +22,15 @@
 	return strings.ToLower(o.Key) == strings.ToLower(key)
 }
 
+func (opts Options) GoString() string {
+	var strs []string
+	for _, opt := range opts {
+		strs = append(strs, fmt.Sprintf("%#v", opt))
+	}
+
+	return strings.Join(strs, ", ")
+}
+
 // Get gets the value for the given key if set,
 // otherwise it returns the empty string.
 //
@@ -69,16 +79,39 @@
 	return append(opts, &Option{key, value})
 }
 
-func (opts Options) withSettedOption(key string, value string) Options {
-	for i := len(opts) - 1; i >= 0; i-- {
-		o := opts[i]
-		if o.IsKey(key) {
-			result := make(Options, len(opts))
-			copy(result, opts)
-			result[i] = &Option{key, value}
-			return result
+func (opts Options) withSettedOption(key string, values ...string) Options {
+	var result Options
+	var added []string
+	for _, o := range opts {
+		if !o.IsKey(key) {
+			result = append(result, o)
+			continue
+		}
+
+		if contains(values, o.Value) {
+			added = append(added, o.Value)
+			result = append(result, o)
+			continue
 		}
 	}
 
-	return opts.withAddedOption(key, value)
+	for _, value := range values {
+		if contains(added, value) {
+			continue
+		}
+
+		result = result.withAddedOption(key, value)
+	}
+
+	return result
+}
+
+func contains(haystack []string, needle string) bool {
+	for _, s := range haystack {
+		if s == needle {
+			return true
+		}
+	}
+
+	return false
 }
diff --git a/plumbing/format/config/section.go b/plumbing/format/config/section.go
index 547da1e..4a17e3b 100644
--- a/plumbing/format/config/section.go
+++ b/plumbing/format/config/section.go
@@ -1,6 +1,9 @@
 package config
 
-import "strings"
+import (
+	"fmt"
+	"strings"
+)
 
 // Section is the representation of a section inside git configuration files.
 // Each Section contains Options that are used by both the Git plumbing
@@ -36,8 +39,26 @@
 
 type Sections []*Section
 
+func (s Sections) GoString() string {
+	var strs []string
+	for _, ss := range s {
+		strs = append(strs, fmt.Sprintf("%#v", ss))
+	}
+
+	return strings.Join(strs, ", ")
+}
+
 type Subsections []*Subsection
 
+func (s Subsections) GoString() string {
+	var strs []string
+	for _, ss := range s {
+		strs = append(strs, fmt.Sprintf("%#v", ss))
+	}
+
+	return strings.Join(strs, ", ")
+}
+
 // IsName checks if the name provided is equals to the Section name, case insensitive.
 func (s *Section) IsName(name string) bool {
 	return strings.ToLower(s.Name) == strings.ToLower(name)
@@ -113,8 +134,8 @@
 
 // SetOption adds a new Option to the Subsection. If the option already exists, is replaced.
 // The updated Subsection is returned.
-func (s *Subsection) SetOption(key string, value string) *Subsection {
-	s.Options = s.Options.withSettedOption(key, value)
+func (s *Subsection) SetOption(key string, value ...string) *Subsection {
+	s.Options = s.Options.withSettedOption(key, value...)
 	return s
 }
 
diff --git a/plumbing/format/config/section_test.go b/plumbing/format/config/section_test.go
index cfd9f3f..0290386 100644
--- a/plumbing/format/config/section_test.go
+++ b/plumbing/format/config/section_test.go
@@ -69,3 +69,22 @@
 	}
 	c.Assert(sect.RemoveOption("key1"), DeepEquals, expected)
 }
+
+func (s *SectionSuite) TestSubsection_SetOption(c *C) {
+	sect := &Subsection{
+		Options: []*Option{
+			{Key: "key1", Value: "value1"},
+			{Key: "key2", Value: "value2"},
+			{Key: "key1", Value: "value3"},
+		},
+	}
+
+	expected := &Subsection{
+		Options: []*Option{
+			{Key: "key1", Value: "value1"},
+			{Key: "key2", Value: "value2"},
+			{Key: "key1", Value: "value4"},
+		},
+	}
+	c.Assert(sect.SetOption("key1", "value1", "value4"), DeepEquals, expected)
+}
diff --git a/remote.go b/remote.go
index fe44009..eb5a6a2 100644
--- a/remote.go
+++ b/remote.go
@@ -43,8 +43,11 @@
 }
 
 func (r *Remote) String() string {
-	fetch := r.c.URL
-	push := r.c.URL
+	var fetch, push string
+	if len(r.c.URLs) > 0 {
+		fetch = r.c.URLs[0]
+		push = r.c.URLs[0]
+	}
 
 	return fmt.Sprintf("%s\t%s (fetch)\n%[1]s\t%[3]s (push)", r.c.Name, fetch, push)
 }
@@ -71,7 +74,7 @@
 		return fmt.Errorf("remote names don't match: %s != %s", o.RemoteName, r.c.Name)
 	}
 
-	s, err := newSendPackSession(r.c.URL, o.Auth)
+	s, err := newSendPackSession(r.c.URLs[0], o.Auth)
 	if err != nil {
 		return err
 	}
@@ -211,7 +214,7 @@
 		o.RefSpecs = r.c.Fetch
 	}
 
-	s, err := newUploadPackSession(r.c.URL, o.Auth)
+	s, err := newUploadPackSession(r.c.URLs[0], o.Auth)
 	if err != nil {
 		return nil, err
 	}
diff --git a/remote_test.go b/remote_test.go
index ece052a..4953b12 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -26,25 +26,25 @@
 var _ = Suite(&RemoteSuite{})
 
 func (s *RemoteSuite) TestFetchInvalidEndpoint(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "http://\\"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://\\"}})
 	err := r.Fetch(&FetchOptions{RemoteName: "foo"})
 	c.Assert(err, ErrorMatches, ".*invalid character.*")
 }
 
 func (s *RemoteSuite) TestFetchNonExistentEndpoint(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "ssh://non-existent/foo.git"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"ssh://non-existent/foo.git"}})
 	err := r.Fetch(&FetchOptions{})
 	c.Assert(err, NotNil)
 }
 
 func (s *RemoteSuite) TestFetchInvalidSchemaEndpoint(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "qux://foo"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}})
 	err := r.Fetch(&FetchOptions{})
 	c.Assert(err, ErrorMatches, ".*unsupported scheme.*")
 }
 
 func (s *RemoteSuite) TestFetchInvalidFetchOptions(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "qux://foo"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}})
 	invalid := config.RefSpec("^*$ñ")
 	err := r.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{invalid}})
 	c.Assert(err, Equals, config.ErrRefSpecMalformedSeparator)
@@ -52,7 +52,7 @@
 
 func (s *RemoteSuite) TestFetchWildcard(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 
 	s.testFetch(c, r, &FetchOptions{
@@ -68,7 +68,7 @@
 
 func (s *RemoteSuite) TestFetchWildcardTags(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetLocalRepositoryURL(fixtures.ByTag("tags").One()),
+		URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
 	})
 
 	s.testFetch(c, r, &FetchOptions{
@@ -87,7 +87,7 @@
 
 func (s *RemoteSuite) TestFetch(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetLocalRepositoryURL(fixtures.ByTag("tags").One()),
+		URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
 	})
 
 	s.testFetch(c, r, &FetchOptions{
@@ -101,7 +101,7 @@
 
 func (s *RemoteSuite) TestFetchContext(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetLocalRepositoryURL(fixtures.ByTag("tags").One()),
+		URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
 	})
 
 	ctx, cancel := context.WithCancel(context.Background())
@@ -113,12 +113,11 @@
 		},
 	})
 	c.Assert(err, NotNil)
-
 }
 
 func (s *RemoteSuite) TestFetchWithAllTags(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetLocalRepositoryURL(fixtures.ByTag("tags").One()),
+		URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
 	})
 
 	s.testFetch(c, r, &FetchOptions{
@@ -138,7 +137,7 @@
 
 func (s *RemoteSuite) TestFetchWithNoTags(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetLocalRepositoryURL(fixtures.ByTag("tags").One()),
+		URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
 	})
 
 	s.testFetch(c, r, &FetchOptions{
@@ -154,7 +153,7 @@
 
 func (s *RemoteSuite) TestFetchWithDepth(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 
 	s.testFetch(c, r, &FetchOptions{
@@ -193,7 +192,7 @@
 	sto := memory.NewStorage()
 	buf := bytes.NewBuffer(nil)
 
-	r := newRemote(sto, &config.RemoteConfig{Name: "foo", URL: url})
+	r := newRemote(sto, &config.RemoteConfig{Name: "foo", URLs: []string{url}})
 
 	refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*")
 	err := r.Fetch(&FetchOptions{
@@ -229,7 +228,7 @@
 	mock := &mockPackfileWriter{Storer: fss}
 
 	url := s.GetBasicLocalRepositoryURL()
-	r := newRemote(mock, &config.RemoteConfig{Name: "foo", URL: url})
+	r := newRemote(mock, &config.RemoteConfig{Name: "foo", URLs: []string{url}})
 
 	refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*")
 	err = r.Fetch(&FetchOptions{
@@ -258,7 +257,7 @@
 
 func (s *RemoteSuite) TestFetchNoErrAlreadyUpToDateButStillUpdateLocalRemoteRefs(c *C) {
 	r := newRemote(memory.NewStorage(), &config.RemoteConfig{
-		URL: s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 
 	o := &FetchOptions{
@@ -294,7 +293,7 @@
 }
 
 func (s *RemoteSuite) doTestFetchNoErrAlreadyUpToDate(c *C, url string) {
-	r := newRemote(memory.NewStorage(), &config.RemoteConfig{URL: url})
+	r := newRemote(memory.NewStorage(), &config.RemoteConfig{URLs: []string{url}})
 
 	o := &FetchOptions{
 		RefSpecs: []config.RefSpec{
@@ -311,7 +310,7 @@
 func (s *RemoteSuite) TestString(c *C) {
 	r := newRemote(nil, &config.RemoteConfig{
 		Name: "foo",
-		URL:  "https://github.com/git-fixtures/basic.git",
+		URLs: []string{"https://github.com/git-fixtures/basic.git"},
 	})
 
 	c.Assert(r.String(), Equals, ""+
@@ -331,7 +330,7 @@
 
 	r := newRemote(sto, &config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  url,
+		URLs: []string{url},
 	})
 
 	rs := config.RefSpec("refs/heads/*:refs/heads/*")
@@ -369,7 +368,7 @@
 
 	r := newRemote(sto, &config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  url,
+		URLs: []string{url},
 	})
 
 	ctx, cancel := context.WithCancel(context.Background())
@@ -392,7 +391,7 @@
 
 	r := newRemote(sto, &config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  url,
+		URLs: []string{url},
 	})
 
 	err = r.Push(&PushOptions{
@@ -416,7 +415,7 @@
 
 	r := newRemote(sto, &config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  fs.Root(),
+		URLs: []string{fs.Root()},
 	})
 
 	err = r.Push(&PushOptions{
@@ -490,7 +489,7 @@
 	url := dstFs.Root()
 	r := newRemote(sto, &config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  url,
+		URLs: []string{url},
 	})
 
 	oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
@@ -540,25 +539,25 @@
 }
 
 func (s *RemoteSuite) TestPushInvalidEndpoint(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "http://\\"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://\\"}})
 	err := r.Push(&PushOptions{RemoteName: "foo"})
 	c.Assert(err, ErrorMatches, ".*invalid character.*")
 }
 
 func (s *RemoteSuite) TestPushNonExistentEndpoint(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "ssh://non-existent/foo.git"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"ssh://non-existent/foo.git"}})
 	err := r.Push(&PushOptions{})
 	c.Assert(err, NotNil)
 }
 
 func (s *RemoteSuite) TestPushInvalidSchemaEndpoint(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "origin", URL: "qux://foo"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"qux://foo"}})
 	err := r.Push(&PushOptions{})
 	c.Assert(err, ErrorMatches, ".*unsupported scheme.*")
 }
 
 func (s *RemoteSuite) TestPushInvalidFetchOptions(c *C) {
-	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "qux://foo"})
+	r := newRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}})
 	invalid := config.RefSpec("^*$ñ")
 	err := r.Push(&PushOptions{RefSpecs: []config.RefSpec{invalid}})
 	c.Assert(err, Equals, config.ErrRefSpecMalformedSeparator)
@@ -567,7 +566,7 @@
 func (s *RemoteSuite) TestPushInvalidRefSpec(c *C) {
 	r := newRemote(nil, &config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  "some-url",
+		URLs: []string{"some-url"},
 	})
 
 	rs := config.RefSpec("^*$**")
@@ -580,7 +579,7 @@
 func (s *RemoteSuite) TestPushWrongRemoteName(c *C) {
 	r := newRemote(nil, &config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  "some-url",
+		URLs: []string{"some-url"},
 	})
 
 	err := r.Push(&PushOptions{
diff --git a/repository.go b/repository.go
index 7f5b793..92174d6 100644
--- a/repository.go
+++ b/repository.go
@@ -408,7 +408,7 @@
 
 	c := &config.RemoteConfig{
 		Name: o.RemoteName,
-		URL:  o.URL,
+		URLs: []string{o.URL},
 	}
 
 	if _, err := r.CreateRemote(c); err != nil {
diff --git a/repository_test.go b/repository_test.go
index 558149b..00ce120 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -181,7 +181,7 @@
 	r, _ := Init(memory.NewStorage(), nil)
 	remote, err := r.CreateRemote(&config.RemoteConfig{
 		Name: "foo",
-		URL:  "http://foo/foo.git",
+		URLs: []string{"http://foo/foo.git"},
 	})
 
 	c.Assert(err, IsNil)
@@ -205,7 +205,7 @@
 	r, _ := Init(memory.NewStorage(), nil)
 	_, err := r.CreateRemote(&config.RemoteConfig{
 		Name: "foo",
-		URL:  "http://foo/foo.git",
+		URLs: []string{"http://foo/foo.git"},
 	})
 
 	c.Assert(err, IsNil)
@@ -426,7 +426,7 @@
 	r, _ := Init(memory.NewStorage(), nil)
 	_, err := r.CreateRemote(&config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 	c.Assert(err, IsNil)
 	c.Assert(r.Fetch(&FetchOptions{}), IsNil)
@@ -449,7 +449,7 @@
 	r, _ := Init(memory.NewStorage(), nil)
 	_, err := r.CreateRemote(&config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 	c.Assert(err, IsNil)
 
@@ -531,7 +531,7 @@
 	c.Assert(cfg.Core.IsBare, Equals, true)
 	c.Assert(cfg.Remotes, HasLen, 1)
 	c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
-	c.Assert(cfg.Remotes["origin"].URL, Not(Equals), "")
+	c.Assert(cfg.Remotes["origin"].URLs, HasLen, 1)
 }
 
 func (s *RepositorySuite) TestCloneSingleBranchAndNonHEAD(c *C) {
@@ -629,7 +629,7 @@
 
 	_, err = s.Repository.CreateRemote(&config.RemoteConfig{
 		Name: "test",
-		URL:  url,
+		URLs: []string{url},
 	})
 	c.Assert(err, IsNil)
 
@@ -657,7 +657,7 @@
 
 	_, err = s.Repository.CreateRemote(&config.RemoteConfig{
 		Name: "foo",
-		URL:  url,
+		URLs: []string{url},
 	})
 	c.Assert(err, IsNil)
 
diff --git a/storage/filesystem/config_test.go b/storage/filesystem/config_test.go
index 1b812e6..4226b33 100644
--- a/storage/filesystem/config_test.go
+++ b/storage/filesystem/config_test.go
@@ -5,6 +5,7 @@
 	"os"
 
 	"github.com/src-d/go-git-fixtures"
+	"gopkg.in/src-d/go-git.v4/config"
 	"gopkg.in/src-d/go-git.v4/storage/filesystem/internal/dotgit"
 
 	. "gopkg.in/check.v1"
@@ -39,9 +40,8 @@
 	c.Assert(remotes, HasLen, 1)
 	remote := remotes["origin"]
 	c.Assert(remote.Name, Equals, "origin")
-	c.Assert(remote.URL, Equals, "https://github.com/git-fixtures/basic")
-	c.Assert(remote.Fetch, HasLen, 1)
-	c.Assert(remote.Fetch[0].String(), Equals, "+refs/heads/*:refs/remotes/origin/*")
+	c.Assert(remote.URLs, DeepEquals, []string{"https://github.com/git-fixtures/basic"})
+	c.Assert(remote.Fetch, DeepEquals, []config.RefSpec{config.RefSpec("+refs/heads/*:refs/remotes/origin/*")})
 }
 
 func (s *ConfigSuite) TearDownTest(c *C) {
diff --git a/storage/test/storage_suite.go b/storage/test/storage_suite.go
index 624dc57..bf93515 100644
--- a/storage/test/storage_suite.go
+++ b/storage/test/storage_suite.go
@@ -351,7 +351,7 @@
 	expected.Core.IsBare = true
 	expected.Remotes["foo"] = &config.RemoteConfig{
 		Name: "foo",
-		URL:  "http://foo/bar.git",
+		URLs: []string{"http://foo/bar.git"},
 	}
 
 	err := s.Storer.SetConfig(expected)
diff --git a/submodule.go b/submodule.go
index b5de41f..fd3d173 100644
--- a/submodule.go
+++ b/submodule.go
@@ -130,7 +130,7 @@
 
 	_, err = r.CreateRemote(&config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  s.c.URL,
+		URLs: []string{s.c.URL},
 	})
 
 	return r, err
diff --git a/worktree_test.go b/worktree_test.go
index 10774a4..c14d3bc 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -37,7 +37,7 @@
 	r, _ := Init(memory.NewStorage(), fs)
 	r.CreateRemote(&config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 
 	w, err := r.Worktree()
@@ -115,7 +115,7 @@
 	r, _ := Init(memory.NewStorage(), memfs.New())
 	r.CreateRemote(&config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 
 	err := r.Fetch(&FetchOptions{})
@@ -173,7 +173,7 @@
 
 	r.CreateRemote(&config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  s.GetBasicLocalRepositoryURL(),
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
 	})
 
 	w, err := r.Worktree()
@@ -198,7 +198,7 @@
 	r, _ := PlainInit(dir, false)
 	r.CreateRemote(&config.RemoteConfig{
 		Name: DefaultRemoteName,
-		URL:  path,
+		URLs: []string{path},
 	})
 
 	w, err := r.Worktree()