diff options
-rw-r--r-- | _examples/remotes/main.go | 2 | ||||
-rw-r--r-- | config/config.go | 51 | ||||
-rw-r--r-- | config/config_test.go | 29 | ||||
-rw-r--r-- | example_test.go | 2 | ||||
-rw-r--r-- | plumbing/format/config/decoder_test.go | 5 | ||||
-rw-r--r-- | plumbing/format/config/fixtures_test.go | 14 | ||||
-rw-r--r-- | plumbing/format/config/option.go | 51 | ||||
-rw-r--r-- | plumbing/format/config/section.go | 27 | ||||
-rw-r--r-- | plumbing/format/config/section_test.go | 19 | ||||
-rw-r--r-- | plumbing/reference.go | 40 | ||||
-rw-r--r-- | plumbing/reference_test.go | 8 | ||||
-rw-r--r-- | plumbing/transport/file/receive_pack_test.go | 2 | ||||
-rw-r--r-- | plumbing/transport/test/receive_pack.go | 1 | ||||
-rw-r--r-- | remote.go | 15 | ||||
-rw-r--r-- | remote_test.go | 57 | ||||
-rw-r--r-- | repository.go | 41 | ||||
-rw-r--r-- | repository_test.go | 54 | ||||
-rw-r--r-- | storage/filesystem/config_test.go | 6 | ||||
-rw-r--r-- | storage/test/storage_suite.go | 2 | ||||
-rw-r--r-- | submodule.go | 2 | ||||
-rw-r--r-- | utils/merkletrie/noder/path.go | 8 | ||||
-rw-r--r-- | utils/merkletrie/noder/path_test.go | 12 | ||||
-rw-r--r-- | worktree.go | 6 | ||||
-rw-r--r-- | worktree_test.go | 66 |
24 files changed, 376 insertions, 144 deletions
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 @@ func main() { 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) marshalCore() { func (c *Config) marshalRemotes() { s := c.Raw.Section(remoteSection) - s.Subsections = make(format.Subsections, len(c.Remotes)) + 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 + } + } - var i int - for _, r := range c.Remotes { - s.Subsections[i] = r.marshal() - i++ + 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 @@ func (c *Config) marshalSubmodules() { 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 @@ func (c *RemoteConfig) Validate() error { return ErrRemoteConfigEmptyName } - if c.URL == "" { + if len(c.URLs) == 0 { return ErrRemoteConfigEmptyURL } @@ -233,8 +243,13 @@ func (c *RemoteConfig) unmarshal(s *format.Subsection) error { 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 @@ func (c *RemoteConfig) marshal() *format.Subsection { } 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 @@ func (s *ConfigSuite) TestUnmarshall(c *C) { [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 @@ func (s *ConfigSuite) TestUnmarshall(c *C) { 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 @@ func (s *ConfigSuite) TestMarshall(c *C) { 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 @@ func (s *ConfigSuite) TestMarshall(c *C) { 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 @@ func (s *ConfigSuite) TestUnmarshallMarshall(c *C) { 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) TestRemoteConfigValidateMissingName(c *C) { } 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 @@ func ExampleRepository_CreateRemote() { // 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 @@ func (s *DecoderSuite) TestDecode(c *C) { 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 @@ var fixtures = []*Fixture{ 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 @@ func (o *Option) IsKey(key string) bool { 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 @@ func (opts Options) withAddedOption(key string, value string) Options { 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 + } + } + + 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 opts.withAddedOption(key, value) + 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 Subsection struct { 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 @@ func (s *Subsection) AddOption(key string, value string) *Subsection { // 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 @@ func (s *SectionSuite) TestSubsection_RemoveOption(c *C) { } 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/plumbing/reference.go b/plumbing/reference.go index 5d477b9..2c30fe0 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -55,6 +55,26 @@ func (r ReferenceType) String() string { // ReferenceName reference name's type ReferenceName string +// IsBranch check if a reference is a branch +func (r ReferenceName) IsBranch() bool { + return strings.HasPrefix(string(r), refHeadPrefix) +} + +// IsNote check if a reference is a note +func (r ReferenceName) IsNote() bool { + return strings.HasPrefix(string(r), refNotePrefix) +} + +// IsRemote check if a reference is a remote +func (r ReferenceName) IsRemote() bool { + return strings.HasPrefix(string(r), refRemotePrefix) +} + +// IsTag check if a reference is a tag +func (r ReferenceName) IsTag() bool { + return strings.HasPrefix(string(r), refTagPrefix) +} + func (r ReferenceName) String() string { return string(r) } @@ -138,26 +158,6 @@ func (r *Reference) Target() ReferenceName { return r.target } -// IsBranch check if a reference is a branch -func (r *Reference) IsBranch() bool { - return strings.HasPrefix(string(r.n), refHeadPrefix) -} - -// IsNote check if a reference is a note -func (r *Reference) IsNote() bool { - return strings.HasPrefix(string(r.n), refNotePrefix) -} - -// IsRemote check if a reference is a remote -func (r *Reference) IsRemote() bool { - return strings.HasPrefix(string(r.n), refRemotePrefix) -} - -// IsTag check if a reference is a tag -func (r *Reference) IsTag() bool { - return strings.HasPrefix(string(r.n), refTagPrefix) -} - // Strings dump a reference as a [2]string func (r *Reference) Strings() [2]string { var o [2]string diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go index 97c8772..47919ef 100644 --- a/plumbing/reference_test.go +++ b/plumbing/reference_test.go @@ -55,21 +55,21 @@ func (s *ReferenceSuite) TestNewHashReference(c *C) { } func (s *ReferenceSuite) TestIsBranch(c *C) { - r := NewHashReference(ExampleReferenceName, ZeroHash) + r := ExampleReferenceName c.Assert(r.IsBranch(), Equals, true) } func (s *ReferenceSuite) TestIsNote(c *C) { - r := NewHashReference(ReferenceName("refs/notes/foo"), ZeroHash) + r := ReferenceName("refs/notes/foo") c.Assert(r.IsNote(), Equals, true) } func (s *ReferenceSuite) TestIsRemote(c *C) { - r := NewHashReference(ReferenceName("refs/remotes/origin/master"), ZeroHash) + r := ReferenceName("refs/remotes/origin/master") c.Assert(r.IsRemote(), Equals, true) } func (s *ReferenceSuite) TestIsTag(c *C) { - r := NewHashReference(ReferenceName("refs/tags/v3.1."), ZeroHash) + r := ReferenceName("refs/tags/v3.1.") c.Assert(r.IsTag(), Equals, true) } diff --git a/plumbing/transport/file/receive_pack_test.go b/plumbing/transport/file/receive_pack_test.go index ab21aea..a7dc399 100644 --- a/plumbing/transport/file/receive_pack_test.go +++ b/plumbing/transport/file/receive_pack_test.go @@ -70,6 +70,6 @@ func (s *ReceivePackSuite) TestNonExistentCommand(c *C) { cmd := "/non-existent-git" client := NewClient(cmd, cmd) session, err := client.NewReceivePackSession(s.Endpoint, s.EmptyAuth) - c.Assert(err, ErrorMatches, ".*no such file or directory.*") + c.Assert(err, ErrorMatches, ".*(no such file or directory.*|.*file does not exist)*.") c.Assert(session, IsNil) } diff --git a/plumbing/transport/test/receive_pack.go b/plumbing/transport/test/receive_pack.go index 6309ef0..d29d9ca 100644 --- a/plumbing/transport/test/receive_pack.go +++ b/plumbing/transport/test/receive_pack.go @@ -61,6 +61,7 @@ func (s *ReceivePackSuite) TestAdvertisedReferencesNotExists(c *C) { func (s *ReceivePackSuite) TestCallAdvertisedReferenceTwice(c *C) { r, err := s.Client.NewReceivePackSession(s.Endpoint, s.EmptyAuth) + defer func() { c.Assert(r.Close(), IsNil) }() c.Assert(err, IsNil) ar1, err := r.AdvertisedReferences() c.Assert(err, IsNil) @@ -43,8 +43,11 @@ func (r *Remote) Config() *config.RemoteConfig { } 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 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) error { 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 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (storer.ReferenceSt 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 } @@ -464,7 +467,7 @@ func calculateRefs(spec []config.RefSpec, refs := make(memory.ReferenceStorage, 0) return refs, iter.ForEach(func(ref *plumbing.Reference) error { if !config.MatchAny(spec, ref.Name()) { - if !ref.IsTag() || tags != AllTags { + if !ref.Name().IsTag() || tags != AllTags { return nil } } @@ -663,7 +666,7 @@ func (r *Remote) updateLocalReferenceStorage( func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) (updated bool, err error) { for _, ref := range refs { - if !ref.IsTag() { + if !ref.Name().IsTag() { continue } diff --git a/remote_test.go b/remote_test.go index ece052a..e2fd8ae 100644 --- a/remote_test.go +++ b/remote_test.go @@ -26,25 +26,25 @@ type RemoteSuite struct { 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) TestFetchInvalidFetchOptions(c *C) { 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) TestFetchWildcard(c *C) { 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) TestFetchWildcardTags(c *C) { 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) TestFetch(c *C) { 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 @@ func (s *RemoteSuite) TestFetchContext(c *C) { }, }) 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) TestFetchWithAllTags(c *C) { 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) TestFetchWithNoTags(c *C) { 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 @@ func (s *RemoteSuite) TestFetchWithProgress(c *C) { 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 @@ func (s *RemoteSuite) TestFetchWithPackfileWriter(c *C) { 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) TestFetchNoErrAlreadyUpToDate(c *C) { 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) TestFetchNoErrAlreadyUpToDateWithNonCommitObjects(c *C) { } 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) doTestFetchNoErrAlreadyUpToDate(c *C, url string) { 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 @@ func (s *RemoteSuite) TestPushToEmptyRepository(c *C) { r := newRemote(sto, &config.RemoteConfig{ Name: DefaultRemoteName, - URL: url, + URLs: []string{url}, }) rs := config.RefSpec("refs/heads/*:refs/heads/*") @@ -345,7 +344,7 @@ func (s *RemoteSuite) TestPushToEmptyRepository(c *C) { expected := make(map[string]string) iter.ForEach(func(ref *plumbing.Reference) error { - if !ref.IsBranch() { + if !ref.Name().IsBranch() { return nil } @@ -369,7 +368,7 @@ func (s *RemoteSuite) TestPushContext(c *C) { r := newRemote(sto, &config.RemoteConfig{ Name: DefaultRemoteName, - URL: url, + URLs: []string{url}, }) ctx, cancel := context.WithCancel(context.Background()) @@ -392,7 +391,7 @@ func (s *RemoteSuite) TestPushTags(c *C) { r := newRemote(sto, &config.RemoteConfig{ Name: DefaultRemoteName, - URL: url, + URLs: []string{url}, }) err = r.Push(&PushOptions{ @@ -416,7 +415,7 @@ func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) { r := newRemote(sto, &config.RemoteConfig{ Name: DefaultRemoteName, - URL: fs.Root(), + URLs: []string{fs.Root()}, }) err = r.Push(&PushOptions{ @@ -490,7 +489,7 @@ func (s *RemoteSuite) TestPushForce(c *C) { 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) TestPushNewReference(c *C) { } 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) TestPushInvalidFetchOptions(c *C) { 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) TestPushInvalidRefSpec(c *C) { 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..8110cf1 100644 --- a/repository.go +++ b/repository.go @@ -286,7 +286,7 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (billy.Filesyste return nil, fmt.Errorf(".git file has no %s prefix", prefix) } - gitdir := line[len(prefix):] + gitdir := strings.Split(line[len(prefix):], "\n")[0] gitdir = strings.TrimSpace(gitdir) if filepath.IsAbs(gitdir) { return osfs.New(gitdir), nil @@ -408,7 +408,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { c := &config.RemoteConfig{ Name: o.RemoteName, - URL: o.URL, + URLs: []string{o.URL}, } if _, err := r.CreateRemote(c); err != nil { @@ -448,20 +448,24 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { return r.updateRemoteConfigIfNeeded(o, c, head) } -func (r *Repository) cloneRefSpec(o *CloneOptions, - c *config.RemoteConfig) []config.RefSpec { - - if !o.SingleBranch { - return c.Fetch - } +const ( + refspecTagWithDepth = "+refs/tags/%s:refs/tags/%[1]s" + refspecSingleBranch = "+refs/heads/%s:refs/remotes/%s/%[1]s" + refspecSingleBranchHEAD = "+HEAD:refs/remotes/%s/HEAD" +) +func (r *Repository) cloneRefSpec(o *CloneOptions, c *config.RemoteConfig) []config.RefSpec { var rs string - if o.ReferenceName == plumbing.HEAD { + switch { + case o.ReferenceName.IsTag() && o.Depth > 0: + rs = fmt.Sprintf(refspecTagWithDepth, o.ReferenceName.Short()) + case o.SingleBranch && o.ReferenceName == plumbing.HEAD: rs = fmt.Sprintf(refspecSingleBranchHEAD, c.Name) - } else { - rs = fmt.Sprintf(refspecSingleBranch, - o.ReferenceName.Short(), c.Name) + case o.SingleBranch: + rs = fmt.Sprintf(refspecSingleBranch, o.ReferenceName.Short(), c.Name) + default: + return c.Fetch } return []config.RefSpec{config.RefSpec(rs)} @@ -477,11 +481,6 @@ func (r *Repository) setIsBare(isBare bool) error { return r.Storer.SetConfig(cfg) } -const ( - refspecSingleBranch = "+refs/heads/%s:refs/remotes/%s/%[1]s" - refspecSingleBranchHEAD = "+HEAD:refs/remotes/%s/HEAD" -) - func (r *Repository) updateRemoteConfigIfNeeded(o *CloneOptions, c *config.RemoteConfig, head *plumbing.Reference) error { if !o.SingleBranch { return nil @@ -541,7 +540,7 @@ func (r *Repository) fetchAndUpdateReferences( func (r *Repository) updateReferences(spec []config.RefSpec, resolvedHead *plumbing.Reference) (updated bool, err error) { - if !resolvedHead.IsBranch() { + if !resolvedHead.Name().IsBranch() { // Detached HEAD mode head := plumbing.NewHashReference(plumbing.HEAD, resolvedHead.Hash()) return updateReferenceStorerIfNeeded(r.Storer, head) @@ -701,7 +700,7 @@ func (r *Repository) Tags() (storer.ReferenceIter, error) { return storer.NewReferenceFilteredIter( func(r *plumbing.Reference) bool { - return r.IsTag() + return r.Name().IsTag() }, refIter), nil } @@ -714,7 +713,7 @@ func (r *Repository) Branches() (storer.ReferenceIter, error) { return storer.NewReferenceFilteredIter( func(r *plumbing.Reference) bool { - return r.IsBranch() + return r.Name().IsBranch() }, refIter), nil } @@ -727,7 +726,7 @@ func (r *Repository) Notes() (storer.ReferenceIter, error) { return storer.NewReferenceFilteredIter( func(r *plumbing.Reference) bool { - return r.IsNote() + return r.Name().IsNote() }, refIter), nil } diff --git a/repository_test.go b/repository_test.go index 558149b..3da11f6 100644 --- a/repository_test.go +++ b/repository_test.go @@ -181,7 +181,7 @@ func (s *RepositorySuite) TestCreateRemoteAndRemote(c *C) { 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 @@ func (s *RepositorySuite) TestDeleteRemote(c *C) { 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) @@ -348,7 +348,7 @@ func (s *RepositorySuite) TestPlainOpenBareRelativeGitDirFileTrailingGarbage(c * altDir, err := ioutil.TempDir("", "plain-open") c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(altDir, ".git"), []byte(fmt.Sprintf("gitdir: %s\nTRAILING", dir)), 0644) + err = ioutil.WriteFile(filepath.Join(altDir, ".git"), []byte(fmt.Sprintf("gitdir: %s\nTRAILING", altDir)), 0644) c.Assert(err, IsNil) r, err = PlainOpen(altDir) @@ -426,7 +426,7 @@ func (s *RepositorySuite) TestFetch(c *C) { 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 @@ func (s *RepositorySuite) TestFetchContext(c *C) { 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 @@ func (s *RepositorySuite) TestCloneConfig(c *C) { 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) { @@ -613,23 +613,52 @@ func (s *RepositorySuite) TestCloneDetachedHEAD(c *C) { URL: s.GetBasicLocalRepositoryURL(), ReferenceName: plumbing.ReferenceName("refs/tags/v1.0.0"), }) + c.Assert(err, IsNil) + + head, err := r.Reference(plumbing.HEAD, false) + c.Assert(err, IsNil) + c.Assert(head, NotNil) + c.Assert(head.Type(), Equals, plumbing.HashReference) + c.Assert(head.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + count := 0 + objects, err := r.Objects() + c.Assert(err, IsNil) + objects.ForEach(func(object.Object) error { count++; return nil }) + c.Assert(count, Equals, 31) +} + +func (s *RepositorySuite) TestCloneDetachedHEADAndShallow(c *C) { + r, _ := Init(memory.NewStorage(), memfs.New()) + err := r.clone(context.Background(), &CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + ReferenceName: plumbing.ReferenceName("refs/tags/v1.0.0"), + Depth: 1, + }) + + c.Assert(err, IsNil) head, err := r.Reference(plumbing.HEAD, false) c.Assert(err, IsNil) c.Assert(head, NotNil) c.Assert(head.Type(), Equals, plumbing.HashReference) c.Assert(head.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + count := 0 + objects, err := r.Objects() + c.Assert(err, IsNil) + objects.ForEach(func(object.Object) error { count++; return nil }) + c.Assert(count, Equals, 15) } func (s *RepositorySuite) TestPush(c *C) { url := c.MkDir() - fmt.Println(url) server, err := PlainInit(url, true) c.Assert(err, IsNil) _, err = s.Repository.CreateRemote(&config.RemoteConfig{ Name: "test", - URL: url, + URLs: []string{url}, }) c.Assert(err, IsNil) @@ -651,13 +680,12 @@ func (s *RepositorySuite) TestPush(c *C) { func (s *RepositorySuite) TestPushContext(c *C) { url := c.MkDir() - fmt.Println(url) _, err := PlainInit(url, true) c.Assert(err, IsNil) _, err = s.Repository.CreateRemote(&config.RemoteConfig{ Name: "foo", - URL: url, + URLs: []string{url}, }) c.Assert(err, IsNil) @@ -923,7 +951,7 @@ func (s *RepositorySuite) TestTags(c *C) { tags.ForEach(func(tag *plumbing.Reference) error { count++ c.Assert(tag.Hash().IsZero(), Equals, false) - c.Assert(tag.IsTag(), Equals, true) + c.Assert(tag.Name().IsTag(), Equals, true) return nil }) @@ -944,7 +972,7 @@ func (s *RepositorySuite) TestBranches(c *C) { branches.ForEach(func(branch *plumbing.Reference) error { count++ c.Assert(branch.Hash().IsZero(), Equals, false) - c.Assert(branch.IsBranch(), Equals, true) + c.Assert(branch.Name().IsBranch(), Equals, true) return nil }) @@ -968,7 +996,7 @@ func (s *RepositorySuite) TestNotes(c *C) { notes.ForEach(func(note *plumbing.Reference) error { count++ c.Assert(note.Hash().IsZero(), Equals, false) - c.Assert(note.IsNote(), Equals, true) + c.Assert(note.Name().IsNote(), Equals, true) return nil }) 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 @@ import ( "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 @@ func (s *ConfigSuite) TestRemotes(c *C) { 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 @@ func (s *BaseStorageSuite) TestSetConfigAndConfig(c *C) { 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 @@ func (s *Submodule) Repository() (*Repository, error) { _, err = r.CreateRemote(&config.RemoteConfig{ Name: DefaultRemoteName, - URL: s.c.URL, + URLs: []string{s.c.URL}, }) return r, err diff --git a/utils/merkletrie/noder/path.go b/utils/merkletrie/noder/path.go index 85742db..d2e2932 100644 --- a/utils/merkletrie/noder/path.go +++ b/utils/merkletrie/noder/path.go @@ -3,6 +3,8 @@ package noder import ( "bytes" "strings" + + "golang.org/x/text/unicode/norm" ) // Path values represent a noder and its ancestors. The root goes first @@ -78,7 +80,11 @@ func (p Path) Compare(other Path) int { case i == len(p): return -1 default: - cmp := strings.Compare(p[i].Name(), other[i].Name()) + form := norm.Form(norm.NFC) + this := form.String(p[i].Name()) + that := form.String(other[i].Name()) + + cmp := strings.Compare(this, that) if cmp != 0 { return cmp } diff --git a/utils/merkletrie/noder/path_test.go b/utils/merkletrie/noder/path_test.go index 44e3c3c..be25444 100644 --- a/utils/merkletrie/noder/path_test.go +++ b/utils/merkletrie/noder/path_test.go @@ -1,6 +1,9 @@ package noder -import . "gopkg.in/check.v1" +import ( + "golang.org/x/text/unicode/norm" + . "gopkg.in/check.v1" +) type PathSuite struct{} @@ -149,3 +152,10 @@ func (s *PathSuite) TestCompareMixedDepths(c *C) { c.Assert(p1.Compare(p2), Equals, 1) c.Assert(p2.Compare(p1), Equals, -1) } + +func (s *PathSuite) TestCompareNormalization(c *C) { + p1 := Path([]Noder{&noderMock{name: norm.Form(norm.NFKC).String("페")}}) + p2 := Path([]Noder{&noderMock{name: norm.Form(norm.NFKD).String("페")}}) + c.Assert(p1.Compare(p2), Equals, 0) + c.Assert(p2.Compare(p1), Equals, 0) +} diff --git a/worktree.go b/worktree.go index 60c8adb..4f8e740 100644 --- a/worktree.go +++ b/worktree.go @@ -209,7 +209,7 @@ func (w *Worktree) getCommitFromCheckoutOptions(opts *CheckoutOptions) (plumbing return plumbing.ZeroHash, err } - if !b.IsTag() { + if !b.Name().IsTag() { return b.Hash(), nil } @@ -244,7 +244,7 @@ func (w *Worktree) setHEADToBranch(branch plumbing.ReferenceName, commit plumbin } var head *plumbing.Reference - if target.IsBranch() { + if target.Name().IsBranch() { head = plumbing.NewSymbolicReference(plumbing.HEAD, target.Name()) } else { head = plumbing.NewHashReference(plumbing.HEAD, commit) @@ -323,7 +323,7 @@ func (w *Worktree) setHEADCommit(commit plumbing.Hash) error { return err } - if !branch.IsBranch() { + if !branch.Name().IsBranch() { return fmt.Errorf("invalid HEAD target should be a branch, found %s", branch.Type()) } diff --git a/worktree_test.go b/worktree_test.go index 10774a4..0a1c2d1 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -6,6 +6,9 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" + + "golang.org/x/text/unicode/norm" "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/plumbing" @@ -37,7 +40,7 @@ func (s *WorktreeSuite) TestPullCheckout(c *C) { r, _ := Init(memory.NewStorage(), fs) r.CreateRemote(&config.RemoteConfig{ Name: DefaultRemoteName, - URL: s.GetBasicLocalRepositoryURL(), + URLs: []string{s.GetBasicLocalRepositoryURL()}, }) w, err := r.Worktree() @@ -115,7 +118,7 @@ func (s *WorktreeSuite) TestPullUpdateReferencesIfNeeded(c *C) { 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 +176,7 @@ func (s *WorktreeSuite) TestPullProgress(c *C) { r.CreateRemote(&config.RemoteConfig{ Name: DefaultRemoteName, - URL: s.GetBasicLocalRepositoryURL(), + URLs: []string{s.GetBasicLocalRepositoryURL()}, }) w, err := r.Worktree() @@ -198,7 +201,7 @@ func (s *WorktreeSuite) TestPullProgressWithRecursion(c *C) { r, _ := PlainInit(dir, false) r.CreateRemote(&config.RemoteConfig{ Name: DefaultRemoteName, - URL: path, + URLs: []string{path}, }) w, err := r.Worktree() @@ -276,6 +279,10 @@ func (s *WorktreeSuite) TestCheckout(c *C) { } func (s *WorktreeSuite) TestCheckoutSymlink(c *C) { + if runtime.GOOS == "windows" { + c.Skip("git doesn't support symlinks by default in windows") + } + dir, err := ioutil.TempDir("", "checkout") defer os.RemoveAll(dir) @@ -304,6 +311,47 @@ func (s *WorktreeSuite) TestCheckoutSymlink(c *C) { c.Assert(err, IsNil) } +func (s *WorktreeSuite) TestFilenameNormalization(c *C) { + url := c.MkDir() + path := fixtures.Basic().ByTag("worktree").One().Worktree().Root() + + server, err := PlainClone(url, false, &CloneOptions{ + URL: path, + }) + + filename := "페" + + w, err := server.Worktree() + c.Assert(err, IsNil) + util.WriteFile(w.Filesystem, filename, []byte("foo"), 0755) + _, err = w.Add(filename) + c.Assert(err, IsNil) + _, err = w.Commit("foo", &CommitOptions{Author: defaultSignature()}) + c.Assert(err, IsNil) + + r, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{ + URL: url, + }) + + w, err = r.Worktree() + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) + + err = w.Filesystem.Remove(filename) + c.Assert(err, IsNil) + + modFilename := norm.Form(norm.NFKD).String(filename) + util.WriteFile(w.Filesystem, modFilename, []byte("foo"), 0755) + _, err = w.Add(filename) + + status, err = w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) +} + func (s *WorktreeSuite) TestCheckoutSubmodule(c *C) { url := "https://github.com/git-fixtures/submodule.git" r := s.NewRepositoryWithEmptyWorktree(fixtures.ByURL(url).One()) @@ -387,10 +435,12 @@ func (s *WorktreeSuite) TestCheckoutIndexOS(c *C) { c.Assert(idx.Entries[0].Size, Equals, uint32(189)) c.Assert(idx.Entries[0].CreatedAt.IsZero(), Equals, false) - c.Assert(idx.Entries[0].Dev, Not(Equals), uint32(0)) - c.Assert(idx.Entries[0].Inode, Not(Equals), uint32(0)) - c.Assert(idx.Entries[0].UID, Not(Equals), uint32(0)) - c.Assert(idx.Entries[0].GID, Not(Equals), uint32(0)) + if runtime.GOOS != "windows" { + c.Assert(idx.Entries[0].Dev, Not(Equals), uint32(0)) + c.Assert(idx.Entries[0].Inode, Not(Equals), uint32(0)) + c.Assert(idx.Entries[0].UID, Not(Equals), uint32(0)) + c.Assert(idx.Entries[0].GID, Not(Equals), uint32(0)) + } } func (s *WorktreeSuite) TestCheckoutBranch(c *C) { |