1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-03-19 21:28:28 +02:00

fix reflog failing to properly refresh

This commit is contained in:
Jesse Duffield 2022-01-26 10:34:56 +11:00
parent f4ddf2f0d4
commit ce3bcfe37c
16 changed files with 1196 additions and 3 deletions

1
go.mod
View File

@ -36,6 +36,7 @@ require (
github.com/onsi/gomega v1.7.1 // indirect
github.com/pmezard/go-difflib v1.0.0
github.com/sahilm/fuzzy v0.1.0
github.com/sanity-io/litter v1.5.2
github.com/sirupsen/logrus v1.4.2
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stretchr/testify v1.7.0

5
go.sum
View File

@ -18,6 +18,7 @@ github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -126,6 +127,7 @@ github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -133,6 +135,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw=
github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
@ -142,6 +146,7 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@ -41,7 +41,7 @@ d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield||65f910ebd852
func TestGetCommits(t *testing.T) {
type scenario struct {
testName string
runner oscommands.ICmdObjRunner
runner *oscommands.FakeCmdObjRunner
expectedCommits []*models.Commit
expectedError error
rebaseMode enums.RebaseMode
@ -208,6 +208,8 @@ func TestGetCommits(t *testing.T) {
assert.Equal(t, scenario.expectedCommits, commits)
assert.Equal(t, scenario.expectedError, err)
scenario.runner.CheckForMissingCalls()
})
}
}

View File

@ -32,7 +32,7 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
filterPathArg = fmt.Sprintf(" --follow -- %s", self.cmd.Quote(filterPath))
}
cmdObj := self.cmd.New(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg)).DontLog()
cmdObj := self.cmd.New(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs"%s`, filterPathArg)).DontLog()
onlyObtainedNewReflogCommits := false
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) {
fields := strings.SplitN(line, " ", 3)
@ -49,7 +49,11 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
Status: "reflog",
}
if lastReflogCommit != nil && commit.Sha == lastReflogCommit.Sha && commit.UnixTimestamp == lastReflogCommit.UnixTimestamp {
// note that the unix timestamp here is the timestamp of the COMMIT, not the reflog entry itself,
// so two consequetive reflog entries may have both the same SHA and therefore same timestamp.
// We use the reflog message to disambiguate, and fingers crossed that we never see the same of those
// twice in a row. Reason being that it would mean we'd be erroneously exiting early.
if lastReflogCommit != nil && commit.Sha == lastReflogCommit.Sha && commit.UnixTimestamp == lastReflogCommit.UnixTimestamp && commit.Name == lastReflogCommit.Name {
onlyObtainedNewReflogCommits = true
// after this point we already have these reflogs loaded so we'll simply return the new ones
return true, nil

View File

@ -0,0 +1,159 @@
package loaders
import (
"errors"
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sanity-io/litter"
"github.com/stretchr/testify/assert"
)
const reflogOutput = `c3c4b66b64c97ffeecde 1643150483 checkout: moving from A to B
c3c4b66b64c97ffeecde 1643150483 checkout: moving from B to A
c3c4b66b64c97ffeecde 1643150483 checkout: moving from A to B
c3c4b66b64c97ffeecde 1643150483 checkout: moving from master to A
f4ddf2f0d4be4ccc7efa 1643149435 checkout: moving from A to master
`
func TestGetReflogCommits(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
lastReflogCommit *models.Commit
filterPath string
expectedCommits []*models.Commit
expectedOnlyObtainedNew bool
expectedError error
}
scenarios := []scenario{
{
testName: "no reflog entries",
runner: oscommands.NewFakeRunner(t).
Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, "", nil),
lastReflogCommit: nil,
expectedCommits: []*models.Commit{},
expectedOnlyObtainedNew: false,
expectedError: nil,
},
{
testName: "some reflog entries",
runner: oscommands.NewFakeRunner(t).
Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, reflogOutput, nil),
lastReflogCommit: nil,
expectedCommits: []*models.Commit{
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: "reflog",
UnixTimestamp: 1643150483,
},
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A",
Status: "reflog",
UnixTimestamp: 1643150483,
},
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: "reflog",
UnixTimestamp: 1643150483,
},
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from master to A",
Status: "reflog",
UnixTimestamp: 1643150483,
},
{
Sha: "f4ddf2f0d4be4ccc7efa",
Name: "checkout: moving from A to master",
Status: "reflog",
UnixTimestamp: 1643149435,
},
},
expectedOnlyObtainedNew: false,
expectedError: nil,
},
{
testName: "some reflog entries where last commit is given",
runner: oscommands.NewFakeRunner(t).
Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, reflogOutput, nil),
lastReflogCommit: &models.Commit{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A",
Status: "reflog",
UnixTimestamp: 1643150483,
},
expectedCommits: []*models.Commit{
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: "reflog",
UnixTimestamp: 1643150483,
},
},
expectedOnlyObtainedNew: true,
expectedError: nil,
},
{
testName: "when passing filterPath",
runner: oscommands.NewFakeRunner(t).
Expect(`git log -g --abbrev=20 --format="%h %ct %gs" --follow -- "path"`, reflogOutput, nil),
lastReflogCommit: &models.Commit{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A",
Status: "reflog",
UnixTimestamp: 1643150483,
},
filterPath: "path",
expectedCommits: []*models.Commit{
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: "reflog",
UnixTimestamp: 1643150483,
},
},
expectedOnlyObtainedNew: true,
expectedError: nil,
},
{
testName: "when command returns error",
runner: oscommands.NewFakeRunner(t).
Expect(`git log -g --abbrev=20 --format="%h %ct %gs"`, "", errors.New("haha")),
lastReflogCommit: nil,
filterPath: "",
expectedCommits: nil,
expectedOnlyObtainedNew: false,
expectedError: errors.New("haha"),
},
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) {
builder := &ReflogCommitLoader{
Common: utils.NewDummyCommon(),
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
}
commits, onlyObtainednew, err := builder.GetReflogCommits(scenario.lastReflogCommit, scenario.filterPath)
assert.Equal(t, scenario.expectedOnlyObtainedNew, onlyObtainednew)
assert.Equal(t, scenario.expectedError, err)
t.Logf("actual commits: \n%s", litter.Sdump(commits))
assert.Equal(t, scenario.expectedCommits, commits)
scenario.runner.CheckForMissingCalls()
})
}
}

