1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-15 00:15:32 +02:00

switch to fork of go-git

This commit is contained in:
Jesse Duffield
2020-10-06 20:50:54 +11:00
parent 3e15ae3211
commit 6e076472b8
196 changed files with 3871 additions and 1028 deletions

4
go.mod
View File

@ -10,12 +10,12 @@ require (
github.com/fatih/color v1.9.0 github.com/fatih/color v1.9.0
github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify v1.4.7
github.com/go-errors/errors v1.1.1 github.com/go-errors/errors v1.1.1
github.com/go-git/go-git/v5 v5.0.0
github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/golang/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.3.1 // indirect github.com/google/go-cmp v0.3.1 // indirect
github.com/integrii/flaggy v1.4.0 github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/go-git/v5 v5.1.1
github.com/jesseduffield/gocui v0.3.1-0.20200930205305-1b445b9bd5da github.com/jesseduffield/gocui v0.3.1-0.20200930205305-1b445b9bd5da
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe // indirect github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
@ -40,3 +40,5 @@ require (
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2
gopkg.in/yaml.v2 v2.2.7 gopkg.in/yaml.v2 v2.2.7
) )
replace github.com/go-git/go-git/v5 => github.com/jesseduffield/go-git/v5 v5.1.1

14
go.sum
View File

@ -55,10 +55,8 @@ github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
@ -92,14 +90,14 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM= github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM=
github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI= github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jesseduffield/gocui v0.3.1-0.20200824100831-1b6ec5d7d449 h1:G5Cm2QuFil8fnrMqUHYFiUkVSS/SXnn3ATtU7MbMFI0= github.com/jesseduffield/go-git/v5 v5.1.1 h1:ihQ0E8QQnKrSQvbXhhErmUSB7G6tkURD6Kx/jm6RzO0=
github.com/jesseduffield/gocui v0.3.1-0.20200824100831-1b6ec5d7d449/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw= github.com/jesseduffield/go-git/v5 v5.1.1/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20200927010622-b998f1723844 h1:D2/gUHscz5LmHojvNUmEVtvNBlX8OF6Ez82oW7GitfI=
github.com/jesseduffield/gocui v0.3.1-0.20200927010622-b998f1723844/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/gocui v0.3.1-0.20200930205305-1b445b9bd5da h1:I3GD+Set99tj57PgmjZJLoW6lkv0NTHadKNosJ1OKj4= github.com/jesseduffield/gocui v0.3.1-0.20200930205305-1b445b9bd5da h1:I3GD+Set99tj57PgmjZJLoW6lkv0NTHadKNosJ1OKj4=
github.com/jesseduffield/gocui v0.3.1-0.20200930205305-1b445b9bd5da/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw= github.com/jesseduffield/gocui v0.3.1-0.20200930205305-1b445b9bd5da/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe h1:qsVhCf2RFyyKIUe/+gJblbCpXMUki9rZrHuEctg6M/E= github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe h1:qsVhCf2RFyyKIUe/+gJblbCpXMUki9rZrHuEctg6M/E=

View File

@ -8,7 +8,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"

View File

@ -11,7 +11,7 @@ import (
"time" "time"
"github.com/go-errors/errors" "github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"

View File

@ -3,7 +3,7 @@ package commands
import ( import (
"path/filepath" "path/filepath"
gogit "github.com/go-git/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
) )
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase // RebaseMode returns "" for non-rebase mode, "normal" for normal rebase

View File

@ -1,367 +0,0 @@
package diff
import (
"bytes"
"fmt"
"io"
"regexp"
"strings"
"github.com/go-git/go-git/v5/plumbing"
)
const (
diffInit = "diff --git a/%s b/%s\n"
chunkStart = "@@ -"
chunkMiddle = " +"
chunkEnd = " @@%s\n"
chunkCount = "%d,%d"
noFilePath = "/dev/null"
aDir = "a/"
bDir = "b/"
fPath = "--- %s\n"
tPath = "+++ %s\n"
binary = "Binary files %s and %s differ\n"
addLine = "+%s%s"
deleteLine = "-%s%s"
equalLine = " %s%s"
noNewLine = "\n\\ No newline at end of file\n"
oldMode = "old mode %o\n"
newMode = "new mode %o\n"
deletedFileMode = "deleted file mode %o\n"
newFileMode = "new file mode %o\n"
renameFrom = "from"
renameTo = "to"
renameFileMode = "rename %s %s\n"
indexAndMode = "index %s..%s %o\n"
indexNoMode = "index %s..%s\n"
DefaultContextLines = 3
)
// UnifiedEncoder encodes an unified diff into the provided Writer.
// There are some unsupported features:
// - Similarity index for renames
// - Sort hash representation
type UnifiedEncoder struct {
io.Writer
// ctxLines is the count of unchanged lines that will appear
// surrounding a change.
ctxLines int
buf bytes.Buffer
}
func NewUnifiedEncoder(w io.Writer, ctxLines int) *UnifiedEncoder {
return &UnifiedEncoder{ctxLines: ctxLines, Writer: w}
}
func (e *UnifiedEncoder) Encode(patch Patch) error {
e.printMessage(patch.Message())
if err := e.encodeFilePatch(patch.FilePatches()); err != nil {
return err
}
_, err := e.buf.WriteTo(e)
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)
}
}
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 {
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())
if from.Mode() != to.Mode() {
fmt.Fprintf(&e.buf, oldMode+newMode, from.Mode(), to.Mode())
}
if from.Path() != to.Path() {
fmt.Fprintf(&e.buf,
renameFileMode+renameFileMode,
renameFrom, from.Path(), renameTo, to.Path())
}
if from.Mode() != to.Mode() && !hashEquals {
fmt.Fprintf(&e.buf, indexNoMode, from.Hash(), to.Hash())
} else if !hashEquals {
fmt.Fprintf(&e.buf, indexAndMode, from.Hash(), to.Hash(), from.Mode())
}
if !hashEquals {
e.pathLines(isBinary, aDir+from.Path(), bDir+to.Path())
}
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())
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)
}
return nil
}
func (e *UnifiedEncoder) pathLines(isBinary bool, fromPath, toPath string) {
format := fPath + tPath
if isBinary {
format = binary
}
fmt.Fprintf(&e.buf, format, fromPath, toPath)
}
type hunksGenerator struct {
fromLine, toLine int
ctxLines int
chunks []Chunk
current *hunk
hunks []*hunk
beforeContext, afterContext []string
}
func newHunksGenerator(chunks []Chunk, ctxLines int) *hunksGenerator {
return &hunksGenerator{
chunks: chunks,
ctxLines: ctxLines,
}
}
func (c *hunksGenerator) Generate() []*hunk {
for i, chunk := range c.chunks {
ls := splitLines(chunk.Content())
lsLen := len(ls)
switch chunk.Type() {
case Equal:
c.fromLine += lsLen
c.toLine += lsLen
c.processEqualsLines(ls, i)
case Delete:
if lsLen != 0 {
c.fromLine++
}
c.processHunk(i, chunk.Type())
c.fromLine += lsLen - 1
c.current.AddOp(chunk.Type(), ls...)
case Add:
if lsLen != 0 {
c.toLine++
}
c.processHunk(i, chunk.Type())
c.toLine += lsLen - 1
c.current.AddOp(chunk.Type(), ls...)
}
if i == len(c.chunks)-1 && c.current != nil {
c.hunks = append(c.hunks, c.current)
}
}
return c.hunks
}
func (c *hunksGenerator) processHunk(i int, op Operation) {
if c.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
}
c.current = &hunk{ctxPrefix: strings.TrimSuffix(ctxPrefix, "\n")}
c.current.AddOp(Equal, c.beforeContext...)
switch op {
case Delete:
c.current.fromLine, c.current.toLine =
c.addLineNumbers(c.fromLine, c.toLine, linesBefore, i, Add)
case Add:
c.current.toLine, c.current.fromLine =
c.addLineNumbers(c.toLine, c.fromLine, linesBefore, i, Delete)
}
c.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) {
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
} else {
clb = 1
}
case c.ctxLines == 0:
clb = lb
case i != len(c.chunks)-1:
next := c.chunks[i+1]
if next.Type() == op || next.Type() == Equal {
// this diff will be into this chunk
clb = lb + 1
}
}
return
}
func (c *hunksGenerator) processEqualsLines(ls []string, i int) {
if c.current == nil {
c.beforeContext = append(c.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
} else {
ctxLines := c.ctxLines
if ctxLines > len(c.afterContext) {
ctxLines = len(c.afterContext)
}
c.current.AddOp(Equal, c.afterContext[:ctxLines]...)
c.hunks = append(c.hunks, c.current)
c.current = nil
c.beforeContext = c.afterContext[ctxLines:]
c.afterContext = nil
}
}
var splitLinesRE = regexp.MustCompile(`[^\n]*(\n|$)`)
func splitLines(s string) []string {
out := splitLinesRE.FindAllString(s, -1)
if out[len(out)-1] == "" {
out = out[:len(out)-1]
}
return out
}
type hunk struct {
fromLine int
toLine int
fromCount int
toCount int
ctxPrefix string
ops []*op
}
func (c *hunk) WriteTo(buf *bytes.Buffer) {
buf.WriteString(chunkStart)
if c.fromCount == 1 {
fmt.Fprintf(buf, "%d", c.fromLine)
} else {
fmt.Fprintf(buf, chunkCount, c.fromLine, c.fromCount)
}
buf.WriteString(chunkMiddle)
if c.toCount == 1 {
fmt.Fprintf(buf, "%d", c.toLine)
} else {
fmt.Fprintf(buf, chunkCount, c.toLine, c.toCount)
}
fmt.Fprintf(buf, chunkEnd, c.ctxPrefix)
for _, d := range c.ops {
buf.WriteString(d.String())
}
}
func (c *hunk) AddOp(t Operation, s ...string) {
ls := len(s)
switch t {
case Add:
c.toCount += ls
case Delete:
c.fromCount += ls
case Equal:
c.toCount += ls
c.fromCount += ls
}
for _, l := range s {
c.ops = append(c.ops, &op{l, t})
}
}
type op struct {
text string
t Operation
}
func (o *op) String() string {
var prefix, suffix string
switch o.t {
case Add:
prefix = addLine
case Delete:
prefix = deleteLine
case Equal:
prefix = equalLine
}
n := len(o.text)
if n > 0 && o.text[n-1] != '\n' {
suffix = noNewLine
}
return fmt.Sprintf(prefix, o.text, suffix)
}

View File

@ -1,37 +0,0 @@
package object
import (
"bytes"
"context"
"github.com/go-git/go-git/v5/utils/merkletrie"
"github.com/go-git/go-git/v5/utils/merkletrie/noder"
)
// DiffTree compares the content and mode of the blobs found via two
// tree objects.
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
// tree objects. Provided context must be non-nil.
// An error will be return if context expires
func DiffTreeContext(ctx context.Context, a, b *Tree) (Changes, error) {
from := NewTreeRootNode(a)
to := NewTreeRootNode(b)
hashEqual := func(a, b noder.Hasher) bool {
return bytes.Equal(a.Hash(), b.Hash())
}
merkletrieChanges, err := merkletrie.DiffTreeContext(ctx, from, to, hashEqual)
if err != nil {
if err == merkletrie.ErrCanceled {
return nil, ErrCanceled
}
return nil, err
}
return newChanges(merkletrieChanges)
}

33
vendor/github.com/imdario/mergo/.gitignore generated vendored Normal file
View File

@ -0,0 +1,33 @@
#### joe made this: http://goel.io/joe
#### go ####
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
#### vim ####
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]
# Session
Session.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags

