diff options
author | zeripath <art27@cantab.net> | 2020-06-18 00:29:38 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-17 19:29:38 -0400 |
commit | 14261266901d388ef1f9709bf342e5a367824a70 (patch) | |
tree | a5c6740296078062083276228219b00be7c2a187 /vendor/github.com/go-git | |
parent | 6bf78d2b576a5caa77a670db9bbf2572d0272f25 (diff) | |
download | gitea-14261266901d388ef1f9709bf342e5a367824a70.tar.gz gitea-14261266901d388ef1f9709bf342e5a367824a70.zip |
Update to go-git v5.1.0 (#11936)
Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Diffstat (limited to 'vendor/github.com/go-git')
32 files changed, 1657 insertions, 278 deletions
diff --git a/vendor/github.com/go-git/go-git/v5/COMPATIBILITY.md b/vendor/github.com/go-git/go-git/v5/COMPATIBILITY.md index 395088b7b9..2a72b501e2 100644 --- a/vendor/github.com/go-git/go-git/v5/COMPATIBILITY.md +++ b/vendor/github.com/go-git/go-git/v5/COMPATIBILITY.md @@ -101,7 +101,7 @@ is supported by go-git. | http(s):// (smart) | ✔ | | git:// | ✔ | | ssh:// | ✔ | -| file:// | ✔ | +| file:// | partial | Warning: this is not pure Golang. This shells out to the `git` binary. | | custom | ✔ | | **other features** | | gitignore | ✔ | diff --git a/vendor/github.com/go-git/go-git/v5/README.md b/vendor/github.com/go-git/go-git/v5/README.md index bca3277a07..ff0c9b72ba 100644 --- a/vendor/github.com/go-git/go-git/v5/README.md +++ b/vendor/github.com/go-git/go-git/v5/README.md @@ -1,9 +1,9 @@ ![go-git logo](https://cdn.rawgit.com/src-d/artwork/02036484/go-git/files/go-git-github-readme-header.png) -[![GoDoc](https://godoc.org/github.com/go-git/go-git/v5?status.svg)](https://godoc.org/github.com/src-d/go-git) [![Build Status](https://github.com/go-git/go-git/workflows/Test%20&%20Coverage/badge.svg)](https://github.com/go-git/go-git/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/src-d/go-git)](https://goreportcard.com/report/github.com/src-d/go-git) +[![GoDoc](https://godoc.org/github.com/go-git/go-git/v5?status.svg)](https://pkg.go.dev/github.com/go-git/go-git/v5) [![Build Status](https://github.com/go-git/go-git/workflows/Test/badge.svg)](https://github.com/go-git/go-git/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/go-git/go-git)](https://goreportcard.com/report/github.com/go-git/go-git) *go-git* is a highly extensible git implementation library written in **pure Go**. -It can be used to manipulate git repositories at low level *(plumbing)* or high level *(porcelain)*, through an idiomatic Go API. It also supports several types of storage, such as in-memory filesystems, or custom implementations, thanks to the [`Storer`](https://godoc.org/github.com/go-git/go-git/v5/plumbing/storer) interface. +It can be used to manipulate git repositories at low level *(plumbing)* or high level *(porcelain)*, through an idiomatic Go API. It also supports several types of storage, such as in-memory filesystems, or custom implementations, thanks to the [`Storer`](https://pkg.go.dev/github.com/go-git/go-git/v5/plumbing/storer) interface. It's being actively developed since 2015 and is being used extensively by [Keybase](https://keybase.io/blog/encrypted-git-for-everyone), [Gitea](https://gitea.io/en-us/) or [Pulumi](https://github.com/search?q=org%3Apulumi+go-git&type=Code), and by many other libraries and tools. @@ -12,7 +12,7 @@ Project Status After the legal issues with the [`src-d`](https://github.com/src-d) organization, the lack of update for four months and the requirement to make a hard fork, the project is **now back to normality**. -The project is currently actively maintained by individual contributors, including several of the original authors, but also backed by a new company `gitsigth` where `go-git` is a critical component used at scale. +The project is currently actively maintained by individual contributors, including several of the original authors, but also backed by a new company, [gitsight](https://github.com/gitsight), where `go-git` is a critical component used at scale. Comparison with git @@ -37,7 +37,7 @@ import "github.com/go-git/go-git" // with go modules disabled Examples -------- -> Please note that the `CheckIfError` and `Info` functions used in the examples are from the [examples package](https://github.com/src-d/go-git/blob/master/_examples/common.go#L17) just to be used in the examples. +> Please note that the `CheckIfError` and `Info` functions used in the examples are from the [examples package](https://github.com/go-git/go-git/blob/master/_examples/common.go#L19) just to be used in the examples. ### Basic example diff --git a/vendor/github.com/go-git/go-git/v5/config/config.go b/vendor/github.com/go-git/go-git/v5/config/config.go index bec35b0c27..7d6ab5886b 100644 --- a/vendor/github.com/go-git/go-git/v5/config/config.go +++ b/vendor/github.com/go-git/go-git/v5/config/config.go @@ -5,11 +5,16 @@ import ( "bytes" "errors" "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" "sort" "strconv" "github.com/go-git/go-git/v5/internal/url" format "github.com/go-git/go-git/v5/plumbing/format/config" + "github.com/mitchellh/go-homedir" ) const ( @@ -32,6 +37,16 @@ var ( ErrRemoteConfigEmptyName = errors.New("remote config: empty name") ) +// Scope defines the scope of a config file, such as local, global or system. +type Scope int + +// Available ConfigScope's +const ( + LocalScope Scope = iota + GlobalScope + SystemScope +) + // Config contains the repository configuration // https://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES type Config struct { @@ -46,6 +61,27 @@ type Config struct { CommentChar string } + User struct { + // Name is the personal name of the author and the commiter of a commit. + Name string + // Email is the email of the author and the commiter of a commit. + Email string + } + + Author struct { + // Name is the personal name of the author of a commit. + Name string + // Email is the email of the author of a commit. + Email string + } + + Committer struct { + // Name is the personal name of the commiter of a commit. + Name string + // Email is the email of the the commiter of a commit. + Email string + } + Pack struct { // Window controls the size of the sliding window for delta // compression. The default is 10. A value of 0 turns off @@ -82,6 +118,77 @@ func NewConfig() *Config { return config } +// ReadConfig reads a config file from a io.Reader. +func ReadConfig(r io.Reader) (*Config, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + cfg := NewConfig() + if err = cfg.Unmarshal(b); err != nil { + return nil, err + } + + return cfg, nil +} + +// LoadConfig loads a config file from a given scope. The returned Config, +// contains exclusively information fom the given scope. If couldn't find a +// config file to the given scope, a empty one is returned. +func LoadConfig(scope Scope) (*Config, error) { + if scope == LocalScope { + return nil, fmt.Errorf("LocalScope should be read from the a ConfigStorer.") + } + + files, err := Paths(scope) + if err != nil { + return nil, err + } + + for _, file := range files { + f, err := os.Open(file) + if err != nil { + if os.IsNotExist(err) { + continue + } + + return nil, err + } + + defer f.Close() + return ReadConfig(f) + } + + return NewConfig(), nil +} + +// Paths returns the config file location for a given scope. +func Paths(scope Scope) ([]string, error) { + var files []string + switch scope { + case GlobalScope: + xdg := os.Getenv("XDG_CONFIG_HOME") + if xdg != "" { + files = append(files, filepath.Join(xdg, "git/config")) + } + + home, err := homedir.Dir() + if err != nil { + return nil, err + } + + files = append(files, + filepath.Join(home, ".gitconfig"), + filepath.Join(home, ".config/git/config"), + ) + case SystemScope: + files = append(files, "/etc/gitconfig") + } + + return files, nil +} + // Validate validates the fields and sets the default values. func (c *Config) Validate() error { for name, r := range c.Remotes { @@ -113,6 +220,9 @@ const ( branchSection = "branch" coreSection = "core" packSection = "pack" + userSection = "user" + authorSection = "author" + committerSection = "committer" fetchKey = "fetch" urlKey = "url" bareKey = "bare" @@ -121,6 +231,8 @@ const ( windowKey = "window" mergeKey = "merge" rebaseKey = "rebase" + nameKey = "name" + emailKey = "email" // DefaultPackWindow holds the number of previous objects used to // generate deltas. The value 10 is the same used by git command. @@ -138,6 +250,7 @@ func (c *Config) Unmarshal(b []byte) error { } c.unmarshalCore() + c.unmarshalUser() if err := c.unmarshalPack(); err != nil { return err } @@ -160,6 +273,20 @@ func (c *Config) unmarshalCore() { c.Core.CommentChar = s.Options.Get(commentCharKey) } +func (c *Config) unmarshalUser() { + s := c.Raw.Section(userSection) + c.User.Name = s.Options.Get(nameKey) + c.User.Email = s.Options.Get(emailKey) + + s = c.Raw.Section(authorSection) + c.Author.Name = s.Options.Get(nameKey) + c.Author.Email = s.Options.Get(emailKey) + + s = c.Raw.Section(committerSection) + c.Committer.Name = s.Options.Get(nameKey) + c.Committer.Email = s.Options.Get(emailKey) +} + func (c *Config) unmarshalPack() error { s := c.Raw.Section(packSection) window := s.Options.Get(windowKey) @@ -220,6 +347,7 @@ func (c *Config) unmarshalBranches() error { // Marshal returns Config encoded as a git-config file. func (c *Config) Marshal() ([]byte, error) { c.marshalCore() + c.marshalUser() c.marshalPack() c.marshalRemotes() c.marshalSubmodules() @@ -242,6 +370,35 @@ func (c *Config) marshalCore() { } } +func (c *Config) marshalUser() { + s := c.Raw.Section(userSection) + if c.User.Name != "" { + s.SetOption(nameKey, c.User.Name) + } + + if c.User.Email != "" { + s.SetOption(emailKey, c.User.Email) + } + + s = c.Raw.Section(authorSection) + if c.Author.Name != "" { + s.SetOption(nameKey, c.Author.Name) + } + + if c.Author.Email != "" { + s.SetOption(emailKey, c.Author.Email) + } + + s = c.Raw.Section(committerSection) + if c.Committer.Name != "" { + s.SetOption(nameKey, c.Committer.Name) + } + + if c.Committer.Email != "" { + s.SetOption(emailKey, c.Committer.Email) + } +} + func (c *Config) marshalPack() { s := c.Raw.Section(packSection) if c.Pack.Window != DefaultPackWindow { diff --git a/vendor/github.com/go-git/go-git/v5/config/refspec.go b/vendor/github.com/go-git/go-git/v5/config/refspec.go index 87cf2a601f..4bfaa37bbb 100644 --- a/vendor/github.com/go-git/go-git/v5/config/refspec.go +++ b/vendor/github.com/go-git/go-git/v5/config/refspec.go @@ -25,7 +25,7 @@ var ( // reference even if it isn’t a fast-forward. // eg.: "+refs/heads/*:refs/remotes/origin/*" // -// https://git-scm.com/book/es/v2/Git-Internals-The-Refspec +// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec type RefSpec string // Validate validates the RefSpec @@ -59,6 +59,11 @@ func (s RefSpec) IsDelete() bool { return s[0] == refSpecSeparator[0] } +// IsExactSHA1 returns true if the source is a SHA1 hash. +func (s RefSpec) IsExactSHA1() bool { + return plumbing.IsHash(s.Src()) +} + // Src return the src side. func (s RefSpec) Src() string { spec := string(s) @@ -69,8 +74,8 @@ func (s RefSpec) Src() string { } else { start = 0 } - end := strings.Index(spec, refSpecSeparator) + end := strings.Index(spec, refSpecSeparator) return spec[start:end] } diff --git a/vendor/github.com/go-git/go-git/v5/go.mod b/vendor/github.com/go-git/go-git/v5/go.mod index 6d8da35c32..0c9cfd2cae 100644 --- a/vendor/github.com/go-git/go-git/v5/go.mod +++ b/vendor/github.com/go-git/go-git/v5/go.mod @@ -10,6 +10,7 @@ require ( github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-git-fixtures/v4 v4.0.1 github.com/google/go-cmp v0.3.0 + github.com/imdario/mergo v0.3.9 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jessevdk/go-flags v1.4.0 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd diff --git a/vendor/github.com/go-git/go-git/v5/go.sum b/vendor/github.com/go-git/go-git/v5/go.sum index a73585e900..e14e29ae39 100644 --- a/vendor/github.com/go-git/go-git/v5/go.sum +++ b/vendor/github.com/go-git/go-git/v5/go.sum @@ -22,6 +22,8 @@ github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= diff --git a/vendor/github.com/go-git/go-git/v5/options.go b/vendor/github.com/go-git/go-git/v5/options.go index a147f58b99..5367031f42 100644 --- a/vendor/github.com/go-git/go-git/v5/options.go +++ b/vendor/github.com/go-git/go-git/v5/options.go @@ -6,12 +6,12 @@ import ( "strings" "time" - "golang.org/x/crypto/openpgp" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband" "github.com/go-git/go-git/v5/plumbing/transport" + "golang.org/x/crypto/openpgp" ) // SubmoduleRescursivity defines how depth will affect any submodule recursive @@ -190,6 +190,9 @@ type PushOptions struct { // Prune specify that remote refs that match given RefSpecs and that do // not exist locally will be removed. Prune bool + // Force allows the push to update a remote branch even when the local + // branch does not descend from it. + Force bool } // Validate validates the fields and sets the default values. @@ -375,7 +378,8 @@ type CommitOptions struct { // All automatically stage files that have been modified and deleted, but // new files you have not told Git about are not affected. All bool - // Author is the author's signature of the commit. + // Author is the author's signature of the commit. If Author is empty the + // Name and Email is read from the config, and time.Now it's used as When. Author *object.Signature // Committer is the committer's signature of the commit. If Committer is // nil the Author signature is used. @@ -392,7 +396,9 @@ type CommitOptions struct { // Validate validates the fields and sets the default values. func (o *CommitOptions) Validate(r *Repository) error { if o.Author == nil { - return ErrMissingAuthor + if err := o.loadConfigAuthorAndCommitter(r); err != nil { + return err + } } if o.Committer == nil { @@ -413,6 +419,43 @@ func (o *CommitOptions) Validate(r *Repository) error { return nil } +func (o *CommitOptions) loadConfigAuthorAndCommitter(r *Repository) error { + cfg, err := r.ConfigScoped(config.SystemScope) + if err != nil { + return err + } + + if o.Author == nil && cfg.Author.Email != "" && cfg.Author.Name != "" { + o.Author = &object.Signature{ + Name: cfg.Author.Name, + Email: cfg.Author.Email, + When: time.Now(), + } + } + + if o.Committer == nil && cfg.Committer.Email != "" && cfg.Committer.Name != "" { + o.Committer = &object.Signature{ + Name: cfg.Committer.Name, + Email: cfg.Committer.Email, + When: time.Now(), + } + } + + if o.Author == nil && cfg.User.Email != "" && cfg.User.Name != "" { + o.Author = &object.Signature{ + Name: cfg.User.Name, + Email: cfg.User.Email, + When: time.Now(), + } + } + + if o.Author == nil { + return ErrMissingAuthor + } + + return nil +} + var ( ErrMissingName = errors.New("name field is required") ErrMissingTagger = errors.New("tagger field is required") diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/color/color.go b/vendor/github.com/go-git/go-git/v5/plumbing/color/color.go new file mode 100644 index 0000000000..2cd74bdc1a --- /dev/null +++ b/vendor/github.com/go-git/go-git/v5/plumbing/color/color.go @@ -0,0 +1,38 @@ +package color + +// TODO read colors from a github.com/go-git/go-git/plumbing/format/config.Config struct +// TODO implement color parsing, see https://github.com/git/git/blob/v2.26.2/color.c + +// Colors. See https://github.com/git/git/blob/v2.26.2/color.h#L24-L53. +const ( + Normal = "" + Reset = "\033[m" + Bold = "\033[1m" + Red = "\033[31m" + Green = "\033[32m" + Yellow = "\033[33m" + Blue = "\033[34m" + Magenta = "\033[35m" + Cyan = "\033[36m" + BoldRed = "\033[1;31m" + BoldGreen = "\033[1;32m" + BoldYellow = "\033[1;33m" + BoldBlue = "\033[1;34m" + BoldMagenta = "\033[1;35m" + BoldCyan = "\033[1;36m" + FaintRed = "\033[2;31m" + FaintGreen = "\033[2;32m" + FaintYellow = "\033[2;33m" + FaintBlue = "\033[2;34m" + FaintMagenta = "\033[2;35m" + FaintCyan = "\033[2;36m" + BgRed = "\033[41m" + BgGreen = "\033[42m" + BgYellow = "\033[43m" + BgBlue = "\033[44m" + BgMagenta = "\033[45m" + BgCyan = "\033[46m" + Faint = "\033[2m" + FaintItalic = "\033[2;3m" + Reverse = "\033[7m" +) diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/format/diff/colorconfig.go b/vendor/github.com/go-git/go-git/v5/plumbing/format/diff/colorconfig.go new file mode 100644 index 0000000000..6fd4158462 --- /dev/null +++ b/vendor/github.com/go-git/go-git/v5/plumbing/format/diff/colorconfig.go @@ -0,0 +1,97 @@ +package diff + +import "github.com/go-git/go-git/v5/plumbing/color" + +// A ColorKey is a key into a ColorConfig map and also equal to the key in the +// diff.color subsection of the config. See +// https://github.com/git/git/blob/v2.26.2/diff.c#L83-L106. +type ColorKey string + +// ColorKeys. +const ( + Context ColorKey = "context" + Meta ColorKey = "meta" + Frag ColorKey = "frag" + Old ColorKey = "old" + New ColorKey = "new" + Commit ColorKey = "commit" + Whitespace ColorKey = "whitespace" + Func ColorKey = "func" + OldMoved ColorKey = "oldMoved" + OldMovedAlternative ColorKey = "oldMovedAlternative" + OldMovedDimmed ColorKey = "oldMovedDimmed" + OldMovedAlternativeDimmed ColorKey = "oldMovedAlternativeDimmed" + NewMoved ColorKey = "newMoved" + NewMovedAlternative ColorKey = "newMovedAlternative" + NewMovedDimmed ColorKey = "newMovedDimmed" + NewMovedAlternativeDimmed ColorKey = "newMovedAlternativeDimmed" + ContextDimmed ColorKey = "contextDimmed" + OldDimmed ColorKey = "oldDimmed" + NewDimmed ColorKey = "newDimmed" + ContextBold ColorKey = "contextBold" + OldBold ColorKey = "oldBold" + NewBold ColorKey = "newBold" +) + +// A ColorConfig is a color configuration. A nil or empty ColorConfig +// corresponds to no color. +type ColorConfig map[ColorKey]string + +// A ColorConfigOption sets an option on a ColorConfig. +type ColorConfigOption func(ColorConfig) + +// WithColor sets the color for key. +func WithColor(key ColorKey, color string) ColorConfigOption { + return func(cc ColorConfig) { + cc[key] = color + } +} + +// defaultColorConfig is the default color configuration. See +// https://github.com/git/git/blob/v2.26.2/diff.c#L57-L81. +var defaultColorConfig = ColorConfig{ + Context: color.Normal, + Meta: color.Bold, + Frag: color.Cyan, + Old: color.Red, + New: color.Green, + Commit: color.Yellow, + Whitespace: color.BgRed, + Func: color.Normal, + OldMoved: color.BoldMagenta, + OldMovedAlternative: color.BoldBlue, + OldMovedDimmed: color.Faint, + OldMovedAlternativeDimmed: color.FaintItalic, + NewMoved: color.BoldCyan, + NewMovedAlternative: color.BoldYellow, + NewMovedDimmed: color.Faint, + NewMovedAlternativeDimmed: color.FaintItalic, + ContextDimmed: color.Faint, + OldDimmed: color.FaintRed, + NewDimmed: color.FaintGreen, + ContextBold: color.Bold, + OldBold: color.BoldRed, + NewBold: color.BoldGreen, +} + +// NewColorConfig returns a new ColorConfig. +func NewColorConfig(options ...ColorConfigOption) ColorConfig { + cc := make(ColorConfig) + for key, value := range defaultColorConfig { + cc[key] = value + } + for _, option := range options { + option(cc) + } + return cc +} + +// Reset returns the ANSI escape sequence to reset the color with key set from +// cc. If no color was set then no reset is needed so it returns the empty +// string. +func (cc ColorConfig) Reset(key ColorKey) string { + if cc[key] == "" { + return "" + } + return color.Reset +} diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/format/diff/unified_encoder.go b/vendor/github.com/go-git/go-git/v5/plumbing/format/diff/unified_encoder.go index f2bc910a25..413984aa54 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/format/diff/unified_encoder.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/format/diff/unified_encoder.go @@ -1,157 +1,158 @@ package diff import ( - "bytes" "fmt" "io" "regexp" + "strconv" "strings" "github.com/go-git/go-git/v5/plumbing" ) -const ( - diffInit = "diff --git a/%s b/%s\n" +// DefaultContextLines is the default number of context lines. +const DefaultContextLines = 3 - chunkStart = "@@ -" - chunkMiddle = " +" - chunkEnd = " @@%s\n" - chunkCount = "%d,%d" +var ( + splitLinesRegexp = regexp.MustCompile(`[^\n]*(\n|$)`) - noFilePath = "/dev/null" - aDir = "a/" - bDir = "b/" - - fPath = "--- %s\n" - tPath = "+++ %s\n" - binary = "Binary files %s and %s differ\n" - - addLine = "+%s%s" - deleteLine = "-%s%s" - equalLine = " %s%s" - noNewLine = "\n\\ No newline at end of file\n" - - oldMode = "old mode %o\n" - newMode = "new mode %o\n" - deletedFileMode = "deleted file mode %o\n" - newFileMode = "new file mode %o\n" - - renameFrom = "from" - renameTo = "to" - renameFileMode = "rename %s %s\n" - - indexAndMode = "index %s..%s %o\n" - indexNoMode = "index %s..%s\n" + operationChar = map[Operation]byte{ + Add: '+', + Delete: '-', + Equal: ' ', + } - DefaultContextLines = 3 + operationColorKey = map[Operation]ColorKey{ + Add: New, + Delete: Old, + Equal: Context, + } ) -// UnifiedEncoder encodes an unified diff into the provided Writer. -// There are some unsupported features: -// - Similarity index for renames -// - Sort hash representation +// UnifiedEncoder encodes an unified diff into the provided Writer. It does not +// support similarity index for renames or sorting hash representations. type UnifiedEncoder struct { io.Writer - // ctxLines is the count of unchanged lines that will appear - // surrounding a change. - ctxLines int + // contextLines is the count of unchanged lines that will appear surrounding + // a change. + contextLines int - buf bytes.Buffer + // colorConfig is the color configuration. The default is no color. + color ColorConfig } -func NewUnifiedEncoder(w io.Writer, ctxLines int) *UnifiedEncoder { - return &UnifiedEncoder{ctxLines: ctxLines, Writer: w} -} - -func (e *UnifiedEncoder) Encode(patch Patch) error { - e.printMessage(patch.Message()) - - if err := e.encodeFilePatch(patch.FilePatches()); err != nil { - return err +// NewUnifiedEncoder returns a new UnifiedEncoder that writes to w. +func NewUnifiedEncoder(w io.Writer, contextLines int) *UnifiedEncoder { + return &UnifiedEncoder{ + Writer: w, + contextLines: contextLines, } +} - _, err := e.buf.WriteTo(e) - - return err +// SetColor sets e's color configuration and returns e. +func (e *UnifiedEncoder) SetColor(colorConfig ColorConfig) *UnifiedEncoder { + e.color = colorConfig + return e } -func (e *UnifiedEncoder) encodeFilePatch(filePatches []FilePatch) error { - for _, p := range filePatches { - f, t := p.Files() - if err := e.header(f, t, p.IsBinary()); err != nil { - return err +// Encode encodes patch. +func (e *UnifiedEncoder) Encode(patch Patch) error { + sb := &strings.Builder{} + + if message := patch.Message(); message != "" { + sb.WriteString(message) + if !strings.HasSuffix(message, "\n") { + sb.WriteByte('\n') } + } - g := newHunksGenerator(p.Chunks(), e.ctxLines) - for _, c := range g.Generate() { - c.WriteTo(&e.buf) + for _, filePatch := range patch.FilePatches() { + e.writeFilePatchHeader(sb, filePatch) + g := newHunksGenerator(filePatch.Chunks(), e.contextLines) + for _, hunk := range g.Generate() { + hunk.writeTo(sb, e.color) } } - return nil + _, err := e.Write([]byte(sb.String())) + return err } -func (e *UnifiedEncoder) printMessage(message string) { - isEmpty := message == "" - hasSuffix := strings.HasSuffix(message, "\n") - if !isEmpty && !hasSuffix { - message += "\n" +func (e *UnifiedEncoder) writeFilePatchHeader(sb *strings.Builder, filePatch FilePatch) { + from, to := filePatch.Files() + if from == nil && to == nil { + return } + isBinary := filePatch.IsBinary() - e.buf.WriteString(message) -} - -func (e *UnifiedEncoder) header(from, to File, isBinary bool) error { + var lines []string switch { - case from == nil && to == nil: - return nil case from != nil && to != nil: hashEquals := from.Hash() == to.Hash() - - fmt.Fprintf(&e.buf, diffInit, from.Path(), to.Path()) - + lines = append(lines, + fmt.Sprintf("diff --git a/%s b/%s", from.Path(), to.Path()), + ) if from.Mode() != to.Mode() { - fmt.Fprintf(&e.buf, oldMode+newMode, from.Mode(), to.Mode()) + lines = append(lines, + fmt.Sprintf("old mode %o", from.Mode()), + fmt.Sprintf("new mode %o", to.Mode()), + ) } - if from.Path() != to.Path() { - fmt.Fprintf(&e.buf, - renameFileMode+renameFileMode, - renameFrom, from.Path(), renameTo, to.Path()) + lines = append(lines, + fmt.Sprintf("rename from %s", from.Path()), + fmt.Sprintf("rename to %s", to.Path()), + ) } - if from.Mode() != to.Mode() && !hashEquals { - fmt.Fprintf(&e.buf, indexNoMode, from.Hash(), to.Hash()) + lines = append(lines, + fmt.Sprintf("index %s..%s", from.Hash(), to.Hash()), + ) } else if !hashEquals { - fmt.Fprintf(&e.buf, indexAndMode, from.Hash(), to.Hash(), from.Mode()) + lines = append(lines, + fmt.Sprintf("index %s..%s %o", from.Hash(), to.Hash(), from.Mode()), + ) } - if !hashEquals { - e.pathLines(isBinary, aDir+from.Path(), bDir+to.Path()) + lines = e.appendPathLines(lines, "a/"+from.Path(), "b/"+to.Path(), isBinary) } case from == nil: - fmt.Fprintf(&e.buf, diffInit, to.Path(), to.Path()) - fmt.Fprintf(&e.buf, newFileMode, to.Mode()) - fmt.Fprintf(&e.buf, indexNoMode, plumbing.ZeroHash, to.Hash()) - e.pathLines(isBinary, noFilePath, bDir+to.Path()) + lines = append(lines, + fmt.Sprintf("diff --git a/%s b/%s", to.Path(), to.Path()), + fmt.Sprintf("new file mode %o", to.Mode()), + fmt.Sprintf("index %s..%s", plumbing.ZeroHash, to.Hash()), + ) + lines = e.appendPathLines(lines, "/dev/null", "b/"+to.Path(), isBinary) case to == nil: - fmt.Fprintf(&e.buf, diffInit, from.Path(), from.Path()) - fmt.Fprintf(&e.buf, deletedFileMode, from.Mode()) - fmt.Fprintf(&e.buf, indexNoMode, from.Hash(), plumbing.ZeroHash) - e.pathLines(isBinary, aDir+from.Path(), noFilePath) + lines = append(lines, + fmt.Sprintf("diff --git a/%s b/%s", from.Path(), from.Path()), + fmt.Sprintf("deleted file mode %o", from.Mode()), + fmt.Sprintf("index %s..%s", from.Hash(), plumbing.ZeroHash), + ) + lines = e.appendPathLines(lines, "a/"+from.Path(), "/dev/null", isBinary) } - return nil + sb.WriteString(e.color[Meta]) + sb.WriteString(lines[0]) + for _, line := range lines[1:] { + sb.WriteByte('\n') + sb.WriteString(line) + } + sb.WriteString(e.color.Reset(Meta)) + sb.WriteByte('\n') } -func (e *UnifiedEncoder) pathLines(isBinary bool, fromPath, toPath string) { - format := fPath + tPath +func (e *UnifiedEncoder) appendPathLines(lines []string, fromPath, toPath string, isBinary bool) []string { if isBinary { - format = binary + return append(lines, + fmt.Sprintf("Binary files %s and %s differ", fromPath, toPath), + ) } - - fmt.Fprintf(&e.buf, format, fromPath, toPath) + return append(lines, + fmt.Sprintf("--- %s", fromPath), + fmt.Sprintf("+++ %s", toPath), + ) } type hunksGenerator struct { @@ -170,84 +171,84 @@ func newHunksGenerator(chunks []Chunk, ctxLines int) *hunksGenerator { } } -func (c *hunksGenerator) Generate() []*hunk { - for i, chunk := range c.chunks { - ls := splitLines(chunk.Content()) - lsLen := len(ls) +func (g *hunksGenerator) Generate() []*hunk { + for i, chunk := range g.chunks { + lines := splitLines(chunk.Content()) + nLines := len(lines) switch chunk.Type() { case Equal: - c.fromLine += lsLen - c.toLine += lsLen - c.processEqualsLines(ls, i) + g.fromLine += nLines + g.toLine += nLines + g.processEqualsLines(lines, i) case Delete: - if lsLen != 0 { - c.fromLine++ + if nLines != 0 { + g.fromLine++ } - c.processHunk(i, chunk.Type()) - c.fromLine += lsLen - 1 - c.current.AddOp(chunk.Type(), ls...) + g.processHunk(i, chunk.Type()) + g.fromLine += nLines - 1 + g.current.AddOp(chunk.Type(), lines...) case Add: - if lsLen != 0 { - c.toLine++ + if nLines != 0 { + g.toLine++ } - c.processHunk(i, chunk.Type()) - c.toLine += lsLen - 1 - c.current.AddOp(chunk.Type(), ls...) + g.processHunk(i, chunk.Type()) + g.toLine += nLines - 1 + g.current.AddOp(chunk.Type(), lines...) } - if i == len(c.chunks)-1 && c.current != nil { - c.hunks = append(c.hunks, c.current) + if i == len(g.chunks)-1 && g.current != nil { + g.hunks = append(g.hunks, g.current) } } - return c.hunks + return g.hunks } -func (c *hunksGenerator) processHunk(i int, op Operation) { - if c.current != nil { +func (g *hunksGenerator) processHunk(i int, op Operation) { + if g.current != nil { return } var ctxPrefix string - linesBefore := len(c.beforeContext) - if linesBefore > c.ctxLines { - ctxPrefix = " " + c.beforeContext[linesBefore-c.ctxLines-1] - c.beforeContext = c.beforeContext[linesBefore-c.ctxLines:] - linesBefore = c.ctxLines + linesBefore := len(g.beforeContext) + if linesBefore > g.ctxLines { + ctxPrefix = g.beforeContext[linesBefore-g.ctxLines-1] + g.beforeContext = g.beforeContext[linesBefore-g.ctxLines:] + linesBefore = g.ctxLines } - c.current = &hunk{ctxPrefix: strings.TrimSuffix(ctxPrefix, "\n")} - c.current.AddOp(Equal, c.beforeContext...) + g.current = &hunk{ctxPrefix: strings.TrimSuffix(ctxPrefix, "\n")} + g.current.AddOp(Equal, g.beforeContext...) switch op { case Delete: - c.current.fromLine, c.current.toLine = - c.addLineNumbers(c.fromLine, c.toLine, linesBefore, i, Add) + g.current.fromLine, g.current.toLine = + g.addLineNumbers(g.fromLine, g.toLine, linesBefore, i, Add) case Add: - c.current.toLine, c.current.fromLine = - c.addLineNumbers(c.toLine, c.fromLine, linesBefore, i, Delete) + g.current.toLine, g.current.fromLine = + g.addLineNumbers(g.toLine, g.fromLine, linesBefore, i, Delete) } - c.beforeContext = nil + g.beforeContext = nil } -// addLineNumbers obtains the line numbers in a new chunk -func (c *hunksGenerator) addLineNumbers(la, lb int, linesBefore int, i int, op Operation) (cla, clb int) { +// addLineNumbers obtains the line numbers in a new chunk. +func (g *hunksGenerator) addLineNumbers(la, lb int, linesBefore int, i int, op Operation) (cla, clb int) { cla = la - linesBefore // we need to search for a reference for the next diff switch { - case linesBefore != 0 && c.ctxLines != 0: - if lb > c.ctxLines { - clb = lb - c.ctxLines + 1 + case linesBefore != 0 && g.ctxLines != 0: + if lb > g.ctxLines { + clb = lb - g.ctxLines + 1 } else { clb = 1 } - case c.ctxLines == 0: + case g.ctxLines == 0: clb = lb - case i != len(c.chunks)-1: - next := c.chunks[i+1] + case i != len(g.chunks)-1: + next := g.chunks[i+1] if next.Type() == op || next.Type() == Equal { // this diff will be into this chunk clb = lb + 1 @@ -257,34 +258,32 @@ func (c *hunksGenerator) addLineNumbers(la, lb int, linesBefore int, i int, op O return } -func (c *hunksGenerator) processEqualsLines(ls []string, i int) { - if c.current == nil { - c.beforeContext = append(c.beforeContext, ls...) +func (g *hunksGenerator) processEqualsLines(ls []string, i int) { + if g.current == nil { + g.beforeContext = append(g.beforeContext, ls...) return } - c.afterContext = append(c.afterContext, ls...) - if len(c.afterContext) <= c.ctxLines*2 && i != len(c.chunks)-1 { - c.current.AddOp(Equal, c.afterContext...) - c.afterContext = nil + g.afterContext = append(g.afterContext, ls...) + if len(g.afterContext) <= g.ctxLines*2 && i != len(g.chunks)-1 { + g.current.AddOp(Equal, g.afterContext...) + g.afterContext = nil } else { - ctxLines := c.ctxLines - if ctxLines > len(c.afterContext) { - ctxLines = len(c.afterContext) + ctxLines := g.ctxLines + if ctxLines > len(g.afterContext) { + ctxLines = len(g.afterContext) } - c.current.AddOp(Equal, c.afterContext[:ctxLines]...) - c.hunks = append(c.hunks, c.current) + g.current.AddOp(Equal, g.afterContext[:ctxLines]...) + g.hunks = append(g.hunks, g.current) - c.current = nil - c.beforeContext = c.afterContext[ctxLines:] - c.afterContext = nil + g.current = nil + g.beforeContext = g.afterContext[ctxLines:] + g.afterContext = nil } } -var splitLinesRE = regexp.MustCompile(`[^\n]*(\n|$)`) - func splitLines(s string) []string { - out := splitLinesRE.FindAllString(s, -1) + out := splitLinesRegexp.FindAllString(s, -1) if out[len(out)-1] == "" { out = out[:len(out)-1] } @@ -302,44 +301,59 @@ type hunk struct { ops []*op } -func (c *hunk) WriteTo(buf *bytes.Buffer) { - buf.WriteString(chunkStart) +func (h *hunk) writeTo(sb *strings.Builder, color ColorConfig) { + sb.WriteString(color[Frag]) + sb.WriteString("@@ -") - if c.fromCount == 1 { - fmt.Fprintf(buf, "%d", c.fromLine) + if h.fromCount == 1 { + sb.WriteString(strconv.Itoa(h.fromLine)) } else { - fmt.Fprintf(buf, chunkCount, c.fromLine, c.fromCount) + sb.WriteString(strconv.Itoa(h.fromLine)) + sb.WriteByte(',') + sb.WriteString(strconv.Itoa(h.fromCount)) } - buf.WriteString(chunkMiddle) + sb.WriteString(" +") - if c.toCount == 1 { - fmt.Fprintf(buf, "%d", c.toLine) + if h.toCount == 1 { + sb.WriteString(strconv.Itoa(h.toLine)) } else { - fmt.Fprintf(buf, chunkCount, c.toLine, c.toCount) + sb.WriteString(strconv.Itoa(h.toLine)) + sb.WriteByte(',') + sb.WriteString(strconv.Itoa(h.toCount)) } - fmt.Fprintf(buf, chunkEnd, c.ctxPrefix) + sb.WriteString(" @@") + sb.WriteString(color.Reset(Frag)) - for _, d := range c.ops { - buf.WriteString(d.String()) + if h.ctxPrefix != "" { + sb.WriteByte(' ') + sb.WriteString(color[Func]) + sb.WriteString(h.ctxPrefix) + sb.WriteString(color.Reset(Func)) + } + + sb.WriteByte('\n') + + for _, op := range h.ops { + op.writeTo(sb, color) } } -func (c *hunk) AddOp(t Operation, s ...string) { - ls := len(s) +func (h *hunk) AddOp(t Operation, ss ...string) { + n := len(ss) switch t { case Add: - c.toCount += ls + h.toCount += n case Delete: - c.fromCount += ls + h.fromCount += n case Equal: - c.toCount += ls - c.fromCount += ls + h.toCount += n + h.fromCount += n } - for _, l := range s { - c.ops = append(c.ops, &op{l, t}) + for _, s := range ss { + h.ops = append(h.ops, &op{s, t}) } } @@ -348,20 +362,15 @@ type op struct { t Operation } -func (o *op) String() string { - var prefix, suffix string - switch o.t { - case Add: - prefix = addLine - case Delete: - prefix = deleteLine - case Equal: - prefix = equalLine - } - n := len(o.text) - if n > 0 && o.text[n-1] != '\n' { - suffix = noNewLine +func (o *op) writeTo(sb *strings.Builder, color ColorConfig) { + colorKey := operationColorKey[o.t] + sb.WriteString(color[colorKey]) + sb.WriteByte(operationChar[o.t]) + if strings.HasSuffix(o.text, "\n") { + sb.WriteString(strings.TrimSuffix(o.text, "\n")) + } else { + sb.WriteString(o.text + "\n\\ No newline at end of file") } - - return fmt.Sprintf(prefix, o.text, suffix) + sb.WriteString(color.Reset(colorKey)) + sb.WriteByte('\n') } diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/diff_delta.go b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/diff_delta.go index 1d4b384a77..1951b34ef1 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/diff_delta.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/diff_delta.go @@ -4,6 +4,7 @@ import ( "bytes" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/utils/ioutil" ) // See https://github.com/jelmer/dulwich/blob/master/dulwich/pack.py and @@ -27,17 +28,20 @@ func GetDelta(base, target plumbing.EncodedObject) (plumbing.EncodedObject, erro return getDelta(new(deltaIndex), base, target) } -func getDelta(index *deltaIndex, base, target plumbing.EncodedObject) (plumbing.EncodedObject, error) { +func getDelta(index *deltaIndex, base, target plumbing.EncodedObject) (o plumbing.EncodedObject, err error) { br, err := base.Reader() if err != nil { return nil, err } - defer br.Close() + + defer ioutil.CheckClose(br, &err) + tr, err := target.Reader() if err != nil { return nil, err } - defer tr.Close() + + defer ioutil.CheckClose(tr, &err) bb := bufPool.Get().(*bytes.Buffer) defer bufPool.Put(bb) diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/encoder.go b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/encoder.go index 65fae523ae..5501f8861c 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/encoder.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/encoder.go @@ -9,6 +9,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/binary" + "github.com/go-git/go-git/v5/utils/ioutil" ) // Encoder gets the data from the storage and write it into the writer in PACK @@ -80,7 +81,7 @@ func (e *Encoder) head(numEntries int) error { ) } -func (e *Encoder) entry(o *ObjectToPack) error { +func (e *Encoder) entry(o *ObjectToPack) (err error) { if o.WantWrite() { // A cycle exists in this delta chain. This should only occur if a // selected object representation disappeared during writing @@ -119,17 +120,22 @@ func (e *Encoder) entry(o *ObjectToPack) error { } e.zw.Reset(e.w) + + defer ioutil.CheckClose(e.zw, &err) + or, err := o.Object.Reader() if err != nil { return err } + defer ioutil.CheckClose(or, &err) + _, err = io.Copy(e.zw, or) if err != nil { return err } - return e.zw.Close() + return nil } func (e *Encoder) writeBaseIfDelta(o *ObjectToPack) error { diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/packfile.go b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/packfile.go index d060041975..ddd7f62fce 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/packfile.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/packfile.go @@ -10,6 +10,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/format/idxfile" "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/go-git/go-git/v5/utils/ioutil" ) var ( @@ -307,12 +308,14 @@ func (p *Packfile) getNextMemoryObject(h *ObjectHeader) (plumbing.EncodedObject, return obj, nil } -func (p *Packfile) fillRegularObjectContent(obj plumbing.EncodedObject) error { +func (p *Packfile) fillRegularObjectContent(obj plumbing.EncodedObject) (err error) { w, err := obj.Writer() if err != nil { return err } + defer ioutil.CheckClose(w, &err) + _, _, err = p.s.NextObject(w) p.cachePut(obj) diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/parser.go b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/parser.go index d411c5b57a..4b5a5708cc 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/parser.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/parser.go @@ -4,11 +4,12 @@ import ( "bytes" "errors" "io" - "io/ioutil" + stdioutil "io/ioutil" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/go-git/go-git/v5/utils/ioutil" ) var ( @@ -283,7 +284,7 @@ func (p *Parser) resolveDeltas() error { if !obj.IsDelta() && len(obj.Children) > 0 { for _, child := range obj.Children { - if err := p.resolveObject(ioutil.Discard, child, content); err != nil { + if err := p.resolveObject(stdioutil.Discard, child, content); err != nil { return err } } @@ -298,7 +299,7 @@ func (p *Parser) resolveDeltas() error { return nil } -func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) error { +func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { if !o.ExternalRef { // skip cache check for placeholder parents b, ok := p.cache.Get(o.Offset) if ok { @@ -310,17 +311,21 @@ func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) error { // If it's not on the cache and is not a delta we can try to find it in the // storage, if there's one. External refs must enter here. if p.storage != nil && !o.Type.IsDelta() { - e, err := p.storage.EncodedObject(plumbing.AnyObject, o.SHA1) + var e plumbing.EncodedObject + e, err = p.storage.EncodedObject(plumbing.AnyObject, o.SHA1) if err != nil { return err } o.Type = e.Type() - r, err := e.Reader() + var r io.ReadCloser + r, err = e.Reader() if err != nil { return err } + defer ioutil.CheckClose(r, &err) + _, err = buf.ReadFrom(io.LimitReader(r, e.Size())) return err } diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/patch_delta.go b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/patch_delta.go index 57c9da7ce8..1dc8b8b009 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/patch_delta.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/patch_delta.go @@ -6,6 +6,7 @@ import ( "io" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/utils/ioutil" ) // See https://github.com/git/git/blob/49fa3dc76179e04b0833542fa52d0f287a4955ac/delta.h @@ -16,17 +17,21 @@ import ( const deltaSizeMin = 4 // ApplyDelta writes to target the result of applying the modification deltas in delta to base. -func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) error { +func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) { r, err := base.Reader() if err != nil { return err } + defer ioutil.CheckClose(r, &err) + w, err := target.Writer() if err != nil { return err } + defer ioutil.CheckClose(w, &err) + buf := bufPool.Get().(*bytes.Buffer) defer bufPool.Put(buf) buf.Reset() diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/hash.go b/vendor/github.com/go-git/go-git/v5/plumbing/hash.go index 637a4252ad..afc602a9ec 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/hash.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/hash.go @@ -71,3 +71,13 @@ type HashSlice []Hash func (p HashSlice) Len() int { return len(p) } func (p HashSlice) Less(i, j int) bool { return bytes.Compare(p[i][:], p[j][:]) < 0 } func (p HashSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// IsHash returns true if the given string is a valid hash. +func IsHash(s string) bool { + if len(s) != 40 { + return false + } + + _, err := hex.DecodeString(s) + return err == nil +} diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/object/change.go b/vendor/github.com/go-git/go-git/v5/plumbing/object/change.go index 4474905e0a..c9d1615089 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/object/change.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/object/change.go @@ -18,7 +18,7 @@ type Change struct { To ChangeEntry } -var empty = ChangeEntry{} +var empty ChangeEntry // Action returns the kind of action represented by the change, an // insertion, a deletion or a modification. @@ -27,9 +27,11 @@ func (c *Change) Action() (merkletrie.Action, error) { return merkletrie.Action(0), fmt.Errorf("malformed change: empty from and to") } + if c.From == empty { return merkletrie.Insert, nil } + if c.To == empty { return merkletrie.Delete, nil } diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/object/commit.go b/vendor/github.com/go-git/go-git/v5/plumbing/object/commit.go index b37ff61d33..113cb29e54 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/object/commit.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/object/commit.go @@ -78,21 +78,30 @@ func (c *Commit) Tree() (*Tree, error) { // PatchContext returns the Patch between the actual commit and the provided one. // Error will be return if context expires. Provided context must be non-nil. +// +// NOTE: Since version 5.1.0 the renames are correctly handled, the settings +// used are the recommended options DefaultDiffTreeOptions. func (c *Commit) PatchContext(ctx context.Context, to *Commit) (*Patch, error) { fromTree, err := c.Tree() if err != nil { return nil, err } - toTree, err := to.Tree() - if err != nil { - return nil, err + var toTree *Tree + if to != nil { + toTree, err = to.Tree() + if err != nil { + return nil, err + } } return fromTree.PatchContext(ctx, toTree) } // Patch returns the Patch between the actual commit and the provided one. +// +// NOTE: Since version 5.1.0 the renames are correctly handled, the settings +// used are the recommended options DefaultDiffTreeOptions. func (c *Commit) Patch(to *Commit) (*Patch, error) { return c.PatchContext(context.Background(), to) } diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/object/difftree.go b/vendor/github.com/go-git/go-git/v5/plumbing/object/difftree.go index 72411a590e..7c2222702c 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/object/difftree.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/object/difftree.go @@ -10,14 +10,62 @@ import ( // DiffTree compares the content and mode of the blobs found via two // tree objects. +// DiffTree does not perform rename detection, use DiffTreeWithOptions +// instead to detect renames. func DiffTree(a, b *Tree) (Changes, error) { return DiffTreeContext(context.Background(), a, b) } -// DiffTree compares the content and mode of the blobs found via two +// DiffTreeContext compares the content and mode of the blobs found via two // tree objects. Provided context must be non-nil. -// An error will be return if context expires +// An error will be returned if context expires. func DiffTreeContext(ctx context.Context, a, b *Tree) (Changes, error) { + return DiffTreeWithOptions(ctx, a, b, nil) +} + +// DiffTreeOptions are the configurable options when performing a diff tree. +type DiffTreeOptions struct { + // DetectRenames is whether the diff tree will use rename detection. + DetectRenames bool + // RenameScore is the threshold to of similarity between files to consider + // that a pair of delete and insert are a rename. The number must be + // exactly between 0 and 100. + RenameScore uint + // RenameLimit is the maximum amount of files that can be compared when + // detecting renames. The number of comparisons that have to be performed + // is equal to the number of deleted files * the number of added files. + // That means, that if 100 files were deleted and 50 files were added, 5000 + // file comparisons may be needed. So, if the rename limit is 50, the number + // of both deleted and added needs to be equal or less than 50. + // A value of 0 means no limit. + RenameLimit uint + // OnlyExactRenames performs only detection of exact renames and will not perform + // any detection of renames based on file similarity. + OnlyExactRenames bool +} + +// DefaultDiffTreeOptions are the default and recommended options for the +// diff tree. +var DefaultDiffTreeOptions = &DiffTreeOptions{ + DetectRenames: true, + RenameScore: 60, + RenameLimit: 0, + OnlyExactRenames: false, +} + +// DiffTreeWithOptions compares the content and mode of the blobs found +// via two tree objects with the given options. The provided context +// must be non-nil. +// If no options are passed, no rename detection will be performed. The +// recommended options are DefaultDiffTreeOptions. +// An error will be returned if the context expires. +// This function will be deprecated and removed in v6 so the default +// behaviour of DiffTree is to detect renames. +func DiffTreeWithOptions( + ctx context.Context, + a, b *Tree, + opts *DiffTreeOptions, +) (Changes, error) { from := NewTreeRootNode(a) to := NewTreeRootNode(b) @@ -33,5 +81,18 @@ func DiffTreeContext(ctx context.Context, a, b *Tree) (Changes, error) { return nil, err } - return newChanges(merkletrieChanges) + changes, err := newChanges(merkletrieChanges) + if err != nil { + return nil, err + } + + if opts == nil { + opts = new(DiffTreeOptions) + } + + if opts.DetectRenames { + return DetectRenames(changes, opts) + } + + return changes, nil } diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/object/patch.go b/vendor/github.com/go-git/go-git/v5/plumbing/object/patch.go index be1e609270..1135a40a4a 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/object/patch.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/object/patch.go @@ -115,7 +115,7 @@ func fileContent(f *File) (content string, isBinary bool, err error) { return } -// textPatch is an implementation of fdiff.Patch interface +// Patch is an implementation of fdiff.Patch interface type Patch struct { message string filePatches []fdiff.FilePatch diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/object/rename.go b/vendor/github.com/go-git/go-git/v5/plumbing/object/rename.go new file mode 100644 index 0000000000..35af1d62d5 --- /dev/null +++ b/vendor/github.com/go-git/go-git/v5/plumbing/object/rename.go @@ -0,0 +1,813 @@ +package object + +import ( + "errors" + "io" + "sort" + "strings" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/filemode" + "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/merkletrie" +) + +// DetectRenames detects the renames in the given changes on two trees with +// the given options. It will return the given changes grouping additions and +// deletions into modifications when possible. +// If options is nil, the default diff tree options will be used. +func DetectRenames( + changes Changes, + opts *DiffTreeOptions, +) (Changes, error) { + if opts == nil { + opts = DefaultDiffTreeOptions + } + + detector := &renameDetector{ + renameScore: int(opts.RenameScore), + renameLimit: int(opts.RenameLimit), + onlyExact: opts.OnlyExactRenames, + } + + for _, c := range changes { + action, err := c.Action() + if err != nil { + return nil, err + } + + switch action { + case merkletrie.Insert: + detector.added = append(detector.added, c) + case merkletrie.Delete: + detector.deleted = append(detector.deleted, c) + default: + detector.modified = append(detector.modified, c) + } + } + + return detector.detect() +} + +// renameDetector will detect and resolve renames in a set of changes. +// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java +type renameDetector struct { + added []*Change + deleted []*Change + modified []*Change + + renameScore int + renameLimit int + onlyExact bool +} + +// detectExactRenames detects matches files that were deleted with files that +// were added where the hash is the same on both. If there are multiple targets +// the one with the most similar path will be chosen as the rename and the +// rest as either deletions or additions. +func (d *renameDetector) detectExactRenames() { + added := groupChangesByHash(d.added) + deletes := groupChangesByHash(d.deleted) + var uniqueAdds []*Change + var nonUniqueAdds [][]*Change + var addedLeft []*Change + + for _, cs := range added { + if len(cs) == 1 { + uniqueAdds = append(uniqueAdds, cs[0]) + } else { + nonUniqueAdds = append(nonUniqueAdds, cs) + } + } + + for _, c := range uniqueAdds { + hash := changeHash(c) + deleted := deletes[hash] + + if len(deleted) == 1 { + if sameMode(c, deleted[0]) { + d.modified = append(d.modified, &Change{From: deleted[0].From, To: c.To}) + delete(deletes, hash) + } else { + addedLeft = append(addedLeft, c) + } + } else if len(deleted) > 1 { + bestMatch := bestNameMatch(c, deleted) + if bestMatch != nil && sameMode(c, bestMatch) { + d.modified = append(d.modified, &Change{From: bestMatch.From, To: c.To}) + delete(deletes, hash) + + var newDeletes = make([]*Change, 0, len(deleted)-1) + for _, d := range deleted { + if d != bestMatch { + newDeletes = append(newDeletes, d) + } + } + deletes[hash] = newDeletes + } + } else { + addedLeft = append(addedLeft, c) + } + } + + for _, added := range nonUniqueAdds { + hash := changeHash(added[0]) + deleted := deletes[hash] + + if len(deleted) == 1 { + deleted := deleted[0] + bestMatch := bestNameMatch(deleted, added) + if bestMatch != nil && sameMode(deleted, bestMatch) { + d.modified = append(d.modified, &Change{From: deleted.From, To: bestMatch.To}) + delete(deletes, hash) + + for _, c := range added { + if c != bestMatch { + addedLeft = append(addedLeft, c) + } + } + } else { + addedLeft = append(addedLeft, added...) + } + } else if len(deleted) > 1 { + maxSize := len(deleted) * len(added) + if d.renameLimit > 0 && d.renameLimit < maxSize { + maxSize = d.renameLimit + } + + matrix := make(similarityMatrix, 0, maxSize) + + for delIdx, del := range deleted { + deletedName := changeName(del) + + for addIdx, add := range added { + addedName := changeName(add) + + score := nameSimilarityScore(addedName, deletedName) + matrix = append(matrix, similarityPair{added: addIdx, deleted: delIdx, score: score}) + + if len(matrix) >= maxSize { + break + } + } + + if len(matrix) >= maxSize { + break + } + } + + sort.Stable(matrix) + + usedAdds := make(map[*Change]struct{}) + usedDeletes := make(map[*Change]struct{}) + for i := len(matrix) - 1; i >= 0; i-- { + del := deleted[matrix[i].deleted] + add := added[matrix[i].added] + + if add == nil || del == nil { + // it was already matched + continue + } + + usedAdds[add] = struct{}{} + usedDeletes[del] = struct{}{} + d.modified = append(d.modified, &Change{From: del.From, To: add.To}) + added[matrix[i].added] = nil + deleted[matrix[i].deleted] = nil + } + + for _, c := range added { + if _, ok := usedAdds[c]; !ok && c != nil { + addedLeft = append(addedLeft, c) + } + } + + var newDeletes = make([]*Change, 0, len(deleted)-len(usedDeletes)) + for _, c := range deleted { + if _, ok := usedDeletes[c]; !ok && c != nil { + newDeletes = append(newDeletes, c) + } + } + deletes[hash] = newDeletes + } else { + addedLeft = append(addedLeft, added...) + } + } + + d.added = addedLeft + d.deleted = nil + for _, dels := range deletes { + d.deleted = append(d.deleted, dels...) + } +} + +// detectContentRenames detects renames based on the similarity of the content +// in the files by building a matrix of pairs between sources and destinations +// and matching by the highest score. +// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java +func (d *renameDetector) detectContentRenames() error { + cnt := max(len(d.added), len(d.deleted)) + if d.renameLimit > 0 && cnt > d.renameLimit { + return nil + } + + srcs, dsts := d.deleted, d.added + matrix, err := buildSimilarityMatrix(srcs, dsts, d.renameScore) + if err != nil { + return err + } + renames := make([]*Change, 0, min(len(matrix), len(dsts))) + + // Match rename pairs on a first come, first serve basis until + // we have looked at everything that is above the minimum score. + for i := len(matrix) - 1; i >= 0; i-- { + pair := matrix[i] + src := srcs[pair.deleted] + dst := dsts[pair.added] + + if dst == nil || src == nil { + // It was already matched before + continue + } + + renames = append(renames, &Change{From: src.From, To: dst.To}) + + // Claim destination and source as matched + dsts[pair.added] = nil + srcs[pair.deleted] = nil + } + + d.modified = append(d.modified, renames...) + d.added = compactChanges(dsts) + d.deleted = compactChanges(srcs) + + return nil +} + +func (d *renameDetector) detect() (Changes, error) { + if len(d.added) > 0 && len(d.deleted) > 0 { + d.detectExactRenames() + + if !d.onlyExact { + if err := d.detectContentRenames(); err != nil { + return nil, err + } + } + } + + result := make(Changes, 0, len(d.added)+len(d.deleted)+len(d.modified)) + result = append(result, d.added...) + result = append(result, d.deleted...) + result = append(result, d.modified...) + + sort.Stable(result) + + return result, nil +} + +func bestNameMatch(change *Change, changes []*Change) *Change { + var best *Change + var bestScore int + + cname := changeName(change) + + for _, c := range changes { + score := nameSimilarityScore(cname, changeName(c)) + if score > bestScore { + bestScore = score + best = c + } + } + + return best +} + +func nameSimilarityScore(a, b string) int { + aDirLen := strings.LastIndexByte(a, '/') + 1 + bDirLen := strings.LastIndexByte(b, '/') + 1 + + dirMin := min(aDirLen, bDirLen) + dirMax := max(aDirLen, bDirLen) + + var dirScoreLtr, dirScoreRtl int + if dirMax == 0 { + dirScoreLtr = 100 + dirScoreRtl = 100 + } else { + var dirSim int + + for ; dirSim < dirMin; dirSim++ { + if a[dirSim] != b[dirSim] { + break + } + } + + dirScoreLtr = dirSim * 100 / dirMax + + if dirScoreLtr == 100 { + dirScoreRtl = 100 + } else { + for dirSim = 0; dirSim < dirMin; dirSim++ { + if a[aDirLen-1-dirSim] != b[bDirLen-1-dirSim] { + break + } + } + dirScoreRtl = dirSim * 100 / dirMax + } + } + + fileMin := min(len(a)-aDirLen, len(b)-bDirLen) + fileMax := max(len(a)-aDirLen, len(b)-bDirLen) + + fileSim := 0 + for ; fileSim < fileMin; fileSim++ { + if a[len(a)-1-fileSim] != b[len(b)-1-fileSim] { + break + } + } + fileScore := fileSim * 100 / fileMax + + return (((dirScoreLtr + dirScoreRtl) * 25) + (fileScore * 50)) / 100 +} + +func changeName(c *Change) string { + if c.To != empty { + return c.To.Name + } + return c.From.Name +} + +func changeHash(c *Change) plumbing.Hash { + if c.To != empty { + return c.To.TreeEntry.Hash + } + + return c.From.TreeEntry.Hash +} + +func changeMode(c *Change) filemode.FileMode { + if c.To != empty { + return c.To.TreeEntry.Mode + } + + return c.From.TreeEntry.Mode +} + +func sameMode(a, b *Change) bool { + return changeMode(a) == changeMode(b) +} + +func groupChangesByHash(changes []*Change) map[plumbing.Hash][]*Change { + var result = make(map[plumbing.Hash][]*Change) + for _, c := range changes { + hash := changeHash(c) + result[hash] = append(result[hash], c) + } + return result +} + +type similarityMatrix []similarityPair + +func (m similarityMatrix) Len() int { return len(m) } +func (m similarityMatrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] } +func (m similarityMatrix) Less(i, j int) bool { + if m[i].score == m[j].score { + if m[i].added == m[j].added { + return m[i].deleted < m[j].deleted + } + return m[i].added < m[j].added + } + return m[i].score < m[j].score +} + +type similarityPair struct { + // index of the added file + added int + // index of the deleted file + deleted int + // similarity score + score int +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func buildSimilarityMatrix(srcs, dsts []*Change, renameScore int) (similarityMatrix, error) { + // Allocate for the worst-case scenario where every pair has a score + // that we need to consider. We might not need that many. + matrix := make(similarityMatrix, 0, len(srcs)*len(dsts)) + srcSizes := make([]int64, len(srcs)) + dstSizes := make([]int64, len(dsts)) + dstTooLarge := make(map[int]bool) + + // Consider each pair of files, if the score is above the minimum + // threshold we need to record that scoring in the matrix so we can + // later find the best matches. +outerLoop: + for srcIdx, src := range srcs { + if changeMode(src) != filemode.Regular { + continue + } + + // Declare the from file and the similarity index here to be able to + // reuse it inside the inner loop. The reason to not initialize them + // here is so we can skip the initialization in case they happen to + // not be needed later. They will be initialized inside the inner + // loop if and only if they're needed and reused in subsequent passes. + var from *File + var s *similarityIndex + var err error + for dstIdx, dst := range dsts { + if changeMode(dst) != filemode.Regular { + continue + } + + if dstTooLarge[dstIdx] { + continue + } + + var to *File + srcSize := srcSizes[srcIdx] + if srcSize == 0 { + from, _, err = src.Files() + if err != nil { + return nil, err + } + srcSize = from.Size + 1 + srcSizes[srcIdx] = srcSize + } + + dstSize := dstSizes[dstIdx] + if dstSize == 0 { + _, to, err = dst.Files() + if err != nil { + return nil, err + } + dstSize = to.Size + 1 + dstSizes[dstIdx] = dstSize + } + + min, max := srcSize, dstSize + if dstSize < srcSize { + min = dstSize + max = srcSize + } + + if int(min*100/max) < renameScore { + // File sizes are too different to be a match + continue + } + + if s == nil { + s, err = fileSimilarityIndex(from) + if err != nil { + if err == errIndexFull { + continue outerLoop + } + return nil, err + } + } + + if to == nil { + _, to, err = dst.Files() + if err != nil { + return nil, err + } + } + + di, err := fileSimilarityIndex(to) + if err != nil { + if err == errIndexFull { + dstTooLarge[dstIdx] = true + } + + return nil, err + } + + contentScore := s.score(di, 10000) + // The name score returns a value between 0 and 100, so we need to + // convert it to the same range as the content score. + nameScore := nameSimilarityScore(src.From.Name, dst.To.Name) * 100 + score := (contentScore*99 + nameScore*1) / 10000 + + if score < renameScore { + continue + } + + matrix = append(matrix, similarityPair{added: dstIdx, deleted: srcIdx, score: score}) + } + } + + sort.Stable(matrix) + + return matrix, nil +} + +func compactChanges(changes []*Change) []*Change { + var result []*Change + for _, c := range changes { + if c != nil { + result = append(result, c) + } + } + return result +} + +const ( + keyShift = 32 + maxCountValue = (1 << keyShift) - 1 +) + +var errIndexFull = errors.New("index is full") + +// similarityIndex is an index structure of lines/blocks in one file. +// This structure can be used to compute an approximation of the similarity +// between two files. +// To save space in memory, this index uses a space efficient encoding which +// will not exceed 1MiB per instance. The index starts out at a smaller size +// (closer to 2KiB), but may grow as more distinct blocks withing the scanned +// file are discovered. +// see: https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java +type similarityIndex struct { + hashed uint64 + // number of non-zero entries in hashes + numHashes int + growAt int + hashes []keyCountPair + hashBits int +} + +func fileSimilarityIndex(f *File) (*similarityIndex, error) { + idx := newSimilarityIndex() + if err := idx.hash(f); err != nil { + return nil, err + } + + sort.Stable(keyCountPairs(idx.hashes)) + + return idx, nil +} + +func newSimilarityIndex() *similarityIndex { + return &similarityIndex{ + hashBits: 8, + hashes: make([]keyCountPair, 1<<8), + growAt: shouldGrowAt(8), + } +} + +func (i *similarityIndex) hash(f *File) error { + isBin, err := f.IsBinary() + if err != nil { + return err + } + + r, err := f.Reader() + if err != nil { + return err + } + + defer ioutil.CheckClose(r, &err) + + return i.hashContent(r, f.Size, isBin) +} + +func (i *similarityIndex) hashContent(r io.Reader, size int64, isBin bool) error { + var buf = make([]byte, 4096) + var ptr, cnt int + remaining := size + + for 0 < remaining { + hash := 5381 + var blockHashedCnt uint64 + + // Hash one line or block, whatever happens first + n := int64(0) + for { + if ptr == cnt { + ptr = 0 + var err error + cnt, err = io.ReadFull(r, buf) + if err != nil && err != io.ErrUnexpectedEOF { + return err + } + + if cnt == 0 { + return io.EOF + } + } + n++ + c := buf[ptr] & 0xff + ptr++ + + // Ignore CR in CRLF sequence if it's text + if !isBin && c == '\r' && ptr < cnt && buf[ptr] == '\n' { + continue + } + blockHashedCnt++ + + if c == '\n' { + break + } + + hash = (hash << 5) + hash + int(c) + + if n >= 64 || n >= remaining { + break + } + } + i.hashed += blockHashedCnt + if err := i.add(hash, blockHashedCnt); err != nil { + return err + } + remaining -= n + } + + return nil +} + +// score computes the similarity score between this index and another one. +// A region of a file is defined as a line in a text file or a fixed-size +// block in a binary file. To prepare an index, each region in the file is +// hashed; the values and counts of hashes are retained in a sorted table. +// Define the similarity fraction F as the count of matching regions between +// the two files divided between the maximum count of regions in either file. +// The similarity score is F multiplied by the maxScore constant, yielding a +// range [0, maxScore]. It is defined as maxScore for the degenerate case of +// two empty files. +// The similarity score is symmetrical; i.e. a.score(b) == b.score(a). +func (i *similarityIndex) score(other *similarityIndex, maxScore int) int { + var maxHashed = i.hashed + if maxHashed < other.hashed { + maxHashed = other.hashed + } + if maxHashed == 0 { + return maxScore + } + + return int(i.common(other) * uint64(maxScore) / maxHashed) +} + +func (i *similarityIndex) common(dst *similarityIndex) uint64 { + srcIdx, dstIdx := 0, 0 + if i.numHashes == 0 || dst.numHashes == 0 { + return 0 + } + + var common uint64 + srcKey, dstKey := i.hashes[srcIdx].key(), dst.hashes[dstIdx].key() + + for { + if srcKey == dstKey { + srcCnt, dstCnt := i.hashes[srcIdx].count(), dst.hashes[dstIdx].count() + if srcCnt < dstCnt { + common += srcCnt + } else { + common += dstCnt + } + + srcIdx++ + if srcIdx == len(i.hashes) { + break + } + srcKey = i.hashes[srcIdx].key() + + dstIdx++ + if dstIdx == len(dst.hashes) { + break + } + dstKey = dst.hashes[dstIdx].key() + } else if srcKey < dstKey { + // Region of src that is not in dst + srcIdx++ + if srcIdx == len(i.hashes) { + break + } + srcKey = i.hashes[srcIdx].key() + } else { + // Region of dst that is not in src + dstIdx++ + if dstIdx == len(dst.hashes) { + break + } + dstKey = dst.hashes[dstIdx].key() + } + } + + return common +} + +func (i *similarityIndex) add(key int, cnt uint64) error { + key = int(uint32(key)*0x9e370001 >> 1) + + j := i.slot(key) + for { + v := i.hashes[j] + if v == 0 { + // It's an empty slot, so we can store it here. + if i.growAt <= i.numHashes { + if err := i.grow(); err != nil { + return err + } + j = i.slot(key) + continue + } + + var err error + i.hashes[j], err = newKeyCountPair(key, cnt) + if err != nil { + return err + } + i.numHashes++ + return nil + } else if v.key() == key { + // It's the same key, so increment the counter. + var err error + i.hashes[j], err = newKeyCountPair(key, v.count()+cnt) + if err != nil { + return err + } + return nil + } else if j+1 >= len(i.hashes) { + j = 0 + } else { + j++ + } + } +} + +type keyCountPair uint64 + +func newKeyCountPair(key int, cnt uint64) (keyCountPair, error) { + if cnt > maxCountValue { + return 0, errIndexFull + } + + return keyCountPair((uint64(key) << keyShift) | cnt), nil +} + +func (p keyCountPair) key() int { + return int(p >> keyShift) +} + +func (p keyCountPair) count() uint64 { + return uint64(p) & maxCountValue +} + +func (i *similarityIndex) slot(key int) int { + // We use 31 - hashBits because the upper bit was already forced + // to be 0 and we want the remaining high bits to be used as the + // table slot. + return int(uint32(key) >> uint(31 - i.hashBits)) +} + +func shouldGrowAt(hashBits int) int { + return (1 << uint(hashBits)) * (hashBits - 3) / hashBits +} + +func (i *similarityIndex) grow() error { + if i.hashBits == 30 { + return errIndexFull + } + + old := i.hashes + + i.hashBits++ + i.growAt = shouldGrowAt(i.hashBits) + + // TODO(erizocosmico): find a way to check if it will OOM and return + // errIndexFull instead. + i.hashes = make([]keyCountPair, 1<<uint(i.hashBits)) + + for _, v := range old { + if v != 0 { + j := i.slot(v.key()) + for i.hashes[j] != 0 { + j++ + if j >= len(i.hashes) { + j = 0 + } + } + i.hashes[j] = v + } + } + + return nil +} + +type keyCountPairs []keyCountPair + +func (p keyCountPairs) Len() int { return len(p) } +func (p keyCountPairs) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p keyCountPairs) Less(i, j int) bool { return p[i] < p[j] } diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/object/tree.go b/vendor/github.com/go-git/go-git/v5/plumbing/object/tree.go index d3c8c77d18..5e6378ca49 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/object/tree.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/object/tree.go @@ -304,29 +304,34 @@ func (t *Tree) buildMap() { } // Diff returns a list of changes between this tree and the provided one -func (from *Tree) Diff(to *Tree) (Changes, error) { - return DiffTree(from, to) +func (t *Tree) Diff(to *Tree) (Changes, error) { + return t.DiffContext(context.Background(), to) } -// Diff returns a list of changes between this tree and the provided one -// Error will be returned if context expires -// Provided context must be non nil -func (from *Tree) DiffContext(ctx context.Context, to *Tree) (Changes, error) { - return DiffTreeContext(ctx, from, to) +// DiffContext returns a list of changes between this tree and the provided one +// Error will be returned if context expires. Provided context must be non nil. +// +// NOTE: Since version 5.1.0 the renames are correctly handled, the settings +// used are the recommended options DefaultDiffTreeOptions. +func (t *Tree) DiffContext(ctx context.Context, to *Tree) (Changes, error) { + return DiffTreeWithOptions(ctx, t, to, DefaultDiffTreeOptions) } // Patch returns a slice of Patch objects with all the changes between trees // in chunks. This representation can be used to create several diff outputs. -func (from *Tree) Patch(to *Tree) (*Patch, error) { - return from.PatchContext(context.Background(), to) +func (t *Tree) Patch(to *Tree) (*Patch, error) { + return t.PatchContext(context.Background(), to) } -// Patch returns a slice of Patch objects with all the changes between trees -// in chunks. This representation can be used to create several diff outputs. -// If context expires, an error will be returned -// Provided context must be non-nil -func (from *Tree) PatchContext(ctx context.Context, to *Tree) (*Patch, error) { - changes, err := DiffTreeContext(ctx, from, to) +// PatchContext returns a slice of Patch objects with all the changes between +// trees in chunks. This representation can be used to create several diff +// outputs. If context expires, an error will be returned. Provided context must +// be non-nil. +// +// NOTE: Since version 5.1.0 the renames are correctly handled, the settings +// used are the recommended options DefaultDiffTreeOptions. +func (t *Tree) PatchContext(ctx context.Context, to *Tree) (*Patch, error) { + changes, err := t.DiffContext(ctx, to) if err != nil { return nil, err } diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/protocol/packp/advrefs.go b/vendor/github.com/go-git/go-git/v5/plumbing/protocol/packp/advrefs.go index ab286c6386..1bd724cad5 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/protocol/packp/advrefs.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/protocol/packp/advrefs.go @@ -201,3 +201,11 @@ func (a *AdvRefs) addSymbolicRefs(s storer.ReferenceStorer) error { func (a *AdvRefs) supportSymrefs() bool { return a.Capabilities.Supports(capability.SymRef) } + +// IsEmpty returns true if doesn't contain any reference. +func (a *AdvRefs) IsEmpty() bool { + return a.Head == nil && + len(a.References) == 0 && + len(a.Peeled) == 0 && + len(a.Shallows) == 0 +} diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go b/vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go index d564d25a64..89432e34c1 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go @@ -175,6 +175,13 @@ func (s *session) AdvertisedReferences() (*packp.AdvRefs, error) { } } + // Some servers like jGit, announce capabilities instead of returning an + // packp message with a flush. This verifies that we received a empty + // adv-refs, even it contains capabilities. + if !s.isReceivePack && ar.IsEmpty() { + return nil, transport.ErrEmptyRemoteRepository + } + transport.FilterUnsupportedCapabilities(ar.Capabilities) s.advRefs = ar return ar, nil diff --git a/vendor/github.com/go-git/go-git/v5/plumbing/transport/server/server.go b/vendor/github.com/go-git/go-git/v5/plumbing/transport/server/server.go index 71845e3bbe..727f902150 100644 --- a/vendor/github.com/go-git/go-git/v5/plumbing/transport/server/server.go +++ b/vendor/github.com/go-git/go-git/v5/plumbing/transport/server/server.go @@ -243,11 +243,13 @@ func (s *rpSession) ReceivePack(ctx context.Context, req *packp.ReferenceUpdateR //TODO: Implement 'atomic' update of references. - r := ioutil.NewContextReadCloser(ctx, req.Packfile) - if err := s.writePackfile(r); err != nil { - s.unpackErr = err - s.firstErr = err - return s.reportStatus(), err + if req.Packfile != nil { + r := ioutil.NewContextReadCloser(ctx, req.Packfile) + if err := s.writePackfile(r); err != nil { + s.unpackErr = err + s.firstErr = err + return s.reportStatus(), err + } } s.updateReferences(req) diff --git a/vendor/github.com/go-git/go-git/v5/remote.go b/vendor/github.com/go-git/go-git/v5/remote.go index 242df0d44a..e642c57291 100644 --- a/vendor/github.com/go-git/go-git/v5/remote.go +++ b/vendor/github.com/go-git/go-git/v5/remote.go @@ -29,6 +29,7 @@ var ( NoErrAlreadyUpToDate = errors.New("already up-to-date") ErrDeleteRefNotSupported = errors.New("server does not support delete-refs") ErrForceNeeded = errors.New("some refs were not updated") + ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec") ) const ( @@ -122,6 +123,15 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { return ErrDeleteRefNotSupported } + if o.Force { + for i := 0; i < len(o.RefSpecs); i++ { + rs := &o.RefSpecs[i] + if !rs.IsForceUpdate() { + o.RefSpecs[i] = config.RefSpec("+" + rs.String()) + } + } + } + localRefs, err := r.references() if err != nil { return err @@ -303,6 +313,10 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen return nil, err } + if err := r.isSupportedRefSpec(o.RefSpecs, ar); err != nil { + return nil, err + } + remoteRefs, err := ar.AllReferences() if err != nil { return nil, err @@ -546,6 +560,7 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, func (r *Remote) references() ([]*plumbing.Reference, error) { var localRefs []*plumbing.Reference + iter, err := r.s.IterReferences() if err != nil { return nil, err @@ -701,6 +716,11 @@ func doCalculateRefs( return err } + if s.IsExactSHA1() { + ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src())) + return refs.SetReference(ref) + } + var matched bool err = iter.ForEach(func(ref *plumbing.Reference) error { if !s.Match(ref.Name()) { @@ -850,6 +870,26 @@ func (r *Remote) newUploadPackRequest(o *FetchOptions, return req, nil } +func (r *Remote) isSupportedRefSpec(refs []config.RefSpec, ar *packp.AdvRefs) error { + var containsIsExact bool + for _, ref := range refs { + if ref.IsExactSHA1() { + containsIsExact = true + } + } + + if !containsIsExact { + return nil + } + + if ar.Capabilities.Supports(capability.AllowReachableSHA1InWant) || + ar.Capabilities.Supports(capability.AllowTipSHA1InWant) { + return nil + } + + return ErrExactSHA1NotSupported +} + func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.Progress) io.Reader { var t sideband.Type @@ -883,7 +923,7 @@ func (r *Remote) updateLocalReferenceStorage( } for _, ref := range fetchedRefs { - if !spec.Match(ref.Name()) { + if !spec.Match(ref.Name()) && !spec.IsExactSHA1() { continue } diff --git a/vendor/github.com/go-git/go-git/v5/repository.go b/vendor/github.com/go-git/go-git/v5/repository.go index c83a136036..47318d113b 100644 --- a/vendor/github.com/go-git/go-git/v5/repository.go +++ b/vendor/github.com/go-git/go-git/v5/repository.go @@ -13,7 +13,6 @@ import ( "strings" "time" - "golang.org/x/crypto/openpgp" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/internal/revision" "github.com/go-git/go-git/v5/plumbing" @@ -24,6 +23,8 @@ import ( "github.com/go-git/go-git/v5/storage" "github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/imdario/mergo" + "golang.org/x/crypto/openpgp" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/osfs" @@ -155,7 +156,7 @@ func setConfigWorktree(r *Repository, worktree, storage billy.Filesystem) error return nil } - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return err } @@ -434,14 +435,56 @@ func cleanUpDir(path string, all bool) error { return err } -// Config return the repository config +// Config return the repository config. In a filesystem backed repository this +// means read the `.git/config`. func (r *Repository) Config() (*config.Config, error) { return r.Storer.Config() } +// SetConfig marshall and writes the repository config. In a filesystem backed +// repository this means write the `.git/config`. This function should be called +// with the result of `Repository.Config` and never with the output of +// `Repository.ConfigScoped`. +func (r *Repository) SetConfig(cfg *config.Config) error { + return r.Storer.SetConfig(cfg) +} + +// ConfigScoped returns the repository config, merged with requested scope and +// lower. For example if, config.GlobalScope is given the local and global config +// are returned merged in one config value. +func (r *Repository) ConfigScoped(scope config.Scope) (*config.Config, error) { + // TODO(mcuadros): v6, add this as ConfigOptions.Scoped + + var err error + system := config.NewConfig() + if scope >= config.SystemScope { + system, err = config.LoadConfig(config.SystemScope) + if err != nil { + return nil, err + } + } + + global := config.NewConfig() + if scope >= config.GlobalScope { + global, err = config.LoadConfig(config.GlobalScope) + if err != nil { + return nil, err + } + } + + local, err := r.Storer.Config() + if err != nil { + return nil, err + } + + _ = mergo.Merge(global, system) + _ = mergo.Merge(local, global) + return local, nil +} + // Remote return a remote if exists func (r *Repository) Remote(name string) (*Remote, error) { - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return nil, err } @@ -456,7 +499,7 @@ func (r *Repository) Remote(name string) (*Remote, error) { // Remotes returns a list with all the remotes func (r *Repository) Remotes() ([]*Remote, error) { - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return nil, err } @@ -480,7 +523,7 @@ func (r *Repository) CreateRemote(c *config.RemoteConfig) (*Remote, error) { remote := NewRemote(r.Storer, c) - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return nil, err } @@ -511,7 +554,7 @@ func (r *Repository) CreateRemoteAnonymous(c *config.RemoteConfig) (*Remote, err // DeleteRemote delete a remote from the repository and delete the config func (r *Repository) DeleteRemote(name string) error { - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return err } @@ -526,7 +569,7 @@ func (r *Repository) DeleteRemote(name string) error { // Branch return a Branch if exists func (r *Repository) Branch(name string) (*config.Branch, error) { - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return nil, err } @@ -545,7 +588,7 @@ func (r *Repository) CreateBranch(c *config.Branch) error { return err } - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return err } @@ -560,7 +603,7 @@ func (r *Repository) CreateBranch(c *config.Branch) error { // DeleteBranch delete a Branch from the repository and delete the config func (r *Repository) DeleteBranch(name string) error { - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return err } @@ -835,7 +878,7 @@ func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { } func (r *Repository) setIsBare(isBare bool) error { - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return err } @@ -851,7 +894,7 @@ func (r *Repository) updateRemoteConfigIfNeeded(o *CloneOptions, c *config.Remot c.Fetch = r.cloneRefSpec(o) - cfg, err := r.Storer.Config() + cfg, err := r.Config() if err != nil { return err } @@ -1541,7 +1584,7 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er return h, err } defer ioutil.CheckClose(wc, &err) - scfg, err := r.Storer.Config() + scfg, err := r.Config() if err != nil { return h, err } diff --git a/vendor/github.com/go-git/go-git/v5/storage/filesystem/config.go b/vendor/github.com/go-git/go-git/v5/storage/filesystem/config.go index 01b35b4e36..78a646465a 100644 --- a/vendor/github.com/go-git/go-git/v5/storage/filesystem/config.go +++ b/vendor/github.com/go-git/go-git/v5/storage/filesystem/config.go @@ -1,7 +1,6 @@ package filesystem import ( - stdioutil "io/ioutil" "os" "github.com/go-git/go-git/v5/config" @@ -14,29 +13,17 @@ type ConfigStorage struct { } func (c *ConfigStorage) Config() (conf *config.Config, err error) { - cfg := config.NewConfig() - f, err := c.dir.Config() if err != nil { if os.IsNotExist(err) { - return cfg, nil + return config.NewConfig(), nil } return nil, err } defer ioutil.CheckClose(f, &err) - - b, err := stdioutil.ReadAll(f) - if err != nil { - return nil, err - } - - if err = cfg.Unmarshal(b); err != nil { - return nil, err - } - - return cfg, err + return config.ReadConfig(f) } func (c *ConfigStorage) SetConfig(cfg *config.Config) (err error) { diff --git a/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/dotgit.go b/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/dotgit.go index 3ce9daeda2..83c7683071 100644 --- a/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/dotgit.go +++ b/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/dotgit.go @@ -57,6 +57,9 @@ var ( // targeting a non-existing object. This usually means the repository // is corrupt. ErrSymRefTargetNotFound = errors.New("symbolic reference target not found") + // ErrIsDir is returned when a reference file is attempting to be read, + // but the path specified is a directory. + ErrIsDir = errors.New("reference path is a directory") ) // Options holds configuration for the storage. @@ -926,6 +929,14 @@ func (d *DotGit) addRefFromHEAD(refs *[]*plumbing.Reference) error { func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference, err error) { path = d.fs.Join(path, d.fs.Join(strings.Split(name, "/")...)) + st, err := d.fs.Stat(path) + if err != nil { + return nil, err + } + if st.IsDir() { + return nil, ErrIsDir + } + f, err := d.fs.Open(path) if err != nil { return nil, err diff --git a/vendor/github.com/go-git/go-git/v5/storage/filesystem/object.go b/vendor/github.com/go-git/go-git/v5/storage/filesystem/object.go index 862cc1b1ae..7437174390 100644 --- a/vendor/github.com/go-git/go-git/v5/storage/filesystem/object.go +++ b/vendor/github.com/go-git/go-git/v5/storage/filesystem/object.go @@ -408,6 +408,8 @@ func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedOb return nil, err } + defer ioutil.CheckClose(w, &err) + s.objectCache.Put(obj) _, err = io.Copy(w, r) diff --git a/vendor/github.com/go-git/go-git/v5/submodule.go b/vendor/github.com/go-git/go-git/v5/submodule.go index 92ccdb1c8a..dff26b0d80 100644 --- a/vendor/github.com/go-git/go-git/v5/submodule.go +++ b/vendor/github.com/go-git/go-git/v5/submodule.go @@ -35,7 +35,7 @@ func (s *Submodule) Config() *config.Submodule { // Init initialize the submodule reading the recorded Entry in the index for // the given submodule func (s *Submodule) Init() error { - cfg, err := s.w.r.Storer.Config() + cfg, err := s.w.r.Config() if err != nil { return err } diff --git a/vendor/github.com/go-git/go-git/v5/utils/merkletrie/difftree.go b/vendor/github.com/go-git/go-git/v5/utils/merkletrie/difftree.go index 90d9c958d0..bd084b2ab0 100644 --- a/vendor/github.com/go-git/go-git/v5/utils/merkletrie/difftree.go +++ b/vendor/github.com/go-git/go-git/v5/utils/merkletrie/difftree.go @@ -23,7 +23,7 @@ package merkletrie // # Cases // -// When comparing noders in both trees you will found yourself in +// When comparing noders in both trees you will find yourself in // one of 169 possible cases, but if we ignore moves, we can // simplify a lot the search space into the following table: // @@ -256,17 +256,21 @@ import ( ) var ( + // ErrCanceled is returned whenever the operation is canceled. ErrCanceled = errors.New("operation canceled") ) // DiffTree calculates the list of changes between two merkletries. It // uses the provided hashEqual callback to compare noders. -func DiffTree(fromTree, toTree noder.Noder, - hashEqual noder.Equal) (Changes, error) { +func DiffTree( + fromTree, + toTree noder.Noder, + hashEqual noder.Equal, +) (Changes, error) { return DiffTreeContext(context.Background(), fromTree, toTree, hashEqual) } -// DiffTree calculates the list of changes between two merkletries. It +// DiffTreeContext calculates the list of changes between two merkletries. It // uses the provided hashEqual callback to compare noders. // Error will be returned if context expires // Provided context must be non nil |