1
vendor/github.com/sanity-io/litter/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

7
vendor/github.com/sanity-io/litter/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,7 @@
# 1.1.0 (2017-11-1)
A slight breaking change. The dump-method of the `Dumper` interface has changed from `Dump` to `LitterDump` to mitigate potential collisions.
# 1.0.0 (2017-10-29)
Tagged 1.0.0.

21
vendor/github.com/sanity-io/litter/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2017 Sanity.io.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

202
vendor/github.com/sanity-io/litter/README.md generated vendored Normal file
View File

@ -0,0 +1,202 @@
[![!Build Status](https://travis-ci.org/sanity-io/litter.svg?branch=master)](https://travis-ci.org/sanity-io/litter)
# Litter
**Litter is a pretty printer library for Go data structures to aid in debugging and testing.**
---
Litter is provided by
<a href="https://www.sanity.io/?utm_source=GitHub&utm_campaign=litter" rel="nofollow" target="_blank">
<img src="https://www.sanity.io/static/images/logo_red.svg?v=2" width="300"><br />
Sanity: The Headless CMS Construction Kit
</a>
---
Litter named for the fact that it outputs *literals*, which you *litter* your output with. As a side benefit, all Litter output is syntactically correct Go. You can use Litter to emit data during debug, and it's also really nice for "snapshot data" in unit tests, since it produces consistent, sorted output. Litter was inspired by [Spew](https://github.com/davecgh/go-spew), but focuses on terseness and readability.
### Basic example
This:
```go
type Person struct {
Name string
Age int
Parent *Person
}
litter.Dump(Person{
Name: "Bob",
Age: 20,
Parent: &Person{
Name: "Jane",
Age: 50,
},
})
```
will output:
```
Person{
Name: "Bob",
Age: 20,
Parent: &Person{
Name: "Jane",
Age: 50,
},
}
```
### Use in tests
Litter is a great alternative to JSON or YAML for providing "snapshots" or example data. For example:
```go
func TestSearch(t *testing.T) {
result := DoSearch()
actual := litterOpts.Sdump(result)
expected, err := ioutil.ReadFile("testdata.txt")
if err != nil {
// First run, write test data since it doesn't exist
if !os.IsNotExist(err) {
t.Error(err)
}
ioutil.Write("testdata.txt", actual, 0644)
actual = expected
}
if expected != actual {
t.Errorf("Expected %s, got %s", expected, actual)
}
}
```
The first run will use Litter to write the data to `testdata.txt`. On subsequent runs, the test will compare the data. Since Litter always provides a consistent view of a value, you can compare the strings directly.
### Circular references
Litter detects circular references or aliasing, and will replace additional references to the same object with aliases. For example:
```go
type Circular struct {
Self *Circular
}
selfref := Circular{}
selfref.Self = &selfref
litter.Dump(selfref)
```
will output:
```
Circular { // p0
Self: p0,
}
```
## Installation
```bash
$ go get -u github.com/sanity-io/litter
```
## Quick start
Add this import line to the file you're working in:
```go
import "github.com/sanity-io/litter"
```
To dump a variable with full newlines, indentation, type, and aliasing information, use `Dump` or `Sdump`:
```go
litter.Dump(myVar1)
str := litter.Sdump(myVar1)
```
### `litter.Dump(value, ...)`
Dumps the data structure to STDOUT.
### `litter.Sdump(value, ...)`
Returns the dump as a string
## Configuration
You can configure litter globally by modifying the default `litter.Config`
```go
// Strip all package names from types
litter.Config.StripPackageNames = true
// Hide private struct fields from dumped structs
litter.Config.HidePrivateFields = true
// Hide fields matched with given regexp if it is not nil. It is set up to hide fields generate with protoc-gen-go
litter.Config.FieldExclusions = regexp.MustCompile(`^(XXX_.*)$`)
// Sets a "home" package. The package name will be stripped from all its types
litter.Config.HomePackage = "mypackage"
// Sets separator used when multiple arguments are passed to Dump() or Sdump().
litter.Config.Separator = "\n"
// Use compact output: strip newlines and other unnecessary whitespace
litter.Config.Compact = true
// Prevents duplicate pointers from being replaced by placeholder variable names (except in necessary, in the case
// of circular references)
litter.Config.DisablePointerReplacement = true
```
### `litter.Options`
Allows you to configure a local configuration of litter to allow for proper compartmentalization of state at the expense of some comfort:
``` go
sq := litter.Options {
HidePrivateFields: true,
HomePackage: "thispack",
Separator: " ",
}
sq.Dump("dumped", "with", "local", "settings")
```
## Custom dumpers
Implement the interface Dumper on your types to take control of how your type is dumped.
``` go
type Dumper interface {
LitterDump(w io.Writer)
}
```
Just write your custom dump to the provided stream, using multiple lines divided by `"\n"` if you need. Litter
might indent your output according to context, and optionally decorate your first line with a pointer comment
where appropriate.
A couple of examples from the test suite:
``` go
type CustomMultiLineDumper struct {}
func (cmld *CustomMultiLineDumper) LitterDump(w io.Writer) {
w.Write([]byte("{\n multi\n line\n}"))
}
type CustomSingleLineDumper int
func (csld CustomSingleLineDumper) LitterDump(w io.Writer) {
w.Write([]byte("<custom>"))
}
````

528
vendor/github.com/sanity-io/litter/dump.go generated vendored Normal file
View File

@ -0,0 +1,528 @@
package litter
import (
"bytes"
"fmt"
"io"
"os"
"reflect"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
)
var (
packageNameStripperRegexp = regexp.MustCompile(`\b[a-zA-Z_]+[a-zA-Z_0-9]+\.`)
compactTypeRegexp = regexp.MustCompile(`\s*([,;{}()])\s*`)
)
// Dumper is the interface for implementing custom dumper for your types.
type Dumper interface {
LitterDump(w io.Writer)
}
// Options represents configuration options for litter
type Options struct {
Compact bool
StripPackageNames bool
HidePrivateFields bool
HideZeroValues bool
FieldExclusions *regexp.Regexp
FieldFilter func(reflect.StructField, reflect.Value) bool
HomePackage string
Separator string
StrictGo bool
DumpFunc func(reflect.Value, io.Writer) bool
// DisablePointerReplacement, if true, disables the replacing of pointer data with variable names
// when it's safe. This is useful for diffing two structures, where pointer variables would cause
// false changes. However, circular graphs are still detected and elided to avoid infinite output.
DisablePointerReplacement bool
}
// Config is the default config used when calling Dump
var Config = Options{
StripPackageNames: false,
HidePrivateFields: true,
FieldExclusions: regexp.MustCompile(`^(XXX_.*)$`), // XXX_ is a prefix of fields generated by protoc-gen-go
Separator: " ",
}
type dumpState struct {
w io.Writer
depth int
config *Options
pointers ptrmap
visitedPointers ptrmap
parentPointers ptrmap
currentPointer *ptrinfo
homePackageRegexp *regexp.Regexp
}
func (s *dumpState) write(b []byte) {
if _, err := s.w.Write(b); err != nil {
panic(err)
}
}
func (s *dumpState) writeString(str string) {
s.write([]byte(str))
}
func (s *dumpState) indent() {
if !s.config.Compact {
s.write(bytes.Repeat([]byte(" "), s.depth))
}
}
func (s *dumpState) newlineWithPointerNameComment() {
if ptr := s.currentPointer; ptr != nil {
if s.config.Compact {
s.write([]byte(fmt.Sprintf("/*%s*/", ptr.label())))
} else {
s.write([]byte(fmt.Sprintf(" // %s\n", ptr.label())))
}
s.currentPointer = nil
return
}
if !s.config.Compact {
s.write([]byte("\n"))
}
}
func (s *dumpState) dumpType(v reflect.Value) {
typeName := v.Type().String()
if s.config.StripPackageNames {
typeName = packageNameStripperRegexp.ReplaceAllLiteralString(typeName, "")
} else if s.homePackageRegexp != nil {
typeName = s.homePackageRegexp.ReplaceAllLiteralString(typeName, "")
}
if s.config.Compact {
typeName = compactTypeRegexp.ReplaceAllString(typeName, "$1")
}
s.write([]byte(typeName))
}
func (s *dumpState) dumpSlice(v reflect.Value) {
s.dumpType(v)
numEntries := v.Len()
if numEntries == 0 {
s.write([]byte("{}"))
return
}
s.write([]byte("{"))
s.newlineWithPointerNameComment()
s.depth++
for i := 0; i < numEntries; i++ {
s.indent()
s.dumpVal(v.Index(i))
if !s.config.Compact || i < numEntries-1 {
s.write([]byte(","))
}
s.newlineWithPointerNameComment()
}
s.depth--
s.indent()
s.write([]byte("}"))
}
func (s *dumpState) dumpStruct(v reflect.Value) {
dumpPreamble := func() {
s.dumpType(v)
s.write([]byte("{"))
s.newlineWithPointerNameComment()
s.depth++
}
preambleDumped := false
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
vtf := vt.Field(i)
if s.config.HidePrivateFields && vtf.PkgPath != "" || s.config.FieldExclusions != nil && s.config.FieldExclusions.MatchString(vtf.Name) {
continue
}
if s.config.FieldFilter != nil && !s.config.FieldFilter(vtf, v.Field(i)) {
continue
}
if s.config.HideZeroValues && isZeroValue(v.Field(i)) {
continue
}
if !preambleDumped {
dumpPreamble()
preambleDumped = true
}
s.indent()
s.write([]byte(vtf.Name))
if s.config.Compact {
s.write([]byte(":"))
} else {
s.write([]byte(": "))
}
s.dumpVal(v.Field(i))
if !s.config.Compact || i < numFields-1 {
s.write([]byte(","))
}
s.newlineWithPointerNameComment()
}
if preambleDumped {
s.depth--
s.indent()
s.write([]byte("}"))
} else {
// There were no fields dumped
s.dumpType(v)
s.write([]byte("{}"))
}
}
func (s *dumpState) dumpMap(v reflect.Value) {
if v.IsNil() {
s.dumpType(v)
s.writeString("(nil)")
return
}
s.dumpType(v)
keys := v.MapKeys()
if len(keys) == 0 {
s.write([]byte("{}"))
return
}
s.write([]byte("{"))
s.newlineWithPointerNameComment()
s.depth++
sort.Sort(mapKeySorter{
keys: keys,
options: s.config,
})
numKeys := len(keys)
for i, key := range keys {
s.indent()
s.dumpVal(key)
if s.config.Compact {
s.write([]byte(":"))
} else {
s.write([]byte(": "))
}
s.dumpVal(v.MapIndex(key))
if !s.config.Compact || i < numKeys-1 {
s.write([]byte(","))
}
s.newlineWithPointerNameComment()
}
s.depth--
s.indent()
s.write([]byte("}"))
}
func (s *dumpState) dumpFunc(v reflect.Value) {
parts := strings.Split(runtime.FuncForPC(v.Pointer()).Name(), "/")
name := parts[len(parts)-1]
// Anonymous function
if strings.Count(name, ".") > 1 {
s.dumpType(v)
} else {
if s.config.StripPackageNames {
name = packageNameStripperRegexp.ReplaceAllLiteralString(name, "")
} else if s.homePackageRegexp != nil {
name = s.homePackageRegexp.ReplaceAllLiteralString(name, "")
}
if s.config.Compact {
name = compactTypeRegexp.ReplaceAllString(name, "$1")
}
s.write([]byte(name))
}
}
func (s *dumpState) dumpChan(v reflect.Value) {
vType := v.Type()
res := []byte(vType.String())
s.write(res)
}
func (s *dumpState) dumpCustom(v reflect.Value, buf *bytes.Buffer) {
// Dump the type
s.dumpType(v)
if s.config.Compact {
s.write(buf.Bytes())
return
}
// Now output the dump taking care to apply the current indentation-level
// and pointer name comments.
var err error
firstLine := true
for err == nil {
var lineBytes []byte
lineBytes, err = buf.ReadBytes('\n')
line := strings.TrimRight(string(lineBytes), " \n")
if err != nil && err != io.EOF {
break
}
// Do not indent first line
if firstLine {
firstLine = false
} else {
s.indent()
}
s.write([]byte(line))
// At EOF we're done
if err == io.EOF {
return
}
s.newlineWithPointerNameComment()
}
panic(err)
}
func (s *dumpState) dump(value interface{}) {
if value == nil {
printNil(s.w)
return
}
v := reflect.ValueOf(value)
s.dumpVal(v)
}
func (s *dumpState) descendIntoPossiblePointer(value reflect.Value, f func()) {
canonicalize := true
if isPointerValue(value) {
// If elision disabled, and this is not a circular reference, don't canonicalize
if s.config.DisablePointerReplacement && s.parentPointers.add(value) {
canonicalize = false
}
// Add to stack of pointers we're recursively descending into
s.parentPointers.add(value)
defer s.parentPointers.remove(value)
}
if !canonicalize {
ptr, _ := s.pointerFor(value)
s.currentPointer = ptr
f()
return
}
ptr, firstVisit := s.pointerFor(value)
if ptr == nil {
f()
return
}
if firstVisit {
s.currentPointer = ptr
f()
return
}
s.write([]byte(ptr.label()))
}
func (s *dumpState) dumpVal(value reflect.Value) {
if value.Kind() == reflect.Ptr && value.IsNil() {
s.write([]byte("nil"))
return
}
v := deInterface(value)
kind := v.Kind()
// Try to handle with dump func
if s.config.DumpFunc != nil {
buf := new(bytes.Buffer)
if s.config.DumpFunc(v, buf) {
s.dumpCustom(v, buf)
return
}
}
// Handle custom dumpers
dumperType := reflect.TypeOf((*Dumper)(nil)).Elem()
if v.Type().Implements(dumperType) {
s.descendIntoPossiblePointer(v, func() {
// Run the custom dumper buffering the output
buf := new(bytes.Buffer)
dumpFunc := v.MethodByName("LitterDump")
dumpFunc.Call([]reflect.Value{reflect.ValueOf(buf)})
s.dumpCustom(v, buf)
})
return
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
s.write([]byte("<invalid>"))
case reflect.Bool:
printBool(s.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(s.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(s.w, v.Uint(), 10)
case reflect.Float32:
printFloat(s.w, v.Float(), 32)
case reflect.Float64:
printFloat(s.w, v.Float(), 64)
case reflect.Complex64:
printComplex(s.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(s.w, v.Complex(), 64)
case reflect.String:
s.write([]byte(strconv.Quote(v.String())))
case reflect.Slice:
if v.IsNil() {
printNil(s.w)
break
}
fallthrough
case reflect.Array:
s.descendIntoPossiblePointer(v, func() {
s.dumpSlice(v)
})
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
printNil(s.w)
}
case reflect.Ptr:
s.descendIntoPossiblePointer(v, func() {
if s.config.StrictGo {
s.writeString(fmt.Sprintf("(func(v %s) *%s { return &v })(", v.Elem().Type(), v.Elem().Type()))
s.dumpVal(v.Elem())
s.writeString(")")
} else {
s.writeString("&")
s.dumpVal(v.Elem())
}
})
case reflect.Map:
s.descendIntoPossiblePointer(v, func() {
s.dumpMap(v)
})
case reflect.Struct:
s.dumpStruct(v)
case reflect.Func:
s.dumpFunc(v)
case reflect.Chan:
s.dumpChan(v)
default:
if v.CanInterface() {
s.writeString(fmt.Sprintf("%v", v.Interface()))
} else {
s.writeString(fmt.Sprintf("%v", v.String()))
}
}
}
// registers that the value has been visited and checks to see if it is one of the
// pointers we will see multiple times. If it is, it returns a temporary name for this
// pointer. It also returns a boolean value indicating whether this is the first time
// this name is returned so the caller can decide whether the contents of the pointer
// has been dumped before or not.
func (s *dumpState) pointerFor(v reflect.Value) (*ptrinfo, bool) {
if isPointerValue(v) {
if info, ok := s.pointers.get(v); ok {
firstVisit := s.visitedPointers.add(v)
return info, firstVisit
}
}
return nil, false
}
// prepares a new state object for dumping the provided value
func newDumpState(value interface{}, options *Options, writer io.Writer) *dumpState {
result := &dumpState{
config: options,
pointers: mapReusedPointers(reflect.ValueOf(value)),
w: writer,
}
if options.HomePackage != "" {
result.homePackageRegexp = regexp.MustCompile(fmt.Sprintf("\\b%s\\.", options.HomePackage))
}
return result
}
// Dump a value to stdout
func Dump(value ...interface{}) {
(&Config).Dump(value...)
}
// Sdump dumps a value to a string
func Sdump(value ...interface{}) string {
return (&Config).Sdump(value...)
}
// Dump a value to stdout according to the options
func (o Options) Dump(values ...interface{}) {
for i, value := range values {
state := newDumpState(value, &o, os.Stdout)
if i > 0 {
state.write([]byte(o.Separator))
}
state.dump(value)
}
_, _ = os.Stdout.Write([]byte("\n"))
}
// Sdump dumps a value to a string according to the options
func (o Options) Sdump(values ...interface{}) string {
buf := new(bytes.Buffer)
for i, value := range values {
if i > 0 {
_, _ = buf.Write([]byte(o.Separator))
}
state := newDumpState(value, &o, buf)
state.dump(value)
}
return buf.String()
}
type mapKeySorter struct {
keys []reflect.Value
options *Options
}
func (s mapKeySorter) Len() int {
return len(s.keys)
}
func (s mapKeySorter) Swap(i, j int) {
s.keys[i], s.keys[j] = s.keys[j], s.keys[i]
}
func (s mapKeySorter) Less(i, j int) bool {
ibuf := new(bytes.Buffer)
jbuf := new(bytes.Buffer)
newDumpState(s.keys[i], s.options, ibuf).dumpVal(s.keys[i])
newDumpState(s.keys[j], s.options, jbuf).dumpVal(s.keys[j])
return ibuf.String() < jbuf.String()
}

9
vendor/github.com/sanity-io/litter/go.mod generated vendored Normal file
View File

@ -0,0 +1,9 @@
module github.com/sanity-io/litter
go 1.14
require (
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b // indirect
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312
)

6
vendor/github.com/sanity-io/litter/go.sum generated vendored Normal file
View File

@ -0,0 +1,6 @@
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b h1:XxMZvQZtTXpWMNWK82vdjCLCe7uGMFXdTsJH0v3Hkvw=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 h1:UsFdQ3ZmlzS0BqZYGxvYaXvFGUbCmPGy8DM7qWJJiIQ=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

167
vendor/github.com/sanity-io/litter/pointers.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
package litter
import (
"fmt"
"reflect"
"sort"
)
// mapReusedPointers takes a structure, and recursively maps all pointers mentioned in the tree,
// detecting circular references, and providing a list of all pointers that was referenced at
// least twice by the provided structure.
func mapReusedPointers(v reflect.Value) ptrmap {
pm := &pointerVisitor{}
pm.consider(v)
return pm.reused
}
// A map of pointers.
type ptrinfo struct {
id int
parent *ptrmap
}
func (p *ptrinfo) label() string {
if p.id == -1 {
p.id = p.parent.count
p.parent.count++
}
return fmt.Sprintf("p%d", p.id)
}
type ptrkey struct {
p uintptr
t reflect.Type
}
func ptrkeyFor(v reflect.Value) (k ptrkey) {
k.p = v.Pointer()
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.IsValid() {
k.t = v.Type()
}
return
}
type ptrmap struct {
m map[ptrkey]*ptrinfo
count int
}
// Returns true if contains a pointer.
func (pm *ptrmap) contains(v reflect.Value) bool {
if pm.m != nil {
_, ok := pm.m[ptrkeyFor(v)]
return ok
}
return false
}
// Gets a pointer.
func (pm *ptrmap) get(v reflect.Value) (*ptrinfo, bool) {
if pm.m != nil {
p, ok := pm.m[ptrkeyFor(v)]
return p, ok
}
return nil, false
}
// Removes a pointer.
func (pm *ptrmap) remove(v reflect.Value) {
if pm.m != nil {
delete(pm.m, ptrkeyFor(v))
}
}
// Adds a pointer.
func (pm *ptrmap) add(p reflect.Value) bool {
if pm.contains(p) {
return false
}
pm.put(p)
return true
}
// Adds a pointer (slow path).
func (pm *ptrmap) put(v reflect.Value) {
if pm.m == nil {
pm.m = make(map[ptrkey]*ptrinfo, 31)
}
key := ptrkeyFor(v)
if _, ok := pm.m[key]; !ok {
pm.m[key] = &ptrinfo{id: -1, parent: pm}
}
}
type pointerVisitor struct {
pointers ptrmap
reused ptrmap
}
// Recursively consider v and each of its children, updating the map according to the
// semantics of MapReusedPointers
func (pv *pointerVisitor) consider(v reflect.Value) {
if v.Kind() == reflect.Invalid {
return
}
if isPointerValue(v) { // pointer is 0 for unexported fields
if pv.tryAddPointer(v) {
// No use descending inside this value, since it have been seen before and all its descendants
// have been considered
return
}
}
// Now descend into any children of this value
switch v.Kind() {
case reflect.Slice, reflect.Array:
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
pv.consider(v.Index(i))
}
case reflect.Interface:
pv.consider(v.Elem())
case reflect.Ptr:
pv.consider(v.Elem())
case reflect.Map:
keys := v.MapKeys()
sort.Sort(mapKeySorter{
keys: keys,
options: &Config,
})
for _, key := range keys {
pv.consider(v.MapIndex(key))
}
case reflect.Struct:
numFields := v.NumField()
for i := 0; i < numFields; i++ {
pv.consider(v.Field(i))
}
}
}
// addPointer to the pointerMap, update reusedPointers. Returns true if pointer was reused
func (pv *pointerVisitor) tryAddPointer(v reflect.Value) bool {
// Is this allready known to be reused?
if pv.reused.contains(v) {
return true
}
// Have we seen it once before?
if pv.pointers.contains(v) {
// Add it to the register of pointers we have seen more than once
pv.reused.add(v)
return true
}
// This pointer was new to us
pv.pointers.add(v)
return false
}

50
vendor/github.com/sanity-io/litter/print.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
package litter
import (
"io"
"math"
"strconv"
)
func printBool(w io.Writer, value bool) {
if value {
w.Write([]byte("true"))
return
}
w.Write([]byte("false"))
}
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
func printFloat(w io.Writer, val float64, precision int) {
if math.Trunc(val) == val {
// Ensure that floats like 1.0 are always printed with a decimal point
w.Write([]byte(strconv.FormatFloat(val, 'f', 1, precision)))
} else {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
}
func printComplex(w io.Writer, c complex128, floatPrecision int) {
w.Write([]byte("complex"))
printInt(w, int64(floatPrecision*2), 10)
r := real(c)
w.Write([]byte("("))
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write([]byte("+"))
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write([]byte("i)"))
}
func printNil(w io.Writer) {
w.Write([]byte("nil"))
}

28
vendor/github.com/sanity-io/litter/util.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package litter
import (
"reflect"
)
// deInterface returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func deInterface(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
func isPointerValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
return true
}
return false
}
func isZeroValue(v reflect.Value) bool {
return (isPointerValue(v) && v.IsNil()) ||
(v.IsValid() && v.CanInterface() && reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()))
}

3
vendor/modules.txt vendored
View File

@ -214,6 +214,9 @@ github.com/rivo/uniseg
# github.com/sahilm/fuzzy v0.1.0
## explicit
github.com/sahilm/fuzzy
# github.com/sanity-io/litter v1.5.2
## explicit
github.com/sanity-io/litter
# github.com/sergi/go-diff v1.1.0
github.com/sergi/go-diff/diffmatchpatch
# github.com/sirupsen/logrus v1.4.2