diff options
27 files changed, 565 insertions, 708 deletions
diff --git a/_examples/clone/main.go b/_examples/clone/main.go index d4d3880..bcdb6a9 100644 --- a/_examples/clone/main.go +++ b/_examples/clone/main.go @@ -18,7 +18,7 @@ func main() { r, err := git.PlainClone(directory, false, &git.CloneOptions{ URL: url, - RecurseSubmodules: git.DefaultRecursivity, + RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, }) CheckIfError(err) diff --git a/config/config.go b/config/config.go index 65b51eb..259ebf9 100644 --- a/config/config.go +++ b/config/config.go @@ -32,17 +32,18 @@ var ( // Config contains the repository configuration // ftp://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES type Config struct { - // Core variables Core struct { // IsBare if true this repository is assumed to be bare and has no - // working directory associated with it + // working directory associated with it. IsBare bool - // Worktree is the path to the root of the working tree + // Worktree is the path to the root of the working tree. Worktree string } - // Remotes list of repository remotes + // Remotes list of repository remotes, the key of the map is the name + // of the remote, should equal to RemoteConfig.Name. Remotes map[string]*RemoteConfig - // Submodules list of repository submodules + // Submodules list of repository submodules, the key of the map is the name + // of the submodule, should equal to Submodule.Name. Submodules map[string]*Submodule // contains the raw information of a config file, the main goal is preserve @@ -51,7 +52,7 @@ type Config struct { raw *format.Config } -// NewConfig returns a new empty Config +// NewConfig returns a new empty Config. func NewConfig() *Config { return &Config{ Remotes: make(map[string]*RemoteConfig, 0), @@ -60,7 +61,7 @@ func NewConfig() *Config { } } -// Validate validates the fields and sets the default values +// Validate validates the fields and sets the default values. func (c *Config) Validate() error { for name, r := range c.Remotes { if r.Name != name { @@ -85,7 +86,7 @@ const ( worktreeKey = "worktree" ) -// Unmarshal parses a git-config file and stores it +// Unmarshal parses a git-config file and stores it. func (c *Config) Unmarshal(b []byte) error { r := bytes.NewBuffer(b) d := format.NewDecoder(r) @@ -133,7 +134,7 @@ func (c *Config) unmarshalSubmodules() { } } -// Marshal returns Config encoded as a git-config file +// Marshal returns Config encoded as a git-config file. func (c *Config) Marshal() ([]byte, error) { c.marshalCore() c.marshalRemotes() @@ -182,7 +183,7 @@ func (c *Config) marshalSubmodules() { } } -// RemoteConfig contains the configuration for a given remote repository +// RemoteConfig contains the configuration for a given remote repository. type RemoteConfig struct { // Name of the remote Name string @@ -196,7 +197,7 @@ type RemoteConfig struct { raw *format.Subsection } -// Validate validates the fields and sets the default values +// Validate validates the fields and sets the default values. func (c *RemoteConfig) Validate() error { if c.Name == "" { return ErrRemoteConfigEmptyName diff --git a/config/modules.go b/config/modules.go index a17cc27..3d01117 100644 --- a/config/modules.go +++ b/config/modules.go @@ -15,7 +15,7 @@ var ( // Modules defines the submodules properties, represents a .gitmodules file // https://www.kernel.org/pub/software/scm/git/docs/gitmodules.html type Modules struct { - // Submodules is a map of submodules being the key the name of the submodule + // Submodules is a map of submodules being the key the name of the submodule. Submodules map[string]*Submodule raw *format.Config @@ -34,7 +34,7 @@ const ( branchKey = "branch" ) -// Unmarshal parses a git-config file and stores it +// Unmarshal parses a git-config file and stores it. func (m *Modules) Unmarshal(b []byte) error { r := bytes.NewBuffer(b) d := format.NewDecoder(r) @@ -55,7 +55,7 @@ func (m *Modules) Unmarshal(b []byte) error { return nil } -// Marshal returns Modules encoded as a git-config file +// Marshal returns Modules encoded as a git-config file. func (m *Modules) Marshal() ([]byte, error) { s := m.raw.Section(submoduleSection) s.Subsections = make(format.Subsections, len(m.Submodules)) @@ -74,12 +74,12 @@ func (m *Modules) Marshal() ([]byte, error) { return buf.Bytes(), nil } -// Submodule defines a submodule +// Submodule defines a submodule. type Submodule struct { // Name module name Name string // Path defines the path, relative to the top-level directory of the Git - // working tree, + // working tree. Path string // URL defines a URL from which the submodule repository can be cloned. URL string @@ -88,11 +88,11 @@ type Submodule struct { Branch string // raw representation of the subsection, filled by marshal or unmarshal are - // called + // called. raw *format.Subsection } -// Validate validates the fields and sets the default values +// Validate validates the fields and sets the default values. func (m *Submodule) Validate() error { if m.Path == "" { return ErrModuleEmptyPath diff --git a/config/refspec.go b/config/refspec.go index dd68edc..9441df8 100644 --- a/config/refspec.go +++ b/config/refspec.go @@ -49,7 +49,7 @@ func (s RefSpec) Validate() error { return ErrRefSpecMalformedWildcard } -// IsForceUpdate returns if update is allowed in non fast-forward merges +// IsForceUpdate returns if update is allowed in non fast-forward merges. func (s RefSpec) IsForceUpdate() bool { if s[0] == refSpecForce[0] { return true @@ -67,7 +67,7 @@ func (s RefSpec) IsDelete() bool { return false } -// Src return the src side +// Src return the src side. func (s RefSpec) Src() string { spec := string(s) start := strings.Index(spec, refSpecForce) + 1 @@ -76,7 +76,7 @@ func (s RefSpec) Src() string { return spec[start:end] } -// Match match the given plumbing.ReferenceName against the source +// Match match the given plumbing.ReferenceName against the source. func (s RefSpec) Match(n plumbing.ReferenceName) bool { if !s.IsWildcard() { return s.matchExact(n) @@ -85,7 +85,7 @@ func (s RefSpec) Match(n plumbing.ReferenceName) bool { return s.matchGlob(n) } -// IsWildcard returns true if the RefSpec contains a wildcard +// IsWildcard returns true if the RefSpec contains a wildcard. func (s RefSpec) IsWildcard() bool { return strings.Index(string(s), refSpecWildcard) != -1 } @@ -110,7 +110,7 @@ func (s RefSpec) matchGlob(n plumbing.ReferenceName) bool { strings.HasSuffix(name, suffix) } -// Dst returns the destination for the given remote reference +// Dst returns the destination for the given remote reference. func (s RefSpec) Dst(n plumbing.ReferenceName) plumbing.ReferenceName { spec := string(s) start := strings.Index(spec, refSpecSeparator) + 1 @@ -133,7 +133,7 @@ func (s RefSpec) String() string { return string(s) } -// MatchAny returns true if any of the RefSpec match with the given ReferenceName +// MatchAny returns true if any of the RefSpec match with the given ReferenceName. func MatchAny(l []RefSpec, n plumbing.ReferenceName) bool { for _, r := range l { if r.Match(n) { @@ -7,4 +7,4 @@ // It is highly extensible, we have been following the open/close principle in // its design to facilitate extensions, mainly focusing the efforts on the // persistence of the objects. -package git // import "srcd.works/go-git.v4" +package git @@ -10,48 +10,48 @@ import ( ) // SubmoduleRescursivity defines how depth will affect any submodule recursive -// operation -type SubmoduleRescursivity int +// operation. +type SubmoduleRescursivity uint const ( - // DefaultRemoteName name of the default Remote, just like git command + // DefaultRemoteName name of the default Remote, just like git command. DefaultRemoteName = "origin" - // NoRecursivity disables the recursion for a submodule operation - NoRecursivity SubmoduleRescursivity = 0 - // DefaultRecursivity allow recursion in a submodule operation - DefaultRecursivity SubmoduleRescursivity = 10 + // NoRecurseSubmodules disables the recursion for a submodule operation. + NoRecurseSubmodules SubmoduleRescursivity = 0 + // DefaultSubmoduleRecursionDepth allow recursion in a submodule operation. + DefaultSubmoduleRecursionDepth SubmoduleRescursivity = 10 ) var ( ErrMissingURL = errors.New("URL field is required") ) -// CloneOptions describes how a clone should be performed +// CloneOptions describes how a clone should be performed. type CloneOptions struct { - // The (possibly remote) repository URL to clone from + // The (possibly remote) repository URL to clone from. URL string - // Auth credentials, if required, to use with the remote repository + // Auth credentials, if required, to use with the remote repository. Auth transport.AuthMethod - // Name of the remote to be added, by default `origin` + // Name of the remote to be added, by default `origin`. RemoteName string - // Remote branch to clone + // Remote branch to clone. ReferenceName plumbing.ReferenceName - // Fetch only ReferenceName if true + // Fetch only ReferenceName if true. SingleBranch bool - // Limit fetching to the specified number of commits + // Limit fetching to the specified number of commits. Depth int // RecurseSubmodules after the clone is created, initialize all submodules // within, using their default settings. This option is ignored if the - // cloned repository does not have a worktree + // cloned repository does not have a worktree. RecurseSubmodules SubmoduleRescursivity // Progress is where the human readable information sent by the server is // stored, if nil nothing is stored and the capability (if supported) - // no-progress, is sent to the server to avoid send this information + // no-progress, is sent to the server to avoid send this information. Progress sideband.Progress } -// Validate validates the fields and sets the default values +// Validate validates the fields and sets the default values. func (o *CloneOptions) Validate() error { if o.URL == "" { return ErrMissingURL @@ -68,7 +68,7 @@ func (o *CloneOptions) Validate() error { return nil } -// PullOptions describes how a pull should be performed +// PullOptions describes how a pull should be performed. type PullOptions struct { // Name of the remote to be pulled. If empty, uses the default. RemoteName string @@ -78,14 +78,14 @@ type PullOptions struct { SingleBranch bool // Limit fetching to the specified number of commits. Depth int - // Auth credentials, if required, to use with the remote repository + // Auth credentials, if required, to use with the remote repository. Auth transport.AuthMethod // RecurseSubmodules controls if new commits of all populated submodules - // should be fetched too + // should be fetched too. RecurseSubmodules SubmoduleRescursivity // Progress is where the human readable information sent by the server is // stored, if nil nothing is stored and the capability (if supported) - // no-progress, is sent to the server to avoid send this information + // no-progress, is sent to the server to avoid send this information. Progress sideband.Progress } @@ -110,15 +110,15 @@ type FetchOptions struct { // Depth limit fetching to the specified number of commits from the tip of // each remote branch history. Depth int - // Auth credentials, if required, to use with the remote repository + // Auth credentials, if required, to use with the remote repository. Auth transport.AuthMethod // Progress is where the human readable information sent by the server is // stored, if nil nothing is stored and the capability (if supported) - // no-progress, is sent to the server to avoid send this information + // no-progress, is sent to the server to avoid send this information. Progress sideband.Progress } -// Validate validates the fields and sets the default values +// Validate validates the fields and sets the default values. func (o *FetchOptions) Validate() error { if o.RemoteName == "" { o.RemoteName = DefaultRemoteName @@ -133,18 +133,18 @@ func (o *FetchOptions) Validate() error { return nil } -// PushOptions describes how a push should be performed +// PushOptions describes how a push should be performed. type PushOptions struct { // RemoteName is the name of the remote to be pushed to. RemoteName string // RefSpecs specify what destination ref to update with what source // object. A refspec with empty src can be used to delete a reference. RefSpecs []config.RefSpec - // Auth credentials, if required, to use with the remote repository + // Auth credentials, if required, to use with the remote repository. Auth transport.AuthMethod } -// Validate validates the fields and sets the default values +// Validate validates the fields and sets the default values. func (o *PushOptions) Validate() error { if o.RemoteName == "" { o.RemoteName = DefaultRemoteName @@ -165,11 +165,11 @@ func (o *PushOptions) Validate() error { return nil } -// SubmoduleUpdateOptions describes how a submodule update should be performed +// SubmoduleUpdateOptions describes how a submodule update should be performed. type SubmoduleUpdateOptions struct { - // Init initializes the submodules recorded in the index + // Init, if true initializes the submodules recorded in the index. Init bool - // NoFetch tell to the update command to don’t fetch new objects from the + // NoFetch tell to the update command to not fetch new objects from the // remote site. NoFetch bool // RecurseSubmodules the update is performed not only in the submodules of diff --git a/plumbing/difftree/difftree.go b/plumbing/difftree/difftree.go index 869f496..76c5f27 100644 --- a/plumbing/difftree/difftree.go +++ b/plumbing/difftree/difftree.go @@ -2,252 +2,24 @@ package difftree import ( "bytes" - "fmt" - "io" - "sort" - "strings" - "srcd.works/go-git.v4/plumbing" "srcd.works/go-git.v4/plumbing/object" + "srcd.works/go-git.v4/utils/merkletrie" + "srcd.works/go-git.v4/utils/merkletrie/noder" ) -type Action int - -func (a Action) String() string { - switch a { - case Insert: - return "Insert" - case Delete: - return "Delete" - case Modify: - return "Modify" - default: - panic(fmt.Sprintf("unsupported action: %d", a)) - } -} - -const ( - Insert Action = iota - Delete - Modify -) - -type Change struct { - Action - From ChangeEntry - To ChangeEntry -} - -type ChangeEntry struct { - Name string - Tree *object.Tree - TreeEntry object.TreeEntry -} - -func (c *Change) Files() (from, to *object.File, err error) { - if c.Action == Insert || c.Action == Modify { - to, err = c.To.Tree.TreeEntryFile(&c.To.TreeEntry) - if err != nil { - return - } - - } - - if c.Action == Delete || c.Action == Modify { - from, err = c.From.Tree.TreeEntryFile(&c.From.TreeEntry) - if err != nil { - return - } - } - - return -} - -func (c *Change) String() string { - return fmt.Sprintf("<Action: %s, Path: %s>", c.Action, c.name()) -} - -func (c *Change) name() string { - if c.From.Name != "" { - return c.From.Name - } - - return c.To.Name -} - -type Changes []*Change - -func newEmpty() Changes { - return make([]*Change, 0, 0) -} - func DiffTree(a, b *object.Tree) ([]*Change, error) { - if a == b { - return newEmpty(), nil - } - - if a == nil || b == nil { - return newWithEmpty(a, b) - } - - return newDiffTree(a, b) -} - -func (c Changes) Len() int { - return len(c) -} - -func (c Changes) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} - -func (c Changes) Less(i, j int) bool { - return strings.Compare(c[i].name(), c[j].name()) < 0 -} - -func (c Changes) String() string { - var buffer bytes.Buffer - buffer.WriteString("[") - comma := "" - for _, v := range c { - buffer.WriteString(comma) - buffer.WriteString(v.String()) - comma = ", " - } - buffer.WriteString("]") - - return buffer.String() -} - -func newWithEmpty(a, b *object.Tree) (Changes, error) { - changes := newEmpty() - - var action Action - var tree *object.Tree - if a == nil { - action = Insert - tree = b - } else { - action = Delete - tree = a - } - - w := object.NewTreeWalker(tree, true) - defer w.Close() - - for { - path, entry, err := w.Next() - if err == io.EOF { - break - } else if err != nil { - return nil, fmt.Errorf("cannot get next file: %s", err) - } - - if entry.Mode.IsDir() { - continue - } - - c := &Change{Action: action} - - if action == Insert { - c.To.Name = path - c.To.TreeEntry = entry - c.To.Tree = tree - } else { - c.From.Name = path - c.From.TreeEntry = entry - c.From.Tree = tree - } - - changes = append(changes, c) - } - - return changes, nil -} - -// FIXME: this is very inefficient, but correct. -// The proper way to do this is to implement a diff-tree algorithm, -// while taking advantage of the tree hashes to avoid traversing -// subtrees when the hash is equal in both inputs. -func newDiffTree(a, b *object.Tree) ([]*Change, error) { - var result []*Change - - aChanges, err := newWithEmpty(a, nil) - if err != nil { - return nil, fmt.Errorf("cannot create nil-diff of source tree: %s", err) - } - sort.Sort(aChanges) + from := newTreeNoder(a) + to := newTreeNoder(b) - bChanges, err := newWithEmpty(nil, b) - if err != nil { - return nil, fmt.Errorf("cannot create nil-diff of destination tree: %s", err) - } - sort.Sort(bChanges) - - for len(aChanges) > 0 && len(bChanges) > 0 { - switch comp := strings.Compare(aChanges[0].name(), bChanges[0].name()); { - case comp == 0: // append as "Modify" or ignore if not changed - modified, err := hasChange(a, b, aChanges[0].name()) - if err != nil { - return nil, err - } - - if modified { - c := mergeInsertAndDeleteIntoModify(aChanges[0], bChanges[0]) - result = append(result, c) - } - - aChanges = aChanges[1:] - bChanges = bChanges[1:] - case comp < 0: // delete first a change - result = append(result, aChanges[0]) - aChanges = aChanges[1:] - case comp > 0: // insert first b change - result = append(result, bChanges[0]) - bChanges = bChanges[1:] - } + hashEqual := func(a, b noder.Hasher) bool { + return bytes.Equal(a.Hash(), b.Hash()) } - // append all remaining changes in aChanges, if any, as deletes - // append all remaining changes in bChanges, if any, as inserts - result = append(result, aChanges...) - result = append(result, bChanges...) - - return result, nil -} - -func mergeInsertAndDeleteIntoModify(a, b *Change) *Change { - c := &Change{Action: Modify} - c.From.Name = a.From.Name - c.From.Tree = a.From.Tree - c.From.TreeEntry = a.From.TreeEntry - c.To.Name = b.To.Name - c.To.Tree = b.To.Tree - c.To.TreeEntry = b.To.TreeEntry - - return c -} - -func hasChange(a, b *object.Tree, path string) (bool, error) { - ha, err := hash(a, path) - if err != nil { - return false, err - } - - hb, err := hash(b, path) - if err != nil { - return false, err - } - - return ha != hb, nil -} - -func hash(tree *object.Tree, path string) (plumbing.Hash, error) { - file, err := tree.File(path) + merkletrieChanges, err := merkletrie.DiffTree(from, to, hashEqual) if err != nil { - var empty plumbing.Hash - return empty, fmt.Errorf("cannot find file %s in tree: %s", path, err) + return nil, err } - return file.Hash, nil + return newChanges(merkletrieChanges) } diff --git a/plumbing/difftree/difftree_test.go b/plumbing/difftree/difftree_test.go index 7679d0f..e2519b3 100644 --- a/plumbing/difftree/difftree_test.go +++ b/plumbing/difftree/difftree_test.go @@ -4,14 +4,15 @@ import ( "sort" "testing" - "github.com/src-d/go-git-fixtures" "srcd.works/go-git.v4/plumbing" "srcd.works/go-git.v4/plumbing/format/packfile" "srcd.works/go-git.v4/plumbing/object" "srcd.works/go-git.v4/plumbing/storer" "srcd.works/go-git.v4/storage/filesystem" "srcd.works/go-git.v4/storage/memory" + "srcd.works/go-git.v4/utils/merkletrie" + "github.com/src-d/go-git-fixtures" . "gopkg.in/check.v1" ) @@ -33,12 +34,6 @@ func (s *DiffTreeSuite) SetUpSuite(c *C) { s.cache = make(map[string]storer.EncodedObjectStorer) } -func (s *DiffTreeSuite) tree(c *C, h plumbing.Hash) *object.Tree { - t, err := object.GetTree(s.Storer, h) - c.Assert(err, IsNil) - return t -} - func (s *DiffTreeSuite) commitFromStorer(c *C, sto storer.EncodedObjectStorer, h plumbing.Hash) *object.Commit { @@ -76,108 +71,48 @@ func (s *DiffTreeSuite) storageFromPackfile(f *fixtures.Fixture) storer.EncodedO var _ = Suite(&DiffTreeSuite{}) -func (s *DiffTreeSuite) TestActionString(c *C) { - expected := "Insert" - action := Insert - obtained := action.String() - c.Assert(obtained, Equals, expected) - - expected = "Delete" - action = Delete - obtained = action.String() - c.Assert(obtained, Equals, expected) - - expected = "Modify" - action = Modify - obtained = action.String() - c.Assert(obtained, Equals, expected) - - action = 37 - c.Assert(func() { _ = action.String() }, - PanicMatches, "unsupported action: 37") -} - -func (s *DiffTreeSuite) TestChangeFilesInsert(c *C) { - tree := s.tree(c, plumbing.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c")) - - change := &Change{Action: Insert} - change.To.Name = "json/long.json" - change.To.Tree = tree - change.To.TreeEntry.Hash = plumbing.NewHash("49c6bb89b17060d7b4deacb7b338fcc6ea2352a9") - - from, to, err := change.Files() - c.Assert(err, IsNil) - c.Assert(from, IsNil) - c.Assert(to.ID(), Equals, change.To.TreeEntry.Hash) -} - -func (s *DiffTreeSuite) TestChangeFilesDelete(c *C) { - tree := s.tree(c, plumbing.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c")) - - change := &Change{Action: Delete} - change.From.Name = "json/long.json" - change.From.Tree = tree - change.From.TreeEntry.Hash = plumbing.NewHash("49c6bb89b17060d7b4deacb7b338fcc6ea2352a9") - - from, to, err := change.Files() - c.Assert(err, IsNil) - c.Assert(to, IsNil) - c.Assert(from.ID(), Equals, change.From.TreeEntry.Hash) -} - -func (s *DiffTreeSuite) TestChangeFilesModify(c *C) { - tree := s.tree(c, plumbing.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c")) - - change := &Change{Action: Modify} - change.To.Name = "json/long.json" - change.To.Tree = tree - change.To.TreeEntry.Hash = plumbing.NewHash("49c6bb89b17060d7b4deacb7b338fcc6ea2352a9") - change.From.Name = "json/long.json" - change.From.Tree = tree - change.From.TreeEntry.Hash = plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492") - - from, to, err := change.Files() - c.Assert(err, IsNil) - c.Assert(to.ID(), Equals, change.To.TreeEntry.Hash) - c.Assert(from.ID(), Equals, change.From.TreeEntry.Hash) +type expectChange struct { + Action merkletrie.Action + Name string } -func (s *DiffTreeSuite) TestChangeString(c *C) { - expected := "<Action: Insert, Path: foo>" - change := &Change{Action: Insert} - change.From.Name = "foo" - - obtained := change.String() - c.Assert(obtained, Equals, expected) +func assertChanges(a Changes, c *C) { + for _, changes := range a { + action, err := changes.Action() + c.Assert(err, IsNil) + switch action { + case merkletrie.Insert: + c.Assert(changes.From.Tree, IsNil) + c.Assert(changes.To.Tree, NotNil) + case merkletrie.Delete: + c.Assert(changes.From.Tree, NotNil) + c.Assert(changes.To.Tree, IsNil) + case merkletrie.Modify: + c.Assert(changes.From.Tree, NotNil) + c.Assert(changes.To.Tree, NotNil) + default: + c.Fatalf("unknown action: %d", action) + } + } } -func (s *DiffTreeSuite) TestChangesString(c *C) { - expected := "[]" - changes := newEmpty() - obtained := changes.String() - c.Assert(obtained, Equals, expected) - - expected = "[<Action: Modify, Path: bla>]" - changes = make([]*Change, 1) - changes[0] = &Change{Action: Modify} - changes[0].From.Name = "bla" +func equalChanges(a Changes, b []expectChange, c *C) bool { + if len(a) != len(b) { + return false + } - obtained = changes.String() - c.Assert(obtained, Equals, expected) + sort.Sort(a) - expected = "[<Action: Modify, Path: bla>, <Action: Insert, Path: foo/bar>]" - changes = make([]*Change, 2) - changes[0] = &Change{Action: Modify} - changes[0].From.Name = "bla" - changes[1] = &Change{Action: Insert} - changes[1].From.Name = "foo/bar" - obtained = changes.String() - c.Assert(obtained, Equals, expected) -} + for i, va := range a { + vb := b[i] + action, err := va.Action() + c.Assert(err, IsNil) + if action != vb.Action || va.name() != vb.Name { + return false + } + } -type expectChange struct { - Action Action - Name string + return true } func (s *DiffTreeSuite) TestDiffTree(c *C) { @@ -186,186 +121,209 @@ func (s *DiffTreeSuite) TestDiffTree(c *C) { commit1 string // the commit of the first tree commit2 string // the commit of the second tree expected []expectChange // the expected list of []changeExpect - }{{ - "https://github.com/dezfowler/LiteMock.git", - "", - "", - []expectChange{}, - }, { - "https://github.com/dezfowler/LiteMock.git", - "b7965eaa2c4f245d07191fe0bcfe86da032d672a", - "b7965eaa2c4f245d07191fe0bcfe86da032d672a", - []expectChange{}, - }, { - "https://github.com/dezfowler/LiteMock.git", - "", - "b7965eaa2c4f245d07191fe0bcfe86da032d672a", - []expectChange{ - {Action: Insert, Name: "README"}, + }{ + { + "https://github.com/dezfowler/LiteMock.git", + "", + "", + []expectChange{}, + }, + { + "https://github.com/dezfowler/LiteMock.git", + "b7965eaa2c4f245d07191fe0bcfe86da032d672a", + "b7965eaa2c4f245d07191fe0bcfe86da032d672a", + []expectChange{}, + }, + { + "https://github.com/dezfowler/LiteMock.git", + "", + "b7965eaa2c4f245d07191fe0bcfe86da032d672a", + []expectChange{ + {Action: merkletrie.Insert, Name: "README"}, + }, }, - }, { - "https://github.com/dezfowler/LiteMock.git", - "b7965eaa2c4f245d07191fe0bcfe86da032d672a", - "", - []expectChange{ - {Action: Delete, Name: "README"}, + { + "https://github.com/dezfowler/LiteMock.git", + "b7965eaa2c4f245d07191fe0bcfe86da032d672a", + "", + []expectChange{ + {Action: merkletrie.Delete, Name: "README"}, + }, }, - }, { - "https://github.com/githubtraining/example-branches.git", - "", - "f0eb272cc8f77803478c6748103a1450aa1abd37", - []expectChange{ - {Action: Insert, Name: "README.md"}, + { + "https://github.com/githubtraining/example-branches.git", + "", + "f0eb272cc8f77803478c6748103a1450aa1abd37", + []expectChange{ + {Action: merkletrie.Insert, Name: "README.md"}, + }, }, - }, { - "https://github.com/githubtraining/example-branches.git", - "f0eb272cc8f77803478c6748103a1450aa1abd37", - "", - []expectChange{ - {Action: Delete, Name: "README.md"}, + { + "https://github.com/githubtraining/example-branches.git", + "f0eb272cc8f77803478c6748103a1450aa1abd37", + "", + []expectChange{ + {Action: merkletrie.Delete, Name: "README.md"}, + }, }, - }, { - "https://github.com/githubtraining/example-branches.git", - "f0eb272cc8f77803478c6748103a1450aa1abd37", - "f0eb272cc8f77803478c6748103a1450aa1abd37", - []expectChange{}, - }, { - "https://github.com/github/gem-builder.git", - "", - "9608eed92b3839b06ebf72d5043da547de10ce85", - []expectChange{ - {Action: Insert, Name: "README"}, - {Action: Insert, Name: "gem_builder.rb"}, - {Action: Insert, Name: "gem_eval.rb"}, + { + "https://github.com/githubtraining/example-branches.git", + "f0eb272cc8f77803478c6748103a1450aa1abd37", + "f0eb272cc8f77803478c6748103a1450aa1abd37", + []expectChange{}, }, - }, { - "https://github.com/github/gem-builder.git", - "9608eed92b3839b06ebf72d5043da547de10ce85", - "", - []expectChange{ - {Action: Delete, Name: "README"}, - {Action: Delete, Name: "gem_builder.rb"}, - {Action: Delete, Name: "gem_eval.rb"}, + { + "https://github.com/github/gem-builder.git", + "", + "9608eed92b3839b06ebf72d5043da547de10ce85", + []expectChange{ + {Action: merkletrie.Insert, Name: "README"}, + {Action: merkletrie.Insert, Name: "gem_builder.rb"}, + {Action: merkletrie.Insert, Name: "gem_eval.rb"}, + }, }, - }, { - "https://github.com/github/gem-builder.git", - "9608eed92b3839b06ebf72d5043da547de10ce85", - "9608eed92b3839b06ebf72d5043da547de10ce85", - []expectChange{}, - }, { - "https://github.com/toqueteos/ts3.git", - "", - "764e914b75d6d6df1fc5d832aa9840f590abf1bb", - []expectChange{ - {Action: Insert, Name: "README.markdown"}, - {Action: Insert, Name: "examples/bot.go"}, - {Action: Insert, Name: "examples/raw_shell.go"}, - {Action: Insert, Name: "helpers.go"}, - {Action: Insert, Name: "ts3.go"}, + { + "https://github.com/github/gem-builder.git", + "9608eed92b3839b06ebf72d5043da547de10ce85", + "", + []expectChange{ + {Action: merkletrie.Delete, Name: "README"}, + {Action: merkletrie.Delete, Name: "gem_builder.rb"}, + {Action: merkletrie.Delete, Name: "gem_eval.rb"}, + }, }, - }, { - "https://github.com/toqueteos/ts3.git", - "764e914b75d6d6df1fc5d832aa9840f590abf1bb", - "", - []expectChange{ - {Action: Delete, Name: "README.markdown"}, - {Action: Delete, Name: "examples/bot.go"}, - {Action: Delete, Name: "examples/raw_shell.go"}, - {Action: Delete, Name: "helpers.go"}, - {Action: Delete, Name: "ts3.go"}, + { + "https://github.com/github/gem-builder.git", + "9608eed92b3839b06ebf72d5043da547de10ce85", + "9608eed92b3839b06ebf72d5043da547de10ce85", + []expectChange{}, }, - }, { - "https://github.com/toqueteos/ts3.git", - "764e914b75d6d6df1fc5d832aa9840f590abf1bb", - "764e914b75d6d6df1fc5d832aa9840f590abf1bb", - []expectChange{}, - }, { - "https://github.com/github/gem-builder.git", - "9608eed92b3839b06ebf72d5043da547de10ce85", - "6c41e05a17e19805879689414026eb4e279f7de0", - []expectChange{ - {Action: Modify, Name: "gem_eval.rb"}, + { + "https://github.com/toqueteos/ts3.git", + "", + "764e914b75d6d6df1fc5d832aa9840f590abf1bb", + []expectChange{ + {Action: merkletrie.Insert, Name: "README.markdown"}, + {Action: merkletrie.Insert, Name: "examples/bot.go"}, + {Action: merkletrie.Insert, Name: "examples/raw_shell.go"}, + {Action: merkletrie.Insert, Name: "helpers.go"}, + {Action: merkletrie.Insert, Name: "ts3.go"}, + }, }, - }, { - "https://github.com/github/gem-builder.git", - "6c41e05a17e19805879689414026eb4e279f7de0", - "89be3aac2f178719c12953cc9eaa23441f8d9371", - []expectChange{ - {Action: Modify, Name: "gem_eval.rb"}, - {Action: Insert, Name: "gem_eval_test.rb"}, - {Action: Insert, Name: "security.rb"}, - {Action: Insert, Name: "security_test.rb"}, + { + "https://github.com/toqueteos/ts3.git", + "764e914b75d6d6df1fc5d832aa9840f590abf1bb", + "", + []expectChange{ + {Action: merkletrie.Delete, Name: "README.markdown"}, + {Action: merkletrie.Delete, Name: "examples/bot.go"}, + {Action: merkletrie.Delete, Name: "examples/raw_shell.go"}, + {Action: merkletrie.Delete, Name: "helpers.go"}, + {Action: merkletrie.Delete, Name: "ts3.go"}, + }, }, - }, { - "https://github.com/github/gem-builder.git", - "89be3aac2f178719c12953cc9eaa23441f8d9371", - "597240b7da22d03ad555328f15abc480b820acc0", - []expectChange{ - {Action: Modify, Name: "gem_eval.rb"}, + { + "https://github.com/toqueteos/ts3.git", + "764e914b75d6d6df1fc5d832aa9840f590abf1bb", + "764e914b75d6d6df1fc5d832aa9840f590abf1bb", + []expectChange{}, }, - }, { - "https://github.com/github/gem-builder.git", - "597240b7da22d03ad555328f15abc480b820acc0", - "0260380e375d2dd0e1a8fcab15f91ce56dbe778e", - []expectChange{ - {Action: Modify, Name: "gem_eval.rb"}, - {Action: Modify, Name: "gem_eval_test.rb"}, - {Action: Insert, Name: "lazy_dir.rb"}, - {Action: Insert, Name: "lazy_dir_test.rb"}, - {Action: Modify, Name: "security.rb"}, - {Action: Modify, Name: "security_test.rb"}, + { + "https://github.com/github/gem-builder.git", + "9608eed92b3839b06ebf72d5043da547de10ce85", + "6c41e05a17e19805879689414026eb4e279f7de0", + []expectChange{ + {Action: merkletrie.Modify, Name: "gem_eval.rb"}, + }, }, - }, { - "https://github.com/github/gem-builder.git", - "0260380e375d2dd0e1a8fcab15f91ce56dbe778e", - "597240b7da22d03ad555328f15abc480b820acc0", - []expectChange{ - {Action: Modify, Name: "gem_eval.rb"}, - {Action: Modify, Name: "gem_eval_test.rb"}, - {Action: Delete, Name: "lazy_dir.rb"}, - {Action: Delete, Name: "lazy_dir_test.rb"}, - {Action: Modify, Name: "security.rb"}, - {Action: Modify, Name: "security_test.rb"}, + { + "https://github.com/github/gem-builder.git", + "6c41e05a17e19805879689414026eb4e279f7de0", + "89be3aac2f178719c12953cc9eaa23441f8d9371", + []expectChange{ + {Action: merkletrie.Modify, Name: "gem_eval.rb"}, + {Action: merkletrie.Insert, Name: "gem_eval_test.rb"}, + {Action: merkletrie.Insert, Name: "security.rb"}, + {Action: merkletrie.Insert, Name: "security_test.rb"}, + }, }, - }, { - "https://github.com/github/gem-builder.git", - "0260380e375d2dd0e1a8fcab15f91ce56dbe778e", - "ca9fd470bacb6262eb4ca23ee48bb2f43711c1ff", - []expectChange{ - {Action: Modify, Name: "gem_eval.rb"}, - {Action: Modify, Name: "security.rb"}, - {Action: Modify, Name: "security_test.rb"}, + { + "https://github.com/github/gem-builder.git", + "89be3aac2f178719c12953cc9eaa23441f8d9371", + "597240b7da22d03ad555328f15abc480b820acc0", + []expectChange{ + {Action: merkletrie.Modify, Name: "gem_eval.rb"}, + }, }, - }, { - "https://github.com/github/gem-builder.git", - "fe3c86745f887c23a0d38c85cfd87ca957312f86", - "b7e3f636febf7a0cd3ab473b6d30081786d2c5b6", - []expectChange{ - {Action: Modify, Name: "gem_eval.rb"}, - {Action: Modify, Name: "gem_eval_test.rb"}, - {Action: Insert, Name: "git_mock"}, - {Action: Modify, Name: "lazy_dir.rb"}, - {Action: Modify, Name: "lazy_dir_test.rb"}, - {Action: Modify, Name: "security.rb"}, + { + "https://github.com/github/gem-builder.git", + "597240b7da22d03ad555328f15abc480b820acc0", + "0260380e375d2dd0e1a8fcab15f91ce56dbe778e", + []expectChange{ + {Action: merkletrie.Modify, Name: "gem_eval.rb"}, + {Action: merkletrie.Modify, Name: "gem_eval_test.rb"}, + {Action: merkletrie.Insert, Name: "lazy_dir.rb"}, + {Action: merkletrie.Insert, Name: "lazy_dir_test.rb"}, + {Action: merkletrie.Modify, Name: "security.rb"}, + {Action: merkletrie.Modify, Name: "security_test.rb"}, + }, }, - }, { - "https://github.com/rumpkernel/rumprun-xen.git", - "1831e47b0c6db750714cd0e4be97b5af17fb1eb0", - "51d8515578ea0c88cc8fc1a057903675cf1fc16c", - []expectChange{ - {Action: Modify, Name: "Makefile"}, - {Action: Modify, Name: "netbsd_init.c"}, - {Action: Modify, Name: "rumphyper_stubs.c"}, - {Action: Delete, Name: "sysproxy.c"}, + { + "https://github.com/github/gem-builder.git", + "0260380e375d2dd0e1a8fcab15f91ce56dbe778e", + "597240b7da22d03ad555328f15abc480b820acc0", + []expectChange{ + {Action: merkletrie.Modify, Name: "gem_eval.rb"}, + {Action: merkletrie.Modify, Name: "gem_eval_test.rb"}, + {Action: merkletrie.Delete, Name: "lazy_dir.rb"}, + {Action: merkletrie.Delete, Name: "lazy_dir_test.rb"}, + {Action: merkletrie.Modify, Name: "security.rb"}, + {Action: merkletrie.Modify, Name: "security_test.rb"}, + }, }, - }, { - "https://github.com/rumpkernel/rumprun-xen.git", - "1831e47b0c6db750714cd0e4be97b5af17fb1eb0", - "e13e678f7ee9badd01b120889e0ec5fdc8ae3802", - []expectChange{ - {Action: Modify, Name: "app-tools/rumprun"}, + { + "https://github.com/github/gem-builder.git", + "0260380e375d2dd0e1a8fcab15f91ce56dbe778e", + "ca9fd470bacb6262eb4ca23ee48bb2f43711c1ff", + []expectChange{ + {Action: merkletrie.Modify, Name: "gem_eval.rb"}, + {Action: merkletrie.Modify, Name: "security.rb"}, + {Action: merkletrie.Modify, Name: "security_test.rb"}, + }, }, - }} { + { + "https://github.com/github/gem-builder.git", + "fe3c86745f887c23a0d38c85cfd87ca957312f86", + "b7e3f636febf7a0cd3ab473b6d30081786d2c5b6", + []expectChange{ + {Action: merkletrie.Modify, Name: "gem_eval.rb"}, + {Action: merkletrie.Modify, Name: "gem_eval_test.rb"}, + {Action: merkletrie.Insert, Name: "git_mock"}, + {Action: merkletrie.Modify, Name: "lazy_dir.rb"}, + {Action: merkletrie.Modify, Name: "lazy_dir_test.rb"}, + {Action: merkletrie.Modify, Name: "security.rb"}, + }, + }, + { + "https://github.com/rumpkernel/rumprun-xen.git", + "1831e47b0c6db750714cd0e4be97b5af17fb1eb0", + "51d8515578ea0c88cc8fc1a057903675cf1fc16c", + []expectChange{ + {Action: merkletrie.Modify, Name: "Makefile"}, + {Action: merkletrie.Modify, Name: "netbsd_init.c"}, + {Action: merkletrie.Modify, Name: "rumphyper_stubs.c"}, + {Action: merkletrie.Delete, Name: "sysproxy.c"}, + }, + }, + { + "https://github.com/rumpkernel/rumprun-xen.git", + "1831e47b0c6db750714cd0e4be97b5af17fb1eb0", + "e13e678f7ee9badd01b120889e0ec5fdc8ae3802", + []expectChange{ + {Action: merkletrie.Modify, Name: "app-tools/rumprun"}, + }, + }, + } { f := fixtures.ByURL(t.repository).One() sto := s.storageFromPackfile(f) @@ -388,43 +346,10 @@ func (s *DiffTreeSuite) TestDiffTree(c *C) { obtained, err := DiffTree(tree1, tree2) c.Assert(err, IsNil, Commentf("subtest %d: unable to calculate difftree: %s", i, err)) - c.Assert(equalChanges(obtained, t.expected), Equals, true, + c.Assert(equalChanges(obtained, t.expected, c), Equals, true, Commentf("subtest:%d\nrepo=%s\ncommit1=%s\ncommit2=%s\nexpected=%s\nobtained=%s", i, t.repository, t.commit1, t.commit2, t.expected, obtained)) assertChanges(obtained, c) } } - -func assertChanges(a Changes, c *C) { - for _, changes := range a { - switch changes.Action { - case Insert: - c.Assert(changes.From.Tree, IsNil) - c.Assert(changes.To.Tree, NotNil) - case Delete: - c.Assert(changes.From.Tree, NotNil) - c.Assert(changes.To.Tree, IsNil) - case Modify: - c.Assert(changes.From.Tree, NotNil) - c.Assert(changes.To.Tree, NotNil) - } - } -} - -func equalChanges(a Changes, b []expectChange) bool { - if len(a) != len(b) { - return false - } - - sort.Sort(a) - - for i, va := range a { - vb := b[i] - if va.Action != vb.Action || va.name() != vb.Name { - return false - } - } - - return true -} diff --git a/plumbing/format/index/encoder.go b/plumbing/format/index/encoder.go index 4699d43..bdb10c1 100644 --- a/plumbing/format/index/encoder.go +++ b/plumbing/format/index/encoder.go @@ -62,7 +62,7 @@ func (e *Encoder) encodeHeader(idx *Index) error { } func (e *Encoder) encodeEntries(idx *Index) error { - sort.Sort(ByName(idx.Entries)) + sort.Sort(byName(idx.Entries)) for _, entry := range idx.Entries { if err := e.encodeEntry(&entry); err != nil { @@ -143,8 +143,8 @@ func (e *Encoder) encodeFooter() error { return binary.Write(e.w, e.hash.Sum(nil)) } -type ByName []Entry +type byName []Entry -func (l ByName) Len() int { return len(l) } -func (l ByName) Swap(i, j int) { l[i], l[j] = l[j], l[i] } -func (l ByName) Less(i, j int) bool { return l[i].Name < l[j].Name } +func (l byName) Len() int { return len(l) } +func (l byName) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l byName) Less(i, j int) bool { return l[i].Name < l[j].Name } diff --git a/plumbing/object/file_test.go b/plumbing/object/file_test.go index 426fa8f..ff01c9f 100644 --- a/plumbing/object/file_test.go +++ b/plumbing/object/file_test.go @@ -249,7 +249,9 @@ func (s *FileSuite) TestFileIter(c *C) { } func (s *FileSuite) TestFileIterSubmodule(c *C) { - st, err := filesystem.NewStorage(fixtures.ByTag("submodule").One().DotGit()) + dotgit := fixtures.ByURL("https://github.com/git-fixtures/submodule.git").One().DotGit() + st, err := filesystem.NewStorage(dotgit) + c.Assert(err, IsNil) hash := plumbing.NewHash("a692ec699bff9117c1ed91752afbb7d9d272ebef") diff --git a/plumbing/object/tree_test.go b/plumbing/object/tree_test.go index 0ddf391..8ea31bb 100644 --- a/plumbing/object/tree_test.go +++ b/plumbing/object/tree_test.go @@ -266,7 +266,8 @@ func (s *TreeSuite) TestTreeWalkerNextNonRecursive(c *C) { } func (s *TreeSuite) TestTreeWalkerNextSubmodule(c *C) { - st, err := filesystem.NewStorage(fixtures.ByTag("submodule").One().DotGit()) + dotgit := fixtures.ByURL("https://github.com/git-fixtures/submodule.git").One().DotGit() + st, err := filesystem.NewStorage(dotgit) c.Assert(err, IsNil) hash := plumbing.NewHash("a692ec699bff9117c1ed91752afbb7d9d272ebef") @@ -284,6 +285,8 @@ func (s *TreeSuite) TestTreeWalkerNextSubmodule(c *C) { var count int walker := NewTreeWalker(tree, true) + defer walker.Close() + for { name, entry, err := walker.Next() if err == io.EOF { diff --git a/plumbing/storer/reference.go b/plumbing/storer/reference.go index 40474f9..692fe88 100644 --- a/plumbing/storer/reference.go +++ b/plumbing/storer/reference.go @@ -18,6 +18,7 @@ type ReferenceStorer interface { SetReference(*plumbing.Reference) error Reference(plumbing.ReferenceName) (*plumbing.Reference, error) IterReferences() (ReferenceIter, error) + RemoveReference(plumbing.ReferenceName) error } // ReferenceIter is a generic closable interface for iterating over references diff --git a/plumbing/transport/file/common_test.go b/plumbing/transport/file/common_test.go index 3dc4500..4f3ae8f 100644 --- a/plumbing/transport/file/common_test.go +++ b/plumbing/transport/file/common_test.go @@ -1,6 +1,7 @@ package file import ( + "io/ioutil" "os" "os/exec" "path/filepath" @@ -8,13 +9,13 @@ import ( "github.com/src-d/go-git-fixtures" . "gopkg.in/check.v1" - "io/ioutil" ) type CommonSuite struct { fixtures.Suite ReceivePackBin string UploadPackBin string + tmpDir string // to be removed at teardown } var _ = Suite(&CommonSuite{}) @@ -26,14 +27,20 @@ func (s *CommonSuite) SetUpSuite(c *C) { c.Skip("git command not found") } - binDir, err := ioutil.TempDir(os.TempDir(), "") + var err error + s.tmpDir, err = ioutil.TempDir("", "") c.Assert(err, IsNil) - s.ReceivePackBin = filepath.Join(binDir, "git-receive-pack") - s.UploadPackBin = filepath.Join(binDir, "git-upload-pack") - bin := filepath.Join(binDir, "go-git") + s.ReceivePackBin = filepath.Join(s.tmpDir, "git-receive-pack") + s.UploadPackBin = filepath.Join(s.tmpDir, "git-upload-pack") + bin := filepath.Join(s.tmpDir, "go-git") cmd := exec.Command("go", "build", "-o", bin, "../../../cli/go-git/...") c.Assert(cmd.Run(), IsNil) c.Assert(os.Symlink(bin, s.ReceivePackBin), IsNil) c.Assert(os.Symlink(bin, s.UploadPackBin), IsNil) } + +func (s *CommonSuite) TearDownSuite(c *C) { + defer s.Suite.TearDownSuite(c) + c.Assert(os.RemoveAll(s.tmpDir), IsNil) +} diff --git a/plumbing/transport/file/server_test.go b/plumbing/transport/file/server_test.go index 775b031..a7b4e34 100644 --- a/plumbing/transport/file/server_test.go +++ b/plumbing/transport/file/server_test.go @@ -2,8 +2,6 @@ package file import ( "fmt" - "io" - "io/ioutil" "os" "os/exec" @@ -48,8 +46,8 @@ func (s *ServerSuite) TestPush(c *C) { cmd.Dir = s.SrcPath cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "GIT_TRACE=true", "GIT_TRACE_PACKET=true") - stdout, stderr, err := execAndGetOutput(c, cmd) - c.Assert(err, IsNil, Commentf("STDOUT:\n%s\nSTDERR:\n%s\n", stdout, stderr)) + out, err := cmd.CombinedOutput() + c.Assert(err, IsNil, Commentf("combined stdout and stderr:\n%s\n", out)) } func (s *ServerSuite) TestClone(c *C) { @@ -61,45 +59,6 @@ func (s *ServerSuite) TestClone(c *C) { ) cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "GIT_TRACE=true", "GIT_TRACE_PACKET=true") - stdout, stderr, err := execAndGetOutput(c, cmd) - c.Assert(err, IsNil, Commentf("STDOUT:\n%s\nSTDERR:\n%s\n", stdout, stderr)) -} - -func execAndGetOutput(c *C, cmd *exec.Cmd) (stdout, stderr string, err error) { - sout, err := cmd.StdoutPipe() - c.Assert(err, IsNil) - serr, err := cmd.StderrPipe() - c.Assert(err, IsNil) - - outChan, outErr := readAllAsync(sout) - errChan, errErr := readAllAsync(serr) - - c.Assert(cmd.Start(), IsNil) - - if err = cmd.Wait(); err != nil { - return <-outChan, <-errChan, err - } - - if err := <-outErr; err != nil { - return <-outChan, <-errChan, err - } - - return <-outChan, <-errChan, <-errErr -} - -func readAllAsync(r io.Reader) (out chan string, err chan error) { - out = make(chan string, 1) - err = make(chan error, 1) - go func() { - b, e := ioutil.ReadAll(r) - if e != nil { - err <- e - } else { - err <- nil - } - - out <- string(b) - }() - - return out, err + out, err := cmd.CombinedOutput() + c.Assert(err, IsNil, Commentf("combined stdout and stderr:\n%s\n", out)) } diff --git a/repository.go b/repository.go index 9969b86..c065a26 100644 --- a/repository.go +++ b/repository.go @@ -332,13 +332,11 @@ func (r *Repository) clone(o *CloneOptions) error { Progress: o.Progress, }) if err != nil { - return err } head, err := storer.ResolveReference(remoteRefs, o.ReferenceName) if err != nil { - return err } @@ -350,7 +348,7 @@ func (r *Repository) clone(o *CloneOptions) error { return err } - if o.RecurseSubmodules != NoRecursivity && r.wt != nil { + if o.RecurseSubmodules != NoRecurseSubmodules && r.wt != nil { if err := r.updateSubmodules(o.RecurseSubmodules); err != nil { return err } @@ -553,7 +551,7 @@ func (r *Repository) Pull(o *PullOptions) error { return err } - if o.RecurseSubmodules != NoRecursivity && r.wt != nil { + if o.RecurseSubmodules != NoRecurseSubmodules && r.wt != nil { if err := r.updateSubmodules(o.RecurseSubmodules); err != nil { return err } diff --git a/repository_test.go b/repository_test.go index 6a9c14a..89ea188 100644 --- a/repository_test.go +++ b/repository_test.go @@ -299,10 +299,9 @@ func (s *RepositorySuite) TestPlainCloneWithRecurseSubmodules(c *C) { defer os.RemoveAll(dir) path := fixtures.ByTag("submodule").One().Worktree().Base() - r, err := PlainClone(dir, false, &CloneOptions{ URL: fmt.Sprintf("file://%s", path), - RecurseSubmodules: DefaultRecursivity, + RecurseSubmodules: DefaultSubmoduleRecursionDepth, }) c.Assert(err, IsNil) @@ -595,7 +594,7 @@ func (s *RepositorySuite) TestPullProgressWithRecursion(c *C) { }) err = r.Pull(&PullOptions{ - RecurseSubmodules: DefaultRecursivity, + RecurseSubmodules: DefaultSubmoduleRecursionDepth, }) c.Assert(err, IsNil) diff --git a/storage/filesystem/internal/dotgit/dotgit.go b/storage/filesystem/internal/dotgit/dotgit.go index 360b3d1..b46f827 100644 --- a/storage/filesystem/internal/dotgit/dotgit.go +++ b/storage/filesystem/internal/dotgit/dotgit.go @@ -5,11 +5,12 @@ import ( "bufio" "errors" "fmt" - "io/ioutil" + stdioutil "io/ioutil" "os" "strings" "srcd.works/go-git.v4/plumbing" + "srcd.works/go-git.v4/utils/ioutil" "srcd.works/go-billy.v1" ) @@ -25,6 +26,8 @@ const ( packPath = "pack" refsPath = "refs" + tmpPackedRefsPrefix = "._packed-refs" + packExt = ".pack" idxExt = ".idx" ) @@ -269,6 +272,21 @@ func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) { return nil, plumbing.ErrReferenceNotFound } +// RemoveRef removes a reference by name. +func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error { + path := d.fs.Join(".", name.String()) + _, err := d.fs.Stat(path) + if err == nil { + return d.fs.Remove(path) + } + + if err != nil && !os.IsNotExist(err) { + return err + } + + return d.rewritePackedRefsWithoutRef(name) +} + func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference) (err error) { f, err := d.fs.Open(packedRefsPath) if err != nil { @@ -277,12 +295,7 @@ func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference) (err error) } return err } - - defer func() { - if errClose := f.Close(); err == nil { - err = errClose - } - }() + defer ioutil.CheckClose(f, &err) s := bufio.NewScanner(f) for s.Scan() { @@ -299,8 +312,64 @@ func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference) (err error) return s.Err() } +func (d *DotGit) rewritePackedRefsWithoutRef(name plumbing.ReferenceName) (err error) { + f, err := d.fs.Open(packedRefsPath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + + return err + } + defer ioutil.CheckClose(f, &err) + + // Creating the temp file in the same directory as the target file + // improves our chances for rename operation to be atomic. + tmp, err := d.fs.TempFile("", tmpPackedRefsPrefix) + if err != nil { + return err + } + + tmpPath := tmp.Filename() + defer ioutil.CheckClose(tmp, &err) + defer d.fs.Remove(tmpPath) + + s := bufio.NewScanner(f) + found := false + for s.Scan() { + line := s.Text() + ref, err := d.processLine(line) + if err != nil { + return err + } + + if ref != nil && ref.Name() == name { + found = true + continue + } + + if _, err := fmt.Fprintln(tmp, line); err != nil { + return err + } + } + + if err := s.Err(); err != nil { + return err + } + + if !found { + return nil + } + + return d.fs.Rename(tmpPath, packedRefsPath) +} + // process lines from a packed-refs file func (d *DotGit) processLine(line string) (*plumbing.Reference, error) { + if len(line) == 0 { + return nil, nil + } + switch line[0] { case '#': // comment - ignore return nil, nil @@ -374,14 +443,9 @@ func (d *DotGit) readReferenceFile(refsPath, refFile string) (ref *plumbing.Refe if err != nil { return nil, err } + defer ioutil.CheckClose(f, &err) - defer func() { - if errClose := f.Close(); err == nil { - err = errClose - } - }() - - b, err := ioutil.ReadAll(f) + b, err := stdioutil.ReadAll(f) if err != nil { return nil, err } diff --git a/storage/filesystem/internal/dotgit/dotgit_test.go b/storage/filesystem/internal/dotgit/dotgit_test.go index a335e5f..57dfb53 100644 --- a/storage/filesystem/internal/dotgit/dotgit_test.go +++ b/storage/filesystem/internal/dotgit/dotgit_test.go @@ -1,6 +1,7 @@ package dotgit import ( + "bufio" "io/ioutil" "os" "path/filepath" @@ -108,6 +109,96 @@ func (s *SuiteDotGit) TestRefsFromReferenceFile(c *C) { } +func (s *SuiteDotGit) TestRemoveRefFromReferenceFile(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + name := plumbing.ReferenceName("refs/remotes/origin/HEAD") + err := dir.RemoveRef(name) + c.Assert(err, IsNil) + + refs, err := dir.Refs() + c.Assert(err, IsNil) + + ref := findReference(refs, string(name)) + c.Assert(ref, IsNil) +} + +func (s *SuiteDotGit) TestRemoveRefFromPackedRefs(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + name := plumbing.ReferenceName("refs/remotes/origin/master") + err := dir.RemoveRef(name) + c.Assert(err, IsNil) + + b, err := ioutil.ReadFile(filepath.Join(fs.Base(), packedRefsPath)) + c.Assert(err, IsNil) + + c.Assert(string(b), Equals, ""+ + "# pack-refs with: peeled fully-peeled \n"+ + "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 refs/heads/master\n"+ + "e8d3ffab552895c19b9fcf7aa264d277cde33881 refs/remotes/origin/branch\n") +} + +func (s *SuiteDotGit) TestRemoveRefNonExistent(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + packedRefs := filepath.Join(fs.Base(), packedRefsPath) + before, err := ioutil.ReadFile(packedRefs) + c.Assert(err, IsNil) + + name := plumbing.ReferenceName("refs/heads/nonexistent") + err = dir.RemoveRef(name) + c.Assert(err, IsNil) + + after, err := ioutil.ReadFile(packedRefs) + c.Assert(err, IsNil) + + c.Assert(string(before), Equals, string(after)) +} + +func (s *SuiteDotGit) TestRemoveRefInvalidPackedRefs(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + packedRefs := filepath.Join(fs.Base(), packedRefsPath) + brokenContent := "BROKEN STUFF REALLY BROKEN" + + err := ioutil.WriteFile(packedRefs, []byte(brokenContent), os.FileMode(0755)) + c.Assert(err, IsNil) + + name := plumbing.ReferenceName("refs/heads/nonexistent") + err = dir.RemoveRef(name) + c.Assert(err, NotNil) + + after, err := ioutil.ReadFile(filepath.Join(fs.Base(), packedRefsPath)) + c.Assert(err, IsNil) + + c.Assert(brokenContent, Equals, string(after)) +} + +func (s *SuiteDotGit) TestRemoveRefInvalidPackedRefs2(c *C) { + fs := fixtures.Basic().ByTag(".git").One().DotGit() + dir := New(fs) + + packedRefs := filepath.Join(fs.Base(), packedRefsPath) + brokenContent := strings.Repeat("a", bufio.MaxScanTokenSize*2) + + err := ioutil.WriteFile(packedRefs, []byte(brokenContent), os.FileMode(0755)) + c.Assert(err, IsNil) + + name := plumbing.ReferenceName("refs/heads/nonexistent") + err = dir.RemoveRef(name) + c.Assert(err, NotNil) + + after, err := ioutil.ReadFile(filepath.Join(fs.Base(), packedRefsPath)) + c.Assert(err, IsNil) + + c.Assert(brokenContent, Equals, string(after)) +} + func (s *SuiteDotGit) TestRefsFromHEADFile(c *C) { fs := fixtures.Basic().ByTag(".git").One().DotGit() dir := New(fs) @@ -343,3 +434,11 @@ func (s *SuiteDotGit) TestObjectNotFound(c *C) { c.Assert(err, NotNil) c.Assert(file, IsNil) } + +func (s *SuiteDotGit) TestSubmodules(c *C) { + fs := fixtures.ByTag("submodule").One().DotGit() + dir := New(fs) + + m := dir.Module("basic") + c.Assert(strings.HasSuffix(m.Base(), ".git/module/basic"), Equals, true) +} diff --git a/storage/filesystem/reference.go b/storage/filesystem/reference.go index ee87830..cff00c9 100644 --- a/storage/filesystem/reference.go +++ b/storage/filesystem/reference.go @@ -26,3 +26,7 @@ func (r *ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { return storer.NewReferenceSliceIter(refs), nil } + +func (r *ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { + return r.dir.RemoveRef(n) +} diff --git a/storage/memory/storage.go b/storage/memory/storage.go index fa9a4fa..92aeec9 100644 --- a/storage/memory/storage.go +++ b/storage/memory/storage.go @@ -220,6 +220,11 @@ func (r ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { return storer.NewReferenceSliceIter(refs), nil } +func (r ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { + delete(r, n) + return nil +} + type ShallowStorage []plumbing.Hash func (s *ShallowStorage) SetShallow(commits []plumbing.Hash) error { @@ -234,9 +239,12 @@ func (s ShallowStorage) Shallow() ([]plumbing.Hash, error) { type ModuleStorage map[string]*Storage func (s ModuleStorage) Module(name string) (storage.Storer, error) { - if _, ok := s[name]; !ok { - s[name] = NewStorage() + if m, ok := s[name]; ok { + return m, nil } - return s[name], nil + m := NewStorage() + s[name] = m + + return m, nil } diff --git a/storage/storer.go b/storage/storer.go index 0a2c256..d217209 100644 --- a/storage/storer.go +++ b/storage/storer.go @@ -18,6 +18,9 @@ type Storer interface { ModuleStorer } +// ModuleStorer allows interact with the modules' Storers type ModuleStorer interface { + // Module returns a Storer reprensting a submodule, if not exists returns a + // new empty Storer is returned Module(name string) (Storer, error) } diff --git a/storage/test/storage_suite.go b/storage/test/storage_suite.go index d6c8afa..2a10c78 100644 --- a/storage/test/storage_suite.go +++ b/storage/test/storage_suite.go @@ -22,7 +22,6 @@ type Storer interface { storer.ShallowStorer storer.IndexStorer config.ConfigStorer - storage.ModuleStorer } @@ -245,6 +244,33 @@ func (s *BaseStorageSuite) TestSetReferenceAndGetReference(c *C) { c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") } +func (s *BaseStorageSuite) TestRemoveReference(c *C) { + err := s.Storer.SetReference( + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), + ) + c.Assert(err, IsNil) + + err = s.Storer.RemoveReference(plumbing.ReferenceName("foo")) + c.Assert(err, IsNil) + + _, err = s.Storer.Reference(plumbing.ReferenceName("foo")) + c.Assert(err, Equals, plumbing.ErrReferenceNotFound) +} + +func (s *BaseStorageSuite) TestRemoveReferenceNonExistent(c *C) { + err := s.Storer.SetReference( + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), + ) + c.Assert(err, IsNil) + + err = s.Storer.RemoveReference(plumbing.ReferenceName("nonexistent")) + c.Assert(err, IsNil) + + e, err := s.Storer.Reference(plumbing.ReferenceName("foo")) + c.Assert(err, IsNil) + c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") +} + func (s *BaseStorageSuite) TestGetReferenceNotFound(c *C) { r, err := s.Storer.Reference(plumbing.ReferenceName("bar")) c.Assert(err, Equals, plumbing.ErrReferenceNotFound) @@ -297,7 +323,9 @@ func (s *BaseStorageSuite) TestSetConfigAndConfig(c *C) { cfg, err := s.Storer.Config() c.Assert(err, IsNil) - c.Assert(cfg, DeepEquals, expected) + + c.Assert(cfg.Core.IsBare, DeepEquals, expected.Core.IsBare) + c.Assert(cfg.Remotes, DeepEquals, expected.Remotes) } func (s *BaseStorageSuite) TestIndex(c *C) { diff --git a/submodule.go b/submodule.go index b6cc045..e329fda 100644 --- a/submodule.go +++ b/submodule.go @@ -107,7 +107,7 @@ func (s *Submodule) Update(o *SubmoduleUpdateOptions) error { } func (s *Submodule) doRecrusiveUpdate(r *Repository, o *SubmoduleUpdateOptions) error { - if o.RecurseSubmodules == NoRecursivity { + if o.RecurseSubmodules == NoRecurseSubmodules { return nil } diff --git a/utils/merkletrie/doc.go b/utils/merkletrie/doc.go index 28ece3e..5204024 100644 --- a/utils/merkletrie/doc.go +++ b/utils/merkletrie/doc.go @@ -1,20 +1,11 @@ /* Package merkletrie provides support for n-ary trees that are at the same -time Merkle trees and Radix trees (tries), and provides an efficient -tree comparison algorithm for them. +time Merkle trees and Radix trees (tries). Git trees are Radix n-ary trees in virtue of the names of their tree entries. At the same time, git trees are Merkle trees thanks to their hashes. -When comparing git trees, the simple approach of alphabetically sorting -their elements and comparing the resulting lists is too slow as it -depends linearly on the number of files in the trees: When a directory -has lots of files but none of them has been modified, this approach is -very expensive. We can do better by prunning whole directories that -have not change, just by looking at their hashes. This package provides -the tools to do exactly that. - This package defines Merkle tries as nodes that should have: - a hash: the Merkle part of the Merkle trie @@ -28,5 +19,16 @@ their children, which is good for testing purposes. Nodes in the Merkle trie are abstracted by the Noder interface. The intended use is that git trees implements this interface, either directly or using a simple wrapper. + +This package provides an iterator for merkletries that can skip whole +directory-like noders and an efficient merkletrie comparison algorithm. + +When comparing git trees, the simple approach of alphabetically sorting +their elements and comparing the resulting lists is too slow as it +depends linearly on the number of files in the trees: When a directory +has lots of files but none of them has been modified, this approach is +very expensive. We can do better by prunning whole directories that +have not change, just by looking at their hashes. This package provides +the tools to do exactly that. */ package merkletrie diff --git a/utils/merkletrie/internal/frame/frame_test.go b/utils/merkletrie/internal/frame/frame_test.go index 9cc0994..516d78b 100644 --- a/utils/merkletrie/internal/frame/frame_test.go +++ b/utils/merkletrie/internal/frame/frame_test.go @@ -89,20 +89,13 @@ func checkFirstAndDrop(c *C, f *Frame, expectedNodeName string, expectedOK bool) } // a mock noder that returns error when Children() is called -type errorNoder struct{} +type errorNoder struct{ noder.Noder } -func (e *errorNoder) Hash() []byte { return nil } -func (e *errorNoder) Name() string { return "" } -func (e *errorNoder) String() string { return "" } -func (e *errorNoder) IsDir() bool { return true } func (e *errorNoder) Children() ([]noder.Noder, error) { return nil, fmt.Errorf("mock error") } -func (e *errorNoder) NumChildren() (int, error) { - return 0, fmt.Errorf("mock error") -} func (s *FrameSuite) TestNewFrameErrors(c *C) { _, err := New(&errorNoder{}) - c.Assert(err, Not(IsNil)) + c.Assert(err, ErrorMatches, "mock error") } diff --git a/utils/merkletrie/iter_test.go b/utils/merkletrie/iter_test.go index 52d567a..fa7c5f5 100644 --- a/utils/merkletrie/iter_test.go +++ b/utils/merkletrie/iter_test.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "strings" - "testing" "srcd.works/go-git.v4/utils/merkletrie" "srcd.works/go-git.v4/utils/merkletrie/internal/fsnoder" @@ -13,8 +12,6 @@ import ( . "gopkg.in/check.v1" ) -func Test(t *testing.T) { TestingT(t) } - type IterSuite struct{} var _ = Suite(&IterSuite{}) @@ -443,20 +440,13 @@ func find(c *C, tree noder.Noder, name string) noder.Path { } } -type errorNoder struct{} +type errorNoder struct{ noder.Noder } -func (e *errorNoder) Name() string { return "" } -func (e *errorNoder) String() string { return "" } -func (e *errorNoder) Hash() []byte { return nil } -func (e *errorNoder) IsDir() bool { return true } func (e *errorNoder) Children() ([]noder.Noder, error) { return nil, fmt.Errorf("mock error") } -func (e *errorNoder) NumChildren() (int, error) { - return 0, fmt.Errorf("mock error") -} func (s *IterSuite) TestNewIterFailsOnChildrenErrors(c *C) { _, err := merkletrie.NewIter(&errorNoder{}) - c.Assert(err, Not(IsNil)) + c.Assert(err, ErrorMatches, "mock error") } diff --git a/worktree.go b/worktree.go index 2514a0c..2a4e5d8 100644 --- a/worktree.go +++ b/worktree.go @@ -273,7 +273,6 @@ func (w *Worktree) readGitmodulesFile() (*config.Modules, error) { m := config.NewModules() return m, m.Unmarshal(input) - } func (w *Worktree) readIndexEntry(path string) (index.Entry, error) { |