46
vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md generated vendored Normal file
View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

28
vendor/github.com/imdario/mergo/LICENSE generated vendored Normal file
View File

@ -0,0 +1,28 @@
Copyright (c) 2013 Dario Castañé. All rights reserved.
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

238
vendor/github.com/imdario/mergo/README.md generated vendored Normal file
View File

@ -0,0 +1,238 @@
# Mergo
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
## Status
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
[![GoDoc][3]][4]
[![GoCard][5]][6]
[![Build Status][1]][2]
[![Coverage Status][7]][8]
[![Sourcegraph][9]][10]
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield)
[1]: https://travis-ci.org/imdario/mergo.png
[2]: https://travis-ci.org/imdario/mergo
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
[4]: https://godoc.org/github.com/imdario/mergo
[5]: https://goreportcard.com/badge/imdario/mergo
[6]: https://goreportcard.com/report/github.com/imdario/mergo
[7]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
[8]: https://coveralls.io/github/imdario/mergo?branch=master
[9]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
[10]: https://sourcegraph.com/github.com/imdario/mergo?badge
### Latest release
[Release v0.3.7](https://github.com/imdario/mergo/releases/tag/v0.3.7).
### Important note
Please keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2) Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). An optional/variadic argument has been added, so it won't break existing code.
If you were using Mergo **before** April 6th 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause (I hope it won't!) in existing projects after the change (release 0.2.0).
### Donations
If Mergo is useful to you, consider buying me a coffee, a beer or making a monthly donation so I can keep building great free software. :heart_eyes:
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
[![Beerpay](https://beerpay.io/imdario/mergo/badge.svg)](https://beerpay.io/imdario/mergo)
[![Beerpay](https://beerpay.io/imdario/mergo/make-wish.svg)](https://beerpay.io/imdario/mergo)
<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
### Mergo in the wild
- [moby/moby](https://github.com/moby/moby)
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
- [vmware/dispatch](https://github.com/vmware/dispatch)
- [Shopify/themekit](https://github.com/Shopify/themekit)
- [imdario/zas](https://github.com/imdario/zas)
- [matcornic/hermes](https://github.com/matcornic/hermes)
- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
- [kataras/iris](https://github.com/kataras/iris)
- [michaelsauter/crane](https://github.com/michaelsauter/crane)
- [go-task/task](https://github.com/go-task/task)
- [sensu/uchiwa](https://github.com/sensu/uchiwa)
- [ory/hydra](https://github.com/ory/hydra)
- [sisatech/vcli](https://github.com/sisatech/vcli)
- [dairycart/dairycart](https://github.com/dairycart/dairycart)
- [projectcalico/felix](https://github.com/projectcalico/felix)
- [resin-os/balena](https://github.com/resin-os/balena)
- [go-kivik/kivik](https://github.com/go-kivik/kivik)
- [Telefonica/govice](https://github.com/Telefonica/govice)
- [supergiant/supergiant](supergiant/supergiant)
- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
- [divshot/gitling](https://github.com/divshot/gitling)
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
- [elwinar/rambler](https://github.com/elwinar/rambler)
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
- [thoas/picfit](https://github.com/thoas/picfit)
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
## Installation
go get github.com/imdario/mergo
// use in your .go code
import (
"github.com/imdario/mergo"
)
## Usage
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are not considered zero values](https://golang.org/ref/spec#The_zero_value) either. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
```go
if err := mergo.Merge(&dst, src); err != nil {
// ...
}
```
Also, you can merge overwriting values using the transformer `WithOverride`.
```go
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
// ...
}
```
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
```go
if err := mergo.Map(&dst, srcMap); err != nil {
// ...
}
```
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo).
### Nice example
```go
package main
import (
"fmt"
"github.com/imdario/mergo"
)
type Foo struct {
A string
B int64
}
func main() {
src := Foo{
A: "one",
B: 2,
}
dest := Foo{
A: "two",
}
mergo.Merge(&dest, src)
fmt.Println(dest)
// Will print
// {two 2}
}
```
Note: if test are failing due missing package, please execute:
go get gopkg.in/yaml.v2
### Transformers
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
```go
package main
import (
"fmt"
"github.com/imdario/mergo"
"reflect"
"time"
)
type timeTransfomer struct {
}
func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
if typ == reflect.TypeOf(time.Time{}) {
return func(dst, src reflect.Value) error {
if dst.CanSet() {
isZero := dst.MethodByName("IsZero")
result := isZero.Call([]reflect.Value{})
if result[0].Bool() {
dst.Set(src)
}
}
return nil
}
}
return nil
}
type Snapshot struct {
Time time.Time
// ...
}
func main() {
src := Snapshot{time.Now()}
dest := Snapshot{}
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransfomer{}))
fmt.Println(dest)
// Will print
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
}
```
## Contact me
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
## About
Written by [Dario Castañé](http://dario.im).
## Top Contributors
[![0](https://sourcerer.io/fame/imdario/imdario/mergo/images/0)](https://sourcerer.io/fame/imdario/imdario/mergo/links/0)
[![1](https://sourcerer.io/fame/imdario/imdario/mergo/images/1)](https://sourcerer.io/fame/imdario/imdario/mergo/links/1)
[![2](https://sourcerer.io/fame/imdario/imdario/mergo/images/2)](https://sourcerer.io/fame/imdario/imdario/mergo/links/2)
[![3](https://sourcerer.io/fame/imdario/imdario/mergo/images/3)](https://sourcerer.io/fame/imdario/imdario/mergo/links/3)
[![4](https://sourcerer.io/fame/imdario/imdario/mergo/images/4)](https://sourcerer.io/fame/imdario/imdario/mergo/links/4)
[![5](https://sourcerer.io/fame/imdario/imdario/mergo/images/5)](https://sourcerer.io/fame/imdario/imdario/mergo/links/5)
[![6](https://sourcerer.io/fame/imdario/imdario/mergo/images/6)](https://sourcerer.io/fame/imdario/imdario/mergo/links/6)
[![7](https://sourcerer.io/fame/imdario/imdario/mergo/images/7)](https://sourcerer.io/fame/imdario/imdario/mergo/links/7)
## License
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)

44
vendor/github.com/imdario/mergo/doc.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package mergo merges same-type structs and maps by setting default values in zero-value fields.
Mergo won't merge unexported (private) fields but will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
Usage
From my own work-in-progress project:
type networkConfig struct {
Protocol string
Address string
ServerType string `json: "server_type"`
Port uint16
}
type FssnConfig struct {
Network networkConfig
}
var fssnDefault = FssnConfig {
networkConfig {
"tcp",
"127.0.0.1",
"http",
31560,
},
}
// Inside a function [...]
if err := mergo.Merge(&config, fssnDefault); err != nil {
log.Fatal(err)
}
// More code [...]
*/
package mergo

176
vendor/github.com/imdario/mergo/map.go generated vendored Normal file
View File

@ -0,0 +1,176 @@
// Copyright 2014 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"fmt"
"reflect"
"unicode"
"unicode/utf8"
)
func changeInitialCase(s string, mapper func(rune) rune) string {
if s == "" {
return s
}
r, n := utf8.DecodeRuneInString(s)
return string(mapper(r)) + s[n:]
}
func isExported(field reflect.StructField) bool {
r, _ := utf8.DecodeRuneInString(field.Name)
return r >= 'A' && r <= 'Z'
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
seen := visited[h]
typ := dst.Type()
for p := seen; p != nil; p = p.next {
if p.ptr == addr && p.typ == typ {
return nil
}
}
// Remember, remember...
visited[h] = &visit{addr, typ, seen}
}
zeroValue := reflect.Value{}
switch dst.Kind() {
case reflect.Map:
dstMap := dst.Interface().(map[string]interface{})
for i, n := 0, src.NumField(); i < n; i++ {
srcType := src.Type()
field := srcType.Field(i)
if !isExported(field) {
continue
}
fieldName := field.Name
fieldName = changeInitialCase(fieldName, unicode.ToLower)
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
dstMap[fieldName] = src.Field(i).Interface()
}
}
case reflect.Ptr:
if dst.IsNil() {
v := reflect.New(dst.Type().Elem())
dst.Set(v)
}
dst = dst.Elem()
fallthrough
case reflect.Struct:
srcMap := src.Interface().(map[string]interface{})
for key := range srcMap {
config.overwriteWithEmptyValue = true
srcValue := srcMap[key]
fieldName := changeInitialCase(key, unicode.ToUpper)
dstElement := dst.FieldByName(fieldName)
if dstElement == zeroValue {
// We discard it because the field doesn't exist.
continue
}
srcElement := reflect.ValueOf(srcValue)
dstKind := dstElement.Kind()
srcKind := srcElement.Kind()
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
srcElement = srcElement.Elem()
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
} else if dstKind == reflect.Ptr {
// Can this work? I guess it can't.
if srcKind != reflect.Ptr && srcElement.CanAddr() {
srcPtr := srcElement.Addr()
srcElement = reflect.ValueOf(srcPtr)
srcKind = reflect.Ptr
}
}
if !srcElement.IsValid() {
continue
}
if srcKind == dstKind {
if _, err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
if _, err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if srcKind == reflect.Map {
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else {
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
}
}
}
return
}
// Map sets fields' values in dst from src.
// src can be a map with string keys or a struct. dst must be the opposite:
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
// dst must be map[string]interface{}.
// It won't merge unexported (private) fields and will do recursively
// any exported field.
// If dst is a map, keys will be src fields' names in lower camel case.
// Missing key in src that doesn't match a field in dst will be skipped. This
// doesn't apply if dst is a map.
// This is separated method from Merge because it is cleaner and it keeps sane
// semantics: merging equal types, mapping different (restricted) types.
func Map(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, opts...)
}
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: Use Map(…) with WithOverride
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, append(opts, WithOverride)...)
}
func _map(dst, src interface{}, opts ...func(*Config)) error {
var (
vDst, vSrc reflect.Value
err error
)
config := &Config{}
for _, opt := range opts {
opt(config)
}
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
return err
}
// To be friction-less, we redirect equal-type arguments
// to deepMerge. Only because arguments can be anything.
if vSrc.Kind() == vDst.Kind() {
_, err := deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
return err
}
switch vSrc.Kind() {
case reflect.Struct:
if vDst.Kind() != reflect.Map {
return ErrExpectedMapAsDestination
}
case reflect.Map:
if vDst.Kind() != reflect.Struct {
return ErrExpectedStructAsDestination
}
default:
return ErrNotSupported
}
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}

338
vendor/github.com/imdario/mergo/merge.go generated vendored Normal file
View File

@ -0,0 +1,338 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"fmt"
"reflect"
"unsafe"
)
func hasExportedField(dst reflect.Value) (exported bool) {
for i, n := 0, dst.NumField(); i < n; i++ {
field := dst.Type().Field(i)
if isExportedComponent(&field) {
return true
}
}
return
}
func isExportedComponent(field *reflect.StructField) bool {
name := field.Name
pkgPath := field.PkgPath
if len(pkgPath) > 0 {
return false
}
c := name[0]
if 'a' <= c && c <= 'z' || c == '_' {
return false
}
return true
}
type Config struct {
Overwrite bool
AppendSlice bool
TypeCheck bool
Transformers Transformers
overwriteWithEmptyValue bool
overwriteSliceWithEmptyValue bool
}
type Transformers interface {
Transformer(reflect.Type) func(dst, src reflect.Value) error
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMerge(dstIn, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (dst reflect.Value, err error) {
dst = dstIn
overwrite := config.Overwrite
typeCheck := config.TypeCheck
overwriteWithEmptySrc := config.overwriteWithEmptyValue
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
if !src.IsValid() {
return
}
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
seen := visited[h]
typ := dst.Type()
for p := seen; p != nil; p = p.next {
if p.ptr == addr && p.typ == typ {
return dst, nil
}
}
// Remember, remember...
visited[h] = &visit{addr, typ, seen}
}
if config.Transformers != nil && !isEmptyValue(dst) {
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
err = fn(dst, src)
return
}
}
if dst.IsValid() && src.IsValid() && src.Type() != dst.Type() {
err = fmt.Errorf("cannot append two different types (%s, %s)", src.Kind(), dst.Kind())
return
}
switch dst.Kind() {
case reflect.Struct:
if hasExportedField(dst) {
dstCp := reflect.New(dst.Type()).Elem()
for i, n := 0, dst.NumField(); i < n; i++ {
dstField := dst.Field(i)
structField := dst.Type().Field(i)
// copy un-exported struct fields
if !isExportedComponent(&structField) {
rf := dstCp.Field(i)
rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() //nolint:gosec
dstRF := dst.Field(i)
if !dst.Field(i).CanAddr() {
continue
}
dstRF = reflect.NewAt(dstRF.Type(), unsafe.Pointer(dstRF.UnsafeAddr())).Elem() //nolint:gosec
rf.Set(dstRF)
continue
}
dstField, err = deepMerge(dstField, src.Field(i), visited, depth+1, config)
if err != nil {
return
}
dstCp.Field(i).Set(dstField)
}
if dst.CanSet() {
dst.Set(dstCp)
} else {
dst = dstCp
}
return
} else {
if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) {
dst = src
}
}
case reflect.Map:
if dst.IsNil() && !src.IsNil() {
if dst.CanSet() {
dst.Set(reflect.MakeMap(dst.Type()))
} else {
dst = src
return
}
}
for _, key := range src.MapKeys() {
srcElement := src.MapIndex(key)
dstElement := dst.MapIndex(key)
if !srcElement.IsValid() {
continue
}
if dst.MapIndex(key).IsValid() {
k := dstElement.Interface()
dstElement = reflect.ValueOf(k)
}
if isReflectNil(srcElement) {
if overwrite || isReflectNil(dstElement) {
dst.SetMapIndex(key, srcElement)
}
continue
}
if !srcElement.CanInterface() {
continue
}
if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
if dstElement.IsValid() {
dstElement = reflect.ValueOf(dstElement.Interface())
}
}
dstElement, err = deepMerge(dstElement, srcElement, visited, depth+1, config)
if err != nil {
return
}
dst.SetMapIndex(key, dstElement)
}
case reflect.Slice:
newSlice := dst
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
if typeCheck && src.Type() != dst.Type() {
return dst, fmt.Errorf("cannot override two slices with different type (%s, %s)", src.Type(), dst.Type())
}
newSlice = src
} else if config.AppendSlice {
if typeCheck && src.Type() != dst.Type() {
err = fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
return
}
newSlice = reflect.AppendSlice(dst, src)
}
if dst.CanSet() {
dst.Set(newSlice)
} else {
dst = newSlice
}
case reflect.Ptr, reflect.Interface:
if isReflectNil(src) {
break
}
if dst.Kind() != reflect.Ptr && src.Type().AssignableTo(dst.Type()) {
if dst.IsNil() || overwrite {
if overwrite || isEmptyValue(dst) {
if dst.CanSet() {
dst.Set(src)
} else {
dst = src
}
}
}
break
}
if src.Kind() != reflect.Interface {
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
dst.Set(src)
}
} else if src.Kind() == reflect.Ptr {
if dst, err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
dst = dst.Addr()
} else if dst.Elem().Type() == src.Type() {
if dst, err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
return
}
} else {
return dst, ErrDifferentArgumentsTypes
}
break
}
if dst.IsNil() || overwrite {
if (overwrite || isEmptyValue(dst)) && (overwriteWithEmptySrc || !isEmptyValue(src)) {
if dst.CanSet() {
dst.Set(src)
} else {
dst = src
}
}
} else if _, err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
default:
overwriteFull := (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst))
if overwriteFull {
if dst.CanSet() {
dst.Set(src)
} else {
dst = src
}
}
}
return
}
// Merge will fill any empty for value type attributes on the dst struct using corresponding
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
// and dst must be a pointer to struct.
// It won't merge unexported (private) fields and will do recursively any exported field.
func Merge(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, opts...)
}
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: use Merge(…) with WithOverride
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, append(opts, WithOverride)...)
}
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
func WithTransformers(transformers Transformers) func(*Config) {
return func(config *Config) {
config.Transformers = transformers
}
}
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
func WithOverride(config *Config) {
config.Overwrite = true
}
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
func WithOverwriteWithEmptyValue(config *Config) {
config.overwriteWithEmptyValue = true
}
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
func WithOverrideEmptySlice(config *Config) {
config.overwriteSliceWithEmptyValue = true
}
// WithAppendSlice will make merge append slices instead of overwriting it.
func WithAppendSlice(config *Config) {
config.AppendSlice = true
}
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
func WithTypeCheck(config *Config) {
config.TypeCheck = true
}
func merge(dst, src interface{}, opts ...func(*Config)) error {
var (
vDst, vSrc reflect.Value
err error
)
config := &Config{}
for _, opt := range opts {
opt(config)
}
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
return err
}
if !vDst.CanSet() {
return fmt.Errorf("cannot set dst, needs reference")
}
if vDst.Type() != vSrc.Type() {
return ErrDifferentArgumentsTypes
}
_, err = deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
return err
}
// IsReflectNil is the reflect value provided nil
func isReflectNil(v reflect.Value) bool {
k := v.Kind()
switch k {
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
// Both interface and slice are nil if first word is 0.
// Both are always bigger than a word; assume flagIndir.
return v.IsNil()
default:
return false
}
}

97
vendor/github.com/imdario/mergo/mergo.go generated vendored Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"errors"
"reflect"
)
// Errors reported by Mergo when it finds invalid arguments.
var (
ErrNilArguments = errors.New("src and dst must not be nil")
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
ErrNotSupported = errors.New("only structs and maps are supported")
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
)
// During deepMerge, must keep track of checks that are
// in progress. The comparison algorithm assumes that all
// checks in progress are true when it reencounters them.
// Visited are stored in a map indexed by 17 * a1 + a2;
type visit struct {
ptr uintptr
typ reflect.Type
next *visit
}
// From src/pkg/encoding/json/encode.go.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
if v.IsNil() {
return true
}
return isEmptyValue(v.Elem())
case reflect.Func:
return v.IsNil()
case reflect.Invalid:
return true
}
return false
}
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
if dst == nil || src == nil {
err = ErrNilArguments
return
}
vDst = reflect.ValueOf(dst).Elem()
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
err = ErrNotSupported
return
}
vSrc = reflect.ValueOf(src)
// We check if vSrc is a pointer to dereference it.
if vSrc.Kind() == reflect.Ptr {
vSrc = vSrc.Elem()
}
return
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
seen := visited[h]
typ := dst.Type()
for p := seen; p != nil; p = p.next {
if p.ptr == addr && p.typ == typ {
return nil
}
}
// Remember, remember...
visited[h] = &visit{addr, typ, seen}
}
return // TODO refactor
}

View File

@ -101,7 +101,7 @@ is supported by go-git.
| http(s):// (smart) | ✔ | | http(s):// (smart) | ✔ |
| git:// | ✔ | | git:// | ✔ |
| ssh:// | ✔ | | ssh:// | ✔ |
| file:// | | | file:// | partial | Warning: this is not pure Golang. This shells out to the `git` binary. |
| custom | ✔ | | custom | ✔ |
| **other features** | | **other features** |
| gitignore | ✔ | | gitignore | ✔ |

View File

@ -1,9 +1,9 @@
![go-git logo](https://cdn.rawgit.com/src-d/artwork/02036484/go-git/files/go-git-github-readme-header.png) ![go-git logo](https://cdn.rawgit.com/src-d/artwork/02036484/go-git/files/go-git-github-readme-header.png)
[![GoDoc](https://godoc.org/github.com/go-git/go-git/v5?status.svg)](https://godoc.org/github.com/src-d/go-git) [![Build Status](https://github.com/go-git/go-git/workflows/Test%20&%20Coverage/badge.svg)](https://github.com/go-git/go-git/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/src-d/go-git)](https://goreportcard.com/report/github.com/src-d/go-git) [![GoDoc](https://godoc.org/github.com/jesseduffield/go-git/v5?status.svg)](https://pkg.go.dev/github.com/jesseduffield/go-git/v5) [![Build Status](https://github.com/go-git/go-git/workflows/Test/badge.svg)](https://github.com/go-git/go-git/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/go-git/go-git)](https://goreportcard.com/report/github.com/go-git/go-git)
*go-git* is a highly extensible git implementation library written in **pure Go**. *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/jesseduffield/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. 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**. 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 Comparison with git
@ -29,7 +29,7 @@ Installation
The recommended way to install *go-git* is: The recommended way to install *go-git* is:
```go ```go
import "github.com/go-git/go-git/v5" // with go modules enabled (GO111MODULE=on or outside GOPATH) import "github.com/jesseduffield/go-git/v5" // with go modules enabled (GO111MODULE=on or outside GOPATH)
import "github.com/go-git/go-git" // with go modules disabled import "github.com/go-git/go-git" // with go modules disabled
``` ```
@ -37,7 +37,7 @@ import "github.com/go-git/go-git" // with go modules disabled
Examples 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 ### Basic example

View File

@ -9,9 +9,9 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/jesseduffield/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/utils/diff" "github.com/jesseduffield/go-git/v5/utils/diff"
) )
// BlameResult represents the result of a Blame operation. // BlameResult represents the result of a Blame operation.

View File

@ -3,8 +3,8 @@ package config
import ( import (
"errors" "errors"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
format "github.com/go-git/go-git/v5/plumbing/format/config" format "github.com/jesseduffield/go-git/v5/plumbing/format/config"
) )
var ( var (

View File

@ -5,11 +5,16 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"github.com/go-git/go-git/v5/internal/url" "github.com/jesseduffield/go-git/v5/internal/url"
format "github.com/go-git/go-git/v5/plumbing/format/config" format "github.com/jesseduffield/go-git/v5/plumbing/format/config"
"github.com/mitchellh/go-homedir"
) )
const ( const (
@ -32,6 +37,16 @@ var (
ErrRemoteConfigEmptyName = errors.New("remote config: empty name") 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 // Config contains the repository configuration
// https://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES // https://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES
type Config struct { type Config struct {
@ -46,6 +61,27 @@ type Config struct {
CommentChar string 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 { Pack struct {
// Window controls the size of the sliding window for delta // Window controls the size of the sliding window for delta
// compression. The default is 10. A value of 0 turns off // compression. The default is 10. A value of 0 turns off
@ -82,6 +118,77 @@ func NewConfig() *Config {
return 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. // Validate validates the fields and sets the default values.
func (c *Config) Validate() error { func (c *Config) Validate() error {
for name, r := range c.Remotes { for name, r := range c.Remotes {
@ -113,6 +220,9 @@ const (
branchSection = "branch" branchSection = "branch"
coreSection = "core" coreSection = "core"
packSection = "pack" packSection = "pack"
userSection = "user"
authorSection = "author"
committerSection = "committer"
fetchKey = "fetch" fetchKey = "fetch"
urlKey = "url" urlKey = "url"
bareKey = "bare" bareKey = "bare"
@ -121,6 +231,8 @@ const (
windowKey = "window" windowKey = "window"
mergeKey = "merge" mergeKey = "merge"
rebaseKey = "rebase" rebaseKey = "rebase"
nameKey = "name"
emailKey = "email"
// DefaultPackWindow holds the number of previous objects used to // DefaultPackWindow holds the number of previous objects used to
// generate deltas. The value 10 is the same used by git command. // generate deltas. The value 10 is the same used by git command.
@ -138,14 +250,15 @@ func (c *Config) Unmarshal(b []byte) error {
} }
c.unmarshalCore() c.unmarshalCore()
c.unmarshalUser()
if err := c.unmarshalPack(); err != nil { if err := c.unmarshalPack(); err != nil {
return err return err
} }
unmarshalSubmodules(c.Raw, c.Submodules) unmarshalSubmodules(c.Raw, c.Submodules)
if err := c.unmarshalBranches(); err != nil { // ignore error
return err // Why ignore the error? It seems overly strict and for my use case none of the errors matter to me
} c.unmarshalBranches()
return c.unmarshalRemotes() return c.unmarshalRemotes()
} }
@ -160,6 +273,20 @@ func (c *Config) unmarshalCore() {
c.Core.CommentChar = s.Options.Get(commentCharKey) 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 { func (c *Config) unmarshalPack() error {
s := c.Raw.Section(packSection) s := c.Raw.Section(packSection)
window := s.Options.Get(windowKey) window := s.Options.Get(windowKey)
@ -209,7 +336,7 @@ func (c *Config) unmarshalBranches() error {
b := &Branch{} b := &Branch{}
if err := b.unmarshal(sub); err != nil { if err := b.unmarshal(sub); err != nil {
return err // ignore error
} }
c.Branches[b.Name] = b c.Branches[b.Name] = b
@ -220,6 +347,7 @@ func (c *Config) unmarshalBranches() error {
// Marshal returns Config encoded as a git-config file. // Marshal returns Config encoded as a git-config file.
func (c *Config) Marshal() ([]byte, error) { func (c *Config) Marshal() ([]byte, error) {
c.marshalCore() c.marshalCore()
c.marshalUser()
c.marshalPack() c.marshalPack()
c.marshalRemotes() c.marshalRemotes()
c.marshalSubmodules() 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() { func (c *Config) marshalPack() {
s := c.Raw.Section(packSection) s := c.Raw.Section(packSection)
if c.Pack.Window != DefaultPackWindow { if c.Pack.Window != DefaultPackWindow {

View File

@ -5,7 +5,7 @@ import (
"errors" "errors"
"regexp" "regexp"
format "github.com/go-git/go-git/v5/plumbing/format/config" format "github.com/jesseduffield/go-git/v5/plumbing/format/config"
) )
var ( var (

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"strings" "strings"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
) )
const ( const (
@ -25,7 +25,7 @@ var (
// reference even if it isn’t a fast-forward. // reference even if it isn’t a fast-forward.
// eg.: "+refs/heads/*:refs/remotes/origin/*" // 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 type RefSpec string
// Validate validates the RefSpec // Validate validates the RefSpec
@ -59,6 +59,11 @@ func (s RefSpec) IsDelete() bool {
return s[0] == refSpecSeparator[0] 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. // Src return the src side.
func (s RefSpec) Src() string { func (s RefSpec) Src() string {
spec := string(s) spec := string(s)
@ -69,8 +74,8 @@ func (s RefSpec) Src() string {
} else { } else {
start = 0 start = 0
} }
end := strings.Index(spec, refSpecSeparator)
end := strings.Index(spec, refSpecSeparator)
return spec[start:end] return spec[start:end]
} }

View File

@ -1,4 +1,4 @@
module github.com/go-git/go-git/v5 module github.com/jesseduffield/go-git/v5
require ( require (
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
@ -8,8 +8,9 @@ require (
github.com/gliderlabs/ssh v0.2.2 github.com/gliderlabs/ssh v0.2.2
github.com/go-git/gcfg v1.5.0 github.com/go-git/gcfg v1.5.0
github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git-fixtures/v4 v4.0.1 github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12
github.com/google/go-cmp v0.3.0 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
github.com/jessevdk/go-flags v1.4.0 github.com/jessevdk/go-flags v1.4.0
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd

View File

@ -20,8 +20,12 @@ github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agR
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc= github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/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 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 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= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=

View File

@ -28,7 +28,7 @@ func (e *ErrInvalidRevision) Error() string {
type Revisioner interface { type Revisioner interface {
} }
// Ref represents a reference name : HEAD, master // Ref represents a reference name : HEAD, master, <hash>
type Ref string type Ref string
// TildePath represents ~, ~{n} // TildePath represents ~, ~{n}
@ -297,7 +297,7 @@ func (p *Parser) parseAt() (Revisioner, error) {
} }
if t != cbrace { if t != cbrace {
return nil, &ErrInvalidRevision{fmt.Sprintf(`missing "}" in @{-n} structure`)} return nil, &ErrInvalidRevision{s: `missing "}" in @{-n} structure`}
} }
return AtCheckout{n}, nil return AtCheckout{n}, nil
@ -419,7 +419,7 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) {
case re == "" && tok == emark && nextTok == minus: case re == "" && tok == emark && nextTok == minus:
negate = true negate = true
case re == "" && tok == emark: case re == "" && tok == emark:
return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} return nil, &ErrInvalidRevision{s: `revision suffix brace component sequences starting with "/!" others than those defined are reserved`}
case re == "" && tok == slash: case re == "" && tok == slash:
p.unscan() p.unscan()
case tok != slash && start: case tok != slash && start:
@ -490,7 +490,7 @@ func (p *Parser) parseColonSlash() (Revisioner, error) {
case re == "" && tok == emark && nextTok == minus: case re == "" && tok == emark && nextTok == minus:
negate = true negate = true
case re == "" && tok == emark: case re == "" && tok == emark:
return nil, &ErrInvalidRevision{fmt.Sprintf(`revision suffix brace component sequences starting with "/!" others than those defined are reserved`)} return nil, &ErrInvalidRevision{s: `revision suffix brace component sequences starting with "/!" others than those defined are reserved`}
case tok == eof: case tok == eof:
p.unscan() p.unscan()
reg, err := regexp.Compile(re) reg, err := regexp.Compile(re)

View File

@ -3,10 +3,10 @@ package git
import ( import (
"fmt" "fmt"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode" "github.com/jesseduffield/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/jesseduffield/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage" "github.com/jesseduffield/go-git/v5/storage"
) )
type objectWalker struct { type objectWalker struct {

View File

@ -2,16 +2,17 @@ package git
import ( import (
"errors" "errors"
"fmt"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/jesseduffield/go-git/v5/config"
"github.com/jesseduffield/go-git/v5/plumbing"
"github.com/jesseduffield/go-git/v5/plumbing/object"
"github.com/jesseduffield/go-git/v5/plumbing/protocol/packp/sideband"
"github.com/jesseduffield/go-git/v5/plumbing/transport"
"golang.org/x/crypto/openpgp" "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"
) )
// SubmoduleRescursivity defines how depth will affect any submodule recursive // SubmoduleRescursivity defines how depth will affect any submodule recursive
@ -190,6 +191,9 @@ type PushOptions struct {
// Prune specify that remote refs that match given RefSpecs and that do // Prune specify that remote refs that match given RefSpecs and that do
// not exist locally will be removed. // not exist locally will be removed.
Prune bool 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. // Validate validates the fields and sets the default values.
@ -370,12 +374,37 @@ var (
ErrMissingAuthor = errors.New("author field is required") ErrMissingAuthor = errors.New("author field is required")
) )
// AddOptions describes how a add operation should be performed
type AddOptions struct {
// All equivalent to `git add -A`, update the index not only where the
// working tree has a file matching `Path` but also where the index already
// has an entry. This adds, modifies, and removes index entries to match the
// working tree. If no `Path` nor `Glob` is given when `All` option is
// used, all files in the entire working tree are updated.
All bool
// Path is the exact filepath to a the file or directory to be added.
Path string
// Glob adds all paths, matching pattern, to the index. If pattern matches a
// directory path, all directory contents are added to the index recursively.
Glob string
}
// Validate validates the fields and sets the default values.
func (o *AddOptions) Validate(r *Repository) error {
if o.Path != "" && o.Glob != "" {
return fmt.Errorf("fields Path and Glob are mutual exclusive")
}
return nil
}
// CommitOptions describes how a commit operation should be performed. // CommitOptions describes how a commit operation should be performed.
type CommitOptions struct { type CommitOptions struct {
// All automatically stage files that have been modified and deleted, but // All automatically stage files that have been modified and deleted, but
// new files you have not told Git about are not affected. // new files you have not told Git about are not affected.
All bool 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 Author *object.Signature
// Committer is the committer's signature of the commit. If Committer is // Committer is the committer's signature of the commit. If Committer is
// nil the Author signature is used. // nil the Author signature is used.
@ -392,7 +421,9 @@ type CommitOptions struct {
// Validate validates the fields and sets the default values. // Validate validates the fields and sets the default values.
func (o *CommitOptions) Validate(r *Repository) error { func (o *CommitOptions) Validate(r *Repository) error {
if o.Author == nil { if o.Author == nil {
return ErrMissingAuthor if err := o.loadConfigAuthorAndCommitter(r); err != nil {
return err
}
} }
if o.Committer == nil { if o.Committer == nil {
@ -413,6 +444,43 @@ func (o *CommitOptions) Validate(r *Repository) error {
return nil 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 ( var (
ErrMissingName = errors.New("name field is required") ErrMissingName = errors.New("name field is required")
ErrMissingTagger = errors.New("tagger field is required") ErrMissingTagger = errors.New("tagger field is required")
@ -421,7 +489,8 @@ var (
// CreateTagOptions describes how a tag object should be created. // CreateTagOptions describes how a tag object should be created.
type CreateTagOptions struct { type CreateTagOptions struct {
// Tagger defines the signature of the tag creator. // Tagger defines the signature of the tag creator. If Tagger is empty the
// Name and Email is read from the config, and time.Now it's used as When.
Tagger *object.Signature Tagger *object.Signature
// Message defines the annotation of the tag. It is canonicalized during // Message defines the annotation of the tag. It is canonicalized during
// validation into the format expected by git - no leading whitespace and // validation into the format expected by git - no leading whitespace and
@ -435,7 +504,9 @@ type CreateTagOptions struct {
// Validate validates the fields and sets the default values. // Validate validates the fields and sets the default values.
func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error { func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error {
if o.Tagger == nil { if o.Tagger == nil {
return ErrMissingTagger if err := o.loadConfigTagger(r); err != nil {
return err
}
} }
if o.Message == "" { if o.Message == "" {
@ -448,6 +519,35 @@ func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error {
return nil return nil
} }
func (o *CreateTagOptions) loadConfigTagger(r *Repository) error {
cfg, err := r.ConfigScoped(config.SystemScope)
if err != nil {
return err
}
if o.Tagger == nil && cfg.Author.Email != "" && cfg.Author.Name != "" {
o.Tagger = &object.Signature{
Name: cfg.Author.Name,
Email: cfg.Author.Email,
When: time.Now(),
}
}
if o.Tagger == nil && cfg.User.Email != "" && cfg.User.Name != "" {
o.Tagger = &object.Signature{
Name: cfg.User.Name,
Email: cfg.User.Email,
When: time.Now(),
}
}
if o.Tagger == nil {
return ErrMissingTagger
}
return nil
}
// ListOptions describes how a remote list should be performed. // ListOptions describes how a remote list should be performed.
type ListOptions struct { type ListOptions struct {
// Auth credentials, if required, to use with the remote repository. // Auth credentials, if required, to use with the remote repository.
@ -502,6 +602,9 @@ type PlainOpenOptions struct {
// DetectDotGit defines whether parent directories should be // DetectDotGit defines whether parent directories should be
// walked until a .git directory or file is found. // walked until a .git directory or file is found.
DetectDotGit bool DetectDotGit bool
// Enable .git/commondir support (see https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt).
// NOTE: This option will only work with the filesystem storage.
EnableDotGitCommonDir bool
} }
// Validate validates the fields and sets the default values. // Validate validates the fields and sets the default values.

View File

@ -1,6 +1,6 @@
package cache package cache
import "github.com/go-git/go-git/v5/plumbing" import "github.com/jesseduffield/go-git/v5/plumbing"
const ( const (
Byte FileSize = 1 << (iota * 10) Byte FileSize = 1 << (iota * 10)

View File

@ -4,7 +4,7 @@ import (
"container/list" "container/list"
"sync" "sync"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
) )
// ObjectLRU implements an object cache with an LRU eviction policy and a // ObjectLRU implements an object cache with an LRU eviction policy and a

View 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"
)

View File

@ -118,7 +118,7 @@ func isSetSymLink(m os.FileMode) bool {
func (m FileMode) Bytes() []byte { func (m FileMode) Bytes() []byte {
ret := make([]byte, 4) ret := make([]byte, 4)
binary.LittleEndian.PutUint32(ret, uint32(m)) binary.LittleEndian.PutUint32(ret, uint32(m))
return ret[:] return ret
} }
// IsMalformed returns if the FileMode should not appear in a git packfile, // IsMalformed returns if the FileMode should not appear in a git packfile,

View File

@ -19,7 +19,7 @@ type Options []*Option
// IsKey returns true if the given key matches // IsKey returns true if the given key matches
// this option's key in a case-insensitive comparison. // this option's key in a case-insensitive comparison.
func (o *Option) IsKey(key string) bool { func (o *Option) IsKey(key string) bool {
return strings.ToLower(o.Key) == strings.ToLower(key) return strings.EqualFold(o.Key, key)
} }
func (opts Options) GoString() string { func (opts Options) GoString() string {

View File

@ -61,7 +61,7 @@ func (s Subsections) GoString() string {
// IsName checks if the name provided is equals to the Section name, case insensitive. // IsName checks if the name provided is equals to the Section name, case insensitive.
func (s *Section) IsName(name string) bool { func (s *Section) IsName(name string) bool {
return strings.ToLower(s.Name) == strings.ToLower(name) return strings.EqualFold(s.Name, name)
} }
// Option return the value for the specified key. Empty string is returned if // Option return the value for the specified key. Empty string is returned if

View File

@ -0,0 +1,97 @@
package diff
import "github.com/jesseduffield/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
}

View File

@ -1,8 +1,8 @@
package diff package diff
import ( import (
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode" "github.com/jesseduffield/go-git/v5/plumbing/filemode"
) )
// Operation defines the operation of a diff item. // Operation defines the operation of a diff item.

View File

@ -0,0 +1,376 @@
package diff
import (
"fmt"
"io"
"regexp"
"strconv"
"strings"
"github.com/jesseduffield/go-git/v5/plumbing"
)
// DefaultContextLines is the default number of context lines.
const DefaultContextLines = 3
var (
splitLinesRegexp = regexp.MustCompile(`[^\n]*(\n|$)`)
operationChar = map[Operation]byte{
Add: '+',
Delete: '-',
Equal: ' ',
}
operationColorKey = map[Operation]ColorKey{
Add: New,
Delete: Old,
Equal: Context,
}
)
// 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
// contextLines is the count of unchanged lines that will appear surrounding
// a change.
contextLines int
// colorConfig is the color configuration. The default is no color.
color ColorConfig
}
// 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 {
sb := &strings.Builder{}
if message := patch.Message(); message != "" {
sb.WriteString(message)
if !strings.HasSuffix(message, "\n") {
sb.WriteByte('\n')
}
}
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) writeFilePatchHeader(sb *strings.Builder, filePatch FilePatch) {
from, to := filePatch.Files()
if from == nil && to == nil {
return
}
isBinary := filePatch.IsBinary()
var lines []string
switch {
case from != nil && to != nil:
hashEquals := from.Hash() == to.Hash()
lines = append(lines,
fmt.Sprintf("diff --git a/%s b/%s", from.Path(), to.Path()),
)
if 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() {
lines = append(lines,
fmt.Sprintf("rename from %s", from.Path()),
fmt.Sprintf("rename to %s", to.Path()),
)
}
if from.Mode() != to.Mode() && !hashEquals {
lines = append(lines,
fmt.Sprintf("index %s..%s", from.Hash(), to.Hash()),
)
} else if !hashEquals {
lines = append(lines,
fmt.Sprintf("index %s..%s %o", from.Hash(), to.Hash(), from.Mode()),
)
}
if !hashEquals {
lines = e.appendPathLines(lines, "a/"+from.Path(), "b/"+to.Path(), isBinary)
}
case from == nil:
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:
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)
}
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) appendPathLines(lines []string, fromPath, toPath string, isBinary bool) []string {
if isBinary {
return append(lines,
fmt.Sprintf("Binary files %s and %s differ", fromPath, toPath),
)
}
return append(lines,
fmt.Sprintf("--- %s", fromPath),
fmt.Sprintf("+++ %s", toPath),
)
}
type hunksGenerator struct {
fromLine, toLine int
ctxLines int
chunks []Chunk
current *hunk
hunks []*hunk
beforeContext, afterContext []string
}
func newHunksGenerator(chunks []Chunk, ctxLines int) *hunksGenerator {
return &hunksGenerator{
chunks: chunks,
ctxLines: ctxLines,
}
}
func (g *hunksGenerator) Generate() []*hunk {
for i, chunk := range g.chunks {
lines := splitLines(chunk.Content())
nLines := len(lines)
switch chunk.Type() {
case Equal:
g.fromLine += nLines
g.toLine += nLines
g.processEqualsLines(lines, i)
case Delete:
if nLines != 0 {
g.fromLine++
}
g.processHunk(i, chunk.Type())
g.fromLine += nLines - 1
g.current.AddOp(chunk.Type(), lines...)
case Add:
if nLines != 0 {
g.toLine++
}
g.processHunk(i, chunk.Type())
g.toLine += nLines - 1
g.current.AddOp(chunk.Type(), lines...)
}
if i == len(g.chunks)-1 && g.current != nil {
g.hunks = append(g.hunks, g.current)
}
}
return g.hunks
}
func (g *hunksGenerator) processHunk(i int, op Operation) {
if g.current != nil {
return
}
var ctxPrefix string
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
}
g.current = &hunk{ctxPrefix: strings.TrimSuffix(ctxPrefix, "\n")}
g.current.AddOp(Equal, g.beforeContext...)
switch op {
case Delete:
g.current.fromLine, g.current.toLine =
g.addLineNumbers(g.fromLine, g.toLine, linesBefore, i, Add)
case Add:
g.current.toLine, g.current.fromLine =
g.addLineNumbers(g.toLine, g.fromLine, linesBefore, i, Delete)
}
g.beforeContext = nil
}
// 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 && g.ctxLines != 0:
if lb > g.ctxLines {
clb = lb - g.ctxLines + 1
} else {
clb = 1
}
case g.ctxLines == 0:
clb = lb
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
}
}
return
}
func (g *hunksGenerator) processEqualsLines(ls []string, i int) {
if g.current == nil {
g.beforeContext = append(g.beforeContext, ls...)
return
}
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 := g.ctxLines
if ctxLines > len(g.afterContext) {
ctxLines = len(g.afterContext)
}
g.current.AddOp(Equal, g.afterContext[:ctxLines]...)
g.hunks = append(g.hunks, g.current)
g.current = nil
g.beforeContext = g.afterContext[ctxLines:]
g.afterContext = nil
}
}
func splitLines(s string) []string {
out := splitLinesRegexp.FindAllString(s, -1)
if out[len(out)-1] == "" {
out = out[:len(out)-1]
}
return out
}
type hunk struct {
fromLine int
toLine int
fromCount int
toCount int
ctxPrefix string
ops []*op
}
func (h *hunk) writeTo(sb *strings.Builder, color ColorConfig) {
sb.WriteString(color[Frag])
sb.WriteString("@@ -")
if h.fromCount == 1 {
sb.WriteString(strconv.Itoa(h.fromLine))
} else {
sb.WriteString(strconv.Itoa(h.fromLine))
sb.WriteByte(',')
sb.WriteString(strconv.Itoa(h.fromCount))
}
sb.WriteString(" +")
if h.toCount == 1 {
sb.WriteString(strconv.Itoa(h.toLine))
} else {
sb.WriteString(strconv.Itoa(h.toLine))
sb.WriteByte(',')
sb.WriteString(strconv.Itoa(h.toCount))
}
sb.WriteString(" @@")
sb.WriteString(color.Reset(Frag))
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 (h *hunk) AddOp(t Operation, ss ...string) {
n := len(ss)
switch t {
case Add:
h.toCount += n
case Delete:
h.fromCount += n
case Equal:
h.toCount += n
h.fromCount += n
}
for _, s := range ss {
h.ops = append(h.ops, &op{s, t})
}
}
type op struct {
text string
t Operation
}
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")
}
sb.WriteString(color.Reset(colorKey))
sb.WriteByte('\n')
}

View File

@ -1,6 +1,7 @@
package gitignore package gitignore
import ( import (
"bufio"
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
@ -8,14 +9,13 @@ import (
"strings" "strings"
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/plumbing/format/config" "github.com/jesseduffield/go-git/v5/plumbing/format/config"
gioutil "github.com/go-git/go-git/v5/utils/ioutil" gioutil "github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
const ( const (
commentPrefix = "#" commentPrefix = "#"
coreSection = "core" coreSection = "core"
eol = "\n"
excludesfile = "excludesfile" excludesfile = "excludesfile"
gitDir = ".git" gitDir = ".git"
gitignoreFile = ".gitignore" gitignoreFile = ".gitignore"
@ -29,11 +29,11 @@ func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps [
if err == nil { if err == nil {
defer f.Close() defer f.Close()
if data, err := ioutil.ReadAll(f); err == nil { scanner := bufio.NewScanner(f)
for _, s := range strings.Split(string(data), eol) { for scanner.Scan() {
if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 { s := scanner.Text()
ps = append(ps, ParsePattern(s, path)) if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
} ps = append(ps, ParsePattern(s, path))
} }
} }
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {

View File

@ -6,7 +6,7 @@ import (
"errors" "errors"
"io" "io"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
) )
var ( var (

View File

@ -5,7 +5,7 @@ import (
"hash" "hash"
"io" "io"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
) )
// Encoder writes MemoryIndex structs to an output stream. // Encoder writes MemoryIndex structs to an output stream.

View File

@ -7,7 +7,7 @@ import (
encbin "encoding/binary" encbin "encoding/binary"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
) )
const ( const (

View File

@ -7,8 +7,8 @@ import (
"sort" "sort"
"sync" "sync"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
) )
// objects implements sort.Interface and uses hash as sorting key. // objects implements sort.Interface and uses hash as sorting key.

View File

@ -11,8 +11,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
) )
var ( var (
@ -188,7 +188,7 @@ func (d *Decoder) doReadEntryNameV4() (string, error) {
func (d *Decoder) doReadEntryName(len uint16) (string, error) { func (d *Decoder) doReadEntryName(len uint16) (string, error) {
name := make([]byte, len) name := make([]byte, len)
_, err := io.ReadFull(d.r, name[:]) _, err := io.ReadFull(d.r, name)
return string(name), err return string(name), err
} }
@ -390,7 +390,9 @@ func (d *treeExtensionDecoder) readEntry() (*TreeEntry, error) {
e.Trees = i e.Trees = i
_, err = io.ReadFull(d.r, e.Hash[:]) _, err = io.ReadFull(d.r, e.Hash[:])
if err != nil {
return nil, err
}
return e, nil return e, nil
} }

View File

@ -9,7 +9,7 @@ import (
"sort" "sort"
"time" "time"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
) )
var ( var (

View File

@ -7,8 +7,8 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode" "github.com/jesseduffield/go-git/v5/plumbing/filemode"
) )
var ( var (

View File

@ -6,8 +6,8 @@ import (
"io" "io"
"strconv" "strconv"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/packfile" "github.com/jesseduffield/go-git/v5/plumbing/format/packfile"
) )
var ( var (

View File

@ -6,7 +6,7 @@ import (
"io" "io"
"strconv" "strconv"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
) )
var ( var (

View File

@ -6,8 +6,8 @@ import (
"io" "io"
"sync" "sync"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/utils/ioutil" "github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
var signature = []byte{'P', 'A', 'C', 'K'} var signature = []byte{'P', 'A', 'C', 'K'}

View File

@ -4,8 +4,8 @@ import (
"sort" "sort"
"sync" "sync"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
) )
const ( const (

View File

@ -3,7 +3,8 @@ package packfile
import ( import (
"bytes" "bytes"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
// See https://github.com/jelmer/dulwich/blob/master/dulwich/pack.py and // 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) 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() br, err := base.Reader()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer br.Close()
defer ioutil.CheckClose(br, &err)
tr, err := target.Reader() tr, err := target.Reader()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer tr.Close()
defer ioutil.CheckClose(tr, &err)
bb := bufPool.Get().(*bytes.Buffer) bb := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(bb) defer bufPool.Put(bb)

View File

@ -6,9 +6,10 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
// Encoder gets the data from the storage and write it into the writer in PACK // 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() { if o.WantWrite() {
// A cycle exists in this delta chain. This should only occur if a // A cycle exists in this delta chain. This should only occur if a
// selected object representation disappeared during writing // selected object representation disappeared during writing
@ -119,17 +120,22 @@ func (e *Encoder) entry(o *ObjectToPack) error {
} }
e.zw.Reset(e.w) e.zw.Reset(e.w)
defer ioutil.CheckClose(e.zw, &err)
or, err := o.Object.Reader() or, err := o.Object.Reader()
if err != nil { if err != nil {
return err return err
} }
defer ioutil.CheckClose(or, &err)
_, err = io.Copy(e.zw, or) _, err = io.Copy(e.zw, or)
if err != nil { if err != nil {
return err return err
} }
return e.zw.Close() return nil
} }
func (e *Encoder) writeBaseIfDelta(o *ObjectToPack) error { func (e *Encoder) writeBaseIfDelta(o *ObjectToPack) error {

View File

@ -4,9 +4,9 @@ import (
"io" "io"
billy "github.com/go-git/go-billy/v5" billy "github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache" "github.com/jesseduffield/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/format/idxfile" "github.com/jesseduffield/go-git/v5/plumbing/format/idxfile"
) )
// FSObject is an object from the packfile on the filesystem. // FSObject is an object from the packfile on the filesystem.

View File

@ -1,7 +1,7 @@
package packfile package packfile
import ( import (
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
) )
// ObjectToPack is a representation of an object that is going to be into a // ObjectToPack is a representation of an object that is going to be into a

View File

@ -6,10 +6,11 @@ import (
"os" "os"
billy "github.com/go-git/go-billy/v5" billy "github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache" "github.com/jesseduffield/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/format/idxfile" "github.com/jesseduffield/go-git/v5/plumbing/format/idxfile"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
var ( var (
@ -307,12 +308,14 @@ func (p *Packfile) getNextMemoryObject(h *ObjectHeader) (plumbing.EncodedObject,
return obj, nil return obj, nil
} }
func (p *Packfile) fillRegularObjectContent(obj plumbing.EncodedObject) error { func (p *Packfile) fillRegularObjectContent(obj plumbing.EncodedObject) (err error) {
w, err := obj.Writer() w, err := obj.Writer()
if err != nil { if err != nil {
return err return err
} }
defer ioutil.CheckClose(w, &err)
_, _, err = p.s.NextObject(w) _, _, err = p.s.NextObject(w)
p.cachePut(obj) p.cachePut(obj)

View File

@ -4,11 +4,12 @@ import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"io/ioutil" stdioutil "io/ioutil"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache" "github.com/jesseduffield/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
var ( var (
@ -283,7 +284,7 @@ func (p *Parser) resolveDeltas() error {
if !obj.IsDelta() && len(obj.Children) > 0 { if !obj.IsDelta() && len(obj.Children) > 0 {
for _, child := range obj.Children { 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 return err
} }
} }
@ -298,7 +299,7 @@ func (p *Parser) resolveDeltas() error {
return nil 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 if !o.ExternalRef { // skip cache check for placeholder parents
b, ok := p.cache.Get(o.Offset) b, ok := p.cache.Get(o.Offset)
if ok { 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 // 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. // storage, if there's one. External refs must enter here.
if p.storage != nil && !o.Type.IsDelta() { 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 { if err != nil {
return err return err
} }
o.Type = e.Type() o.Type = e.Type()
r, err := e.Reader() var r io.ReadCloser
r, err = e.Reader()
if err != nil { if err != nil {
return err return err
} }
defer ioutil.CheckClose(r, &err)
_, err = buf.ReadFrom(io.LimitReader(r, e.Size())) _, err = buf.ReadFrom(io.LimitReader(r, e.Size()))
return err return err
} }

View File

@ -5,7 +5,8 @@ import (
"errors" "errors"
"io" "io"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
// See https://github.com/git/git/blob/49fa3dc76179e04b0833542fa52d0f287a4955ac/delta.h // See https://github.com/git/git/blob/49fa3dc76179e04b0833542fa52d0f287a4955ac/delta.h
@ -16,17 +17,21 @@ import (
const deltaSizeMin = 4 const deltaSizeMin = 4
// ApplyDelta writes to target the result of applying the modification deltas in delta to base. // 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() r, err := base.Reader()
if err != nil { if err != nil {
return err return err
} }
defer ioutil.CheckClose(r, &err)
w, err := target.Writer() w, err := target.Writer()
if err != nil { if err != nil {
return err return err
} }
defer ioutil.CheckClose(w, &err)
buf := bufPool.Get().(*bytes.Buffer) buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf) defer bufPool.Put(buf)
buf.Reset() buf.Reset()
@ -44,7 +49,6 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) error {
return err return err
} }
target.SetSize(int64(dst.Len())) target.SetSize(int64(dst.Len()))
b := byteSlicePool.Get().([]byte) b := byteSlicePool.Get().([]byte)
@ -108,7 +112,7 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
invalidOffsetSize(offset, sz, srcSz) { invalidOffsetSize(offset, sz, srcSz) {
break break
} }
dst.Write(src[offset:offset+sz]) dst.Write(src[offset : offset+sz])
remainingTargetSz -= sz remainingTargetSz -= sz
} else if isCopyFromDelta(cmd) { } else if isCopyFromDelta(cmd) {
sz := uint(cmd) // cmd is the size itself sz := uint(cmd) // cmd is the size itself

View File

@ -11,9 +11,9 @@ import (
stdioutil "io/ioutil" stdioutil "io/ioutil"
"sync" "sync"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
"github.com/go-git/go-git/v5/utils/ioutil" "github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
var ( var (

View File

@ -71,3 +71,13 @@ type HashSlice []Hash
func (p HashSlice) Len() int { return len(p) } 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) 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] } 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
}

View File

@ -3,7 +3,6 @@ package plumbing
import ( import (
"bytes" "bytes"
"io" "io"
"io/ioutil"
) )
// MemoryObject on memory Object implementation // MemoryObject on memory Object implementation
@ -39,9 +38,11 @@ func (o *MemoryObject) Size() int64 { return o.sz }
// afterwards // afterwards
func (o *MemoryObject) SetSize(s int64) { o.sz = s } func (o *MemoryObject) SetSize(s int64) { o.sz = s }
// Reader returns a ObjectReader used to read the object's content. // Reader returns an io.ReadCloser used to read the object's content.
//
// For a MemoryObject, this reader is seekable.
func (o *MemoryObject) Reader() (io.ReadCloser, error) { func (o *MemoryObject) Reader() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewBuffer(o.cont)), nil return nopCloser{bytes.NewReader(o.cont)}, nil
} }
// Writer returns a ObjectWriter used to write the object's content. // Writer returns a ObjectWriter used to write the object's content.
@ -59,3 +60,13 @@ func (o *MemoryObject) Write(p []byte) (n int, err error) {
// Close releases any resources consumed by the object when it is acting as a // Close releases any resources consumed by the object when it is acting as a
// ObjectWriter. // ObjectWriter.
func (o *MemoryObject) Close() error { return nil } func (o *MemoryObject) Close() error { return nil }
// nopCloser exposes the extra methods of bytes.Reader while nopping Close().
//
// This allows clients to attempt seeking in a cached Blob's Reader.
type nopCloser struct {
*bytes.Reader
}
// Close does nothing.
func (nc nopCloser) Close() error { return nil }

View File

@ -3,9 +3,9 @@ package object
import ( import (
"io" "io"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/utils/ioutil" "github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
// Blob is used to store arbitrary data - it is generally a file. // Blob is used to store arbitrary data - it is generally a file.

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/go-git/go-git/v5/utils/merkletrie" "github.com/jesseduffield/go-git/v5/utils/merkletrie"
) )
// Change values represent a detected change between two git trees. For // Change values represent a detected change between two git trees. For
@ -18,7 +18,7 @@ type Change struct {
To ChangeEntry To ChangeEntry
} }
var empty = ChangeEntry{} var empty ChangeEntry
// Action returns the kind of action represented by the change, an // Action returns the kind of action represented by the change, an
// insertion, a deletion or a modification. // insertion, a deletion or a modification.
@ -27,9 +27,11 @@ func (c *Change) Action() (merkletrie.Action, error) {
return merkletrie.Action(0), return merkletrie.Action(0),
fmt.Errorf("malformed change: empty from and to") fmt.Errorf("malformed change: empty from and to")
} }
if c.From == empty { if c.From == empty {
return merkletrie.Insert, nil return merkletrie.Insert, nil
} }
if c.To == empty { if c.To == empty {
return merkletrie.Delete, nil return merkletrie.Delete, nil
} }
@ -73,7 +75,7 @@ func (c *Change) Files() (from, to *File, err error) {
func (c *Change) String() string { func (c *Change) String() string {
action, err := c.Action() action, err := c.Action()
if err != nil { if err != nil {
return fmt.Sprintf("malformed change") return "malformed change"
} }
return fmt.Sprintf("<Action: %s, Path: %s>", action, c.name()) return fmt.Sprintf("<Action: %s, Path: %s>", action, c.name())

View File

@ -4,8 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/go-git/go-git/v5/utils/merkletrie" "github.com/jesseduffield/go-git/v5/utils/merkletrie"
"github.com/go-git/go-git/v5/utils/merkletrie/noder" "github.com/jesseduffield/go-git/v5/utils/merkletrie/noder"
) )
// The following functions transform changes types form the merkletrie // The following functions transform changes types form the merkletrie

View File

@ -11,9 +11,9 @@ import (
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/utils/ioutil" "github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
const ( const (
@ -78,21 +78,30 @@ func (c *Commit) Tree() (*Tree, error) {
// PatchContext returns the Patch between the actual commit and the provided one. // 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. // 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) { func (c *Commit) PatchContext(ctx context.Context, to *Commit) (*Patch, error) {
fromTree, err := c.Tree() fromTree, err := c.Tree()
if err != nil { if err != nil {
return nil, err return nil, err
} }
toTree, err := to.Tree() var toTree *Tree
if err != nil { if to != nil {
return nil, err toTree, err = to.Tree()
if err != nil {
return nil, err
}
} }
return fromTree.PatchContext(ctx, toTree) return fromTree.PatchContext(ctx, toTree)
} }
// Patch returns the Patch between the actual commit and the provided one. // 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) { func (c *Commit) Patch(to *Commit) (*Patch, error) {
return c.PatchContext(context.Background(), to) return c.PatchContext(context.Background(), to)
} }
@ -234,16 +243,16 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
} }
// Encode transforms a Commit into a plumbing.EncodedObject. // Encode transforms a Commit into a plumbing.EncodedObject.
func (b *Commit) Encode(o plumbing.EncodedObject) error { func (c *Commit) Encode(o plumbing.EncodedObject) error {
return b.encode(o, true) return c.encode(o, true)
} }
// EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature). // EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature).
func (b *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error { func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
return b.encode(o, false) return c.encode(o, false)
} }
func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) { func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
o.SetType(plumbing.CommitObject) o.SetType(plumbing.CommitObject)
w, err := o.Writer() w, err := o.Writer()
if err != nil { if err != nil {
@ -252,11 +261,11 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
defer ioutil.CheckClose(w, &err) defer ioutil.CheckClose(w, &err)
if _, err = fmt.Fprintf(w, "tree %s\n", b.TreeHash.String()); err != nil { if _, err = fmt.Fprintf(w, "tree %s\n", c.TreeHash.String()); err != nil {
return err return err
} }
for _, parent := range b.ParentHashes { for _, parent := range c.ParentHashes {
if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil { if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil {
return err return err
} }
@ -266,7 +275,7 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err return err
} }
if err = b.Author.Encode(w); err != nil { if err = c.Author.Encode(w); err != nil {
return err return err
} }
@ -274,11 +283,11 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err return err
} }
if err = b.Committer.Encode(w); err != nil { if err = c.Committer.Encode(w); err != nil {
return err return err
} }
if b.PGPSignature != "" && includeSig { if c.PGPSignature != "" && includeSig {
if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil { if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil {
return err return err
} }
@ -287,14 +296,14 @@ func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
// newline. Use join for this so it's clear that a newline should not be // newline. Use join for this so it's clear that a newline should not be
// added after this section, as it will be added when the message is // added after this section, as it will be added when the message is
// printed. // printed.
signature := strings.TrimSuffix(b.PGPSignature, "\n") signature := strings.TrimSuffix(c.PGPSignature, "\n")
lines := strings.Split(signature, "\n") lines := strings.Split(signature, "\n")
if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil { if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil {
return err return err
} }
} }
if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil { if _, err = fmt.Fprintf(w, "\n\n%s", c.Message); err != nil {
return err return err
} }

View File

@ -4,9 +4,9 @@ import (
"container/list" "container/list"
"io" "io"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/storage" "github.com/jesseduffield/go-git/v5/storage"
) )
type commitPreIterator struct { type commitPreIterator struct {

View File

@ -3,8 +3,8 @@ package object
import ( import (
"io" "io"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
) )
type bfsCommitIterator struct { type bfsCommitIterator struct {

View File

@ -3,8 +3,8 @@ package object
import ( import (
"io" "io"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
) )
// NewFilterCommitIter returns a CommitIter that walks the commit history, // NewFilterCommitIter returns a CommitIter that walks the commit history,
@ -173,4 +173,3 @@ func (w *filterCommitIter) addToQueue(
return nil return nil
} }

View File

@ -5,8 +5,8 @@ import (
"github.com/emirpasic/gods/trees/binaryheap" "github.com/emirpasic/gods/trees/binaryheap"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
) )
type commitIteratorByCTime struct { type commitIteratorByCTime struct {

View File

@ -4,7 +4,7 @@ import (
"io" "io"
"time" "time"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
) )
type commitLimitIter struct { type commitLimitIter struct {

View File

@ -3,9 +3,8 @@ package object
import ( import (
"io" "io"
"github.com/go-git/go-git/v5/plumbing" "github.com/jesseduffield/go-git/v5/plumbing"
"github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/plumbing/storer"
) )
type commitPathIter struct { type commitPathIter struct {
@ -29,7 +28,7 @@ func NewCommitPathIterFromIter(pathFilter func(string) bool, commitIter CommitIt
return iterator return iterator
} }
// this function is kept for compatibilty, can be replaced with NewCommitPathIterFromIter // NewCommitFileIterFromIter is kept for compatibility, can be replaced with NewCommitPathIterFromIter
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter { func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter {
return NewCommitPathIterFromIter( return NewCommitPathIterFromIter(
func(path string) bool { func(path string) bool {

View File

@ -0,0 +1,98 @@
package object
import (
"bytes"
"context"
"github.com/jesseduffield/go-git/v5/utils/merkletrie"
"github.com/jesseduffield/go-git/v5/utils/merkletrie/noder"
)
// 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)
}
// DiffTreeContext compares the content and mode of the blobs found via two
// tree objects. Provided context must be non-nil.
// 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)
hashEqual := func(a, b noder.Hasher) bool {
return bytes.Equal(a.Hash(), b.Hash())
}
merkletrieChanges, err := merkletrie.DiffTreeContext(ctx, from, to, hashEqual)
if err != nil {
if err == merkletrie.ErrCanceled {
return nil, ErrCanceled
}
return nil, err
}
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
}

View File

@ -5,10 +5,10 @@ import (
"io" "io"
"strings" "strings"
"github.com/go-git/go-git/v5/plumbing/filemode" "github.com/jesseduffield/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/utils/binary" "github.com/jesseduffield/go-git/v5/utils/binary"
"github.com/go-git/go-git/v5/utils/ioutil" "github.com/jesseduffield/go-git/v5/utils/ioutil"
) )
// File represents git file objects. // File represents git file objects.

Some files were not shown because too many files have changed in this diff Show More