Update to go-git v5.1.0 (#11936)
Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
parent
6bf78d2b57
commit
1426126690
76 changed files with 3134 additions and 556 deletions
2
vendor/github.com/go-git/go-git/v5/COMPATIBILITY.md
generated
vendored
2
vendor/github.com/go-git/go-git/v5/COMPATIBILITY.md
generated
vendored
|
@ -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 | ✔ |
|
||||
|
|
8
vendor/github.com/go-git/go-git/v5/README.md
generated
vendored
8
vendor/github.com/go-git/go-git/v5/README.md
generated
vendored
|
@ -1,9 +1,9 @@
|
|||

|
||||
[](https://godoc.org/github.com/src-d/go-git) [](https://github.com/go-git/go-git/actions) [](https://goreportcard.com/report/github.com/src-d/go-git)
|
||||
[](https://pkg.go.dev/github.com/go-git/go-git/v5) [](https://github.com/go-git/go-git/actions) [](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
|
||||
|
|
157
vendor/github.com/go-git/go-git/v5/config/config.go
generated
vendored
157
vendor/github.com/go-git/go-git/v5/config/config.go
generated
vendored
|
@ -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 {
|
||||
|
|
9
vendor/github.com/go-git/go-git/v5/config/refspec.go
generated
vendored
9
vendor/github.com/go-git/go-git/v5/config/refspec.go
generated
vendored
|
@ -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]
|
||||
}
|
||||
|
||||
|
|
1
vendor/github.com/go-git/go-git/v5/go.mod
generated
vendored
1
vendor/github.com/go-git/go-git/v5/go.mod
generated
vendored
|
@ -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
|
||||
|
|
2
vendor/github.com/go-git/go-git/v5/go.sum
generated
vendored
2
vendor/github.com/go-git/go-git/v5/go.sum
generated
vendored
|
@ -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=
|
||||
|
|
49
vendor/github.com/go-git/go-git/v5/options.go
generated
vendored
49
vendor/github.com/go-git/go-git/v5/options.go
generated
vendored
|
@ -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")
|
||||
|
|
38
vendor/github.com/go-git/go-git/v5/plumbing/color/color.go
generated
vendored
Normal file
38
vendor/github.com/go-git/go-git/v5/plumbing/color/color.go
generated
vendored
Normal file
|
@ -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"
|
||||
)
|
97
vendor/github.com/go-git/go-git/v5/plumbing/format/diff/colorconfig.go
generated
vendored
Normal file
97
vendor/github.com/go-git/go-git/v5/plumbing/format/diff/colorconfig.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
399
vendor/github.com/go-git/go-git/v5/plumbing/format/diff/unified_encoder.go
generated
vendored
399
vendor/github.com/go-git/go-git/v5/plumbing/format/diff/unified_encoder.go
generated
vendored
|
@ -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/"
|
||||
operationChar = map[Operation]byte{
|
||||
Add: '+',
|
||||
Delete: '-',
|
||||
Equal: ' ',
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
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}
|
||||
// NewUnifiedEncoder returns a new UnifiedEncoder that writes to w.
|
||||
func NewUnifiedEncoder(w io.Writer, contextLines int) *UnifiedEncoder {
|
||||
return &UnifiedEncoder{
|
||||
Writer: w,
|
||||
contextLines: contextLines,
|
||||
}
|
||||
}
|
||||
|
||||
// SetColor sets e's color configuration and returns e.
|
||||
func (e *UnifiedEncoder) SetColor(colorConfig ColorConfig) *UnifiedEncoder {
|
||||
e.color = colorConfig
|
||||
return e
|
||||
}
|
||||
|
||||
// Encode encodes patch.
|
||||
func (e *UnifiedEncoder) Encode(patch Patch) error {
|
||||
e.printMessage(patch.Message())
|
||||
sb := &strings.Builder{}
|
||||
|
||||
if err := e.encodeFilePatch(patch.FilePatches()); err != nil {
|
||||
return err
|
||||
if message := patch.Message(); message != "" {
|
||||
sb.WriteString(message)
|
||||
if !strings.HasSuffix(message, "\n") {
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
_, err := e.buf.WriteTo(e)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := e.Write([]byte(sb.String()))
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
g := newHunksGenerator(p.Chunks(), e.ctxLines)
|
||||
for _, c := range g.Generate() {
|
||||
c.WriteTo(&e.buf)
|
||||
}
|
||||
func (e *UnifiedEncoder) writeFilePatchHeader(sb *strings.Builder, filePatch FilePatch) {
|
||||
from, to := filePatch.Files()
|
||||
if from == nil && to == nil {
|
||||
return
|
||||
}
|
||||
isBinary := filePatch.IsBinary()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *UnifiedEncoder) printMessage(message string) {
|
||||
isEmpty := message == ""
|
||||
hasSuffix := strings.HasSuffix(message, "\n")
|
||||
if !isEmpty && !hasSuffix {
|
||||
message += "\n"
|
||||
}
|
||||
|
||||
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
|
||||
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")
|
||||
}
|
||||
n := len(o.text)
|
||||
if n > 0 && o.text[n-1] != '\n' {
|
||||
suffix = noNewLine
|
||||
}
|
||||
|
||||
return fmt.Sprintf(prefix, o.text, suffix)
|
||||
sb.WriteString(color.Reset(colorKey))
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
|
10
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/diff_delta.go
generated
vendored
10
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/diff_delta.go
generated
vendored
|
@ -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)
|
||||
|
|
10
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/encoder.go
generated
vendored
10
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/encoder.go
generated
vendored
|
@ -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 {
|
||||
|
|
5
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/packfile.go
generated
vendored
5
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/packfile.go
generated
vendored
|
@ -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)
|
||||
|
||||
|
|
15
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/parser.go
generated
vendored
15
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/parser.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
7
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/patch_delta.go
generated
vendored
7
vendor/github.com/go-git/go-git/v5/plumbing/format/packfile/patch_delta.go
generated
vendored
|
@ -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()
|
||||
|
|
10
vendor/github.com/go-git/go-git/v5/plumbing/hash.go
generated
vendored
10
vendor/github.com/go-git/go-git/v5/plumbing/hash.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
4
vendor/github.com/go-git/go-git/v5/plumbing/object/change.go
generated
vendored
4
vendor/github.com/go-git/go-git/v5/plumbing/object/change.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
15
vendor/github.com/go-git/go-git/v5/plumbing/object/commit.go
generated
vendored
15
vendor/github.com/go-git/go-git/v5/plumbing/object/commit.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
|
|
67
vendor/github.com/go-git/go-git/v5/plumbing/object/difftree.go
generated
vendored
67
vendor/github.com/go-git/go-git/v5/plumbing/object/difftree.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
2
vendor/github.com/go-git/go-git/v5/plumbing/object/patch.go
generated
vendored
2
vendor/github.com/go-git/go-git/v5/plumbing/object/patch.go
generated
vendored
|
@ -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
|
||||
|
|
813
vendor/github.com/go-git/go-git/v5/plumbing/object/rename.go
generated
vendored
Normal file
813
vendor/github.com/go-git/go-git/v5/plumbing/object/rename.go
generated
vendored
Normal file
|
@ -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] }
|
35
vendor/github.com/go-git/go-git/v5/plumbing/object/tree.go
generated
vendored
35
vendor/github.com/go-git/go-git/v5/plumbing/object/tree.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
8
vendor/github.com/go-git/go-git/v5/plumbing/protocol/packp/advrefs.go
generated
vendored
8
vendor/github.com/go-git/go-git/v5/plumbing/protocol/packp/advrefs.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
7
vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go
generated
vendored
7
vendor/github.com/go-git/go-git/v5/plumbing/transport/internal/common/common.go
generated
vendored
|
@ -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
|
||||
|
|
12
vendor/github.com/go-git/go-git/v5/plumbing/transport/server/server.go
generated
vendored
12
vendor/github.com/go-git/go-git/v5/plumbing/transport/server/server.go
generated
vendored
|
@ -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)
|
||||
|
|
42
vendor/github.com/go-git/go-git/v5/remote.go
generated
vendored
42
vendor/github.com/go-git/go-git/v5/remote.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
||||
|
|
69
vendor/github.com/go-git/go-git/v5/repository.go
generated
vendored
69
vendor/github.com/go-git/go-git/v5/repository.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
17
vendor/github.com/go-git/go-git/v5/storage/filesystem/config.go
generated
vendored
17
vendor/github.com/go-git/go-git/v5/storage/filesystem/config.go
generated
vendored
|
@ -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) {
|
||||
|
|
11
vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/dotgit.go
generated
vendored
11
vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/dotgit.go
generated
vendored
|
@ -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
|
||||
|
|
2
vendor/github.com/go-git/go-git/v5/storage/filesystem/object.go
generated
vendored
2
vendor/github.com/go-git/go-git/v5/storage/filesystem/object.go
generated
vendored
|
@ -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)
|
||||
|
|
2
vendor/github.com/go-git/go-git/v5/submodule.go
generated
vendored
2
vendor/github.com/go-git/go-git/v5/submodule.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
12
vendor/github.com/go-git/go-git/v5/utils/merkletrie/difftree.go
generated
vendored
12
vendor/github.com/go-git/go-git/v5/utils/merkletrie/difftree.go
generated
vendored
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue