1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-30 03:23:08 +03:00

Bump go-git

This commit is contained in:
Stefan Haller
2025-04-09 10:38:46 +02:00
parent da0105c16b
commit 4cf49ff449
527 changed files with 70489 additions and 10167 deletions

View File

@ -39,7 +39,7 @@ func (c *Change) Action() (merkletrie.Action, error) {
return merkletrie.Modify, nil
}
// Files return the files before and after a change.
// Files returns the files before and after a change.
// For insertions from will be nil. For deletions to will be nil.
func (c *Change) Files() (from, to *File, err error) {
action, err := c.Action()

View File

@ -16,11 +16,11 @@ func newChange(c merkletrie.Change) (*Change, error) {
var err error
if ret.From, err = newChangeEntry(c.From); err != nil {
return nil, fmt.Errorf("From field: %s", err)
return nil, fmt.Errorf("from field: %s", err)
}
if ret.To, err = newChangeEntry(c.To); err != nil {
return nil, fmt.Errorf("To field: %s", err)
return nil, fmt.Errorf("to field: %s", err)
}
return ret, nil

View File

@ -1,7 +1,6 @@
package object
import (
"bufio"
"bytes"
"context"
"errors"
@ -9,22 +8,34 @@ import (
"io"
"strings"
"golang.org/x/crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/jesseduffield/go-git/v5/plumbing"
"github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
"github.com/jesseduffield/go-git/v5/utils/sync"
)
const (
beginpgp string = "-----BEGIN PGP SIGNATURE-----"
endpgp string = "-----END PGP SIGNATURE-----"
headerpgp string = "gpgsig"
beginpgp string = "-----BEGIN PGP SIGNATURE-----"
endpgp string = "-----END PGP SIGNATURE-----"
headerpgp string = "gpgsig"
headerencoding string = "encoding"
// https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153
// When a merge commit is created from a signed tag, the tag is embedded in
// the commit with the "mergetag" header.
headermergetag string = "mergetag"
defaultUtf8CommitMessageEncoding MessageEncoding = "UTF-8"
)
// Hash represents the hash of an object
type Hash plumbing.Hash
// MessageEncoding represents the encoding of a commit
type MessageEncoding string
// Commit points to a single tree, marking it as what the project looked like
// at a certain point in time. It contains meta-information about that point
// in time, such as a timestamp, the author of the changes since the last
@ -38,6 +49,9 @@ type Commit struct {
// Committer is the one performing the commit, might be different from
// Author.
Committer Signature
// MergeTag is the embedded tag object when a merge commit is created by
// merging a signed tag.
MergeTag string
// PGPSignature is the PGP signature of the commit.
PGPSignature string
// Message is the commit message, contains arbitrary text.
@ -46,6 +60,8 @@ type Commit struct {
TreeHash plumbing.Hash
// ParentHashes are the hashes of the parent commits of the commit.
ParentHashes []plumbing.Hash
// Encoding is the encoding of the commit.
Encoding MessageEncoding
s storer.EncodedObjectStorer
}
@ -173,6 +189,7 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
}
c.Hash = o.Hash()
c.Encoding = defaultUtf8CommitMessageEncoding
reader, err := o.Reader()
if err != nil {
@ -180,11 +197,11 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
}
defer ioutil.CheckClose(reader, &err)
r := bufPool.Get().(*bufio.Reader)
defer bufPool.Put(r)
r.Reset(reader)
r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r)
var message bool
var mergetag bool
var pgpsig bool
var msgbuf bytes.Buffer
for {
@ -193,6 +210,16 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
return err
}
if mergetag {
if len(line) > 0 && line[0] == ' ' {
line = bytes.TrimLeft(line, " ")
c.MergeTag += string(line)
continue
} else {
mergetag = false
}
}
if pgpsig {
if len(line) > 0 && line[0] == ' ' {
line = bytes.TrimLeft(line, " ")
@ -226,6 +253,11 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
c.Author.Decode(data)
case "committer":
c.Committer.Decode(data)
case headermergetag:
c.MergeTag += string(data) + "\n"
mergetag = true
case headerencoding:
c.Encoding = MessageEncoding(data)
case headerpgp:
c.PGPSignature += string(data) + "\n"
pgpsig = true
@ -287,6 +319,28 @@ func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err
}
if c.MergeTag != "" {
if _, err = fmt.Fprint(w, "\n"+headermergetag+" "); err != nil {
return err
}
// Split tag information lines and re-write with a left padding and
// newline. Use join for this so it's clear that a newline should not be
// added after this section. The newline will be added either as part of
// the PGP signature or the commit message.
mergetag := strings.TrimSuffix(c.MergeTag, "\n")
lines := strings.Split(mergetag, "\n")
if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil {
return err
}
}
if string(c.Encoding) != "" && c.Encoding != defaultUtf8CommitMessageEncoding {
if _, err = fmt.Fprintf(w, "\n%s %s", headerencoding, c.Encoding); err != nil {
return err
}
}
if c.PGPSignature != "" && includeSig {
if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil {
return err
@ -374,7 +428,18 @@ func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
return nil, err
}
return openpgp.CheckArmoredDetachedSignature(keyring, er, signature)
return openpgp.CheckArmoredDetachedSignature(keyring, er, signature, nil)
}
// Less defines a compare function to determine which commit is 'earlier' by:
// - First use Committer.When
// - If Committer.When are equal then use Author.When
// - If Author.When also equal then compare the string value of the hash
func (c *Commit) Less(rhs *Commit) bool {
return c.Committer.When.Before(rhs.Committer.When) ||
(c.Committer.When.Equal(rhs.Committer.When) &&
(c.Author.When.Before(rhs.Author.When) ||
(c.Author.When.Equal(rhs.Author.When) && bytes.Compare(c.Hash[:], rhs.Hash[:]) < 0)))
}
func indent(t string) string {

View File

@ -57,6 +57,8 @@ func (c *commitPathIter) Next() (*Commit, error) {
}
func (c *commitPathIter) getNextFileCommit() (*Commit, error) {
var parentTree, currentTree *Tree
for {
// Parent-commit can be nil if the current-commit is the initial commit
parentCommit, parentCommitErr := c.sourceIter.Next()
@ -68,13 +70,17 @@ func (c *commitPathIter) getNextFileCommit() (*Commit, error) {
parentCommit = nil
}
// Fetch the trees of the current and parent commits
currentTree, currTreeErr := c.currentCommit.Tree()
if currTreeErr != nil {
return nil, currTreeErr
if parentTree == nil {
var currTreeErr error
currentTree, currTreeErr = c.currentCommit.Tree()
if currTreeErr != nil {
return nil, currTreeErr
}
} else {
currentTree = parentTree
parentTree = nil
}
var parentTree *Tree
if parentCommit != nil {
var parentTreeErr error
parentTree, parentTreeErr = parentCommit.Tree()
@ -115,7 +121,8 @@ func (c *commitPathIter) hasFileChange(changes Changes, parent *Commit) bool {
// filename matches, now check if source iterator contains all commits (from all refs)
if c.checkParent {
if parent != nil && isParentHash(parent.Hash, c.currentCommit) {
// Check if parent is beyond the initial commit
if parent == nil || isParentHash(parent.Hash, c.currentCommit) {
return true
}
continue

View File

@ -1,12 +0,0 @@
package object
import (
"bufio"
"sync"
)
var bufPool = sync.Pool{
New: func() interface{} {
return bufio.NewReader(nil)
},
}

View File

@ -6,7 +6,7 @@ import (
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"github.com/jesseduffield/go-git/v5/plumbing"
@ -96,10 +96,6 @@ func filePatchWithContext(ctx context.Context, c *Change) (fdiff.FilePatch, erro
}
func filePatch(c *Change) (fdiff.FilePatch, error) {
return filePatchWithContext(context.Background(), c)
}
func fileContent(f *File) (content string, isBinary bool, err error) {
if f == nil {
return
@ -238,61 +234,56 @@ func (fileStats FileStats) String() string {
return printStat(fileStats)
}
// printStat prints the stats of changes in content of files.
// Original implementation: https://github.com/git/git/blob/1a87c842ece327d03d08096395969aca5e0a6996/diff.c#L2615
// Parts of the output:
// <pad><filename><pad>|<pad><changeNumber><pad><+++/---><newline>
// example: " main.go | 10 +++++++--- "
func printStat(fileStats []FileStat) string {
padLength := float64(len(" "))
newlineLength := float64(len("\n"))
separatorLength := float64(len("|"))
// Soft line length limit. The text length calculation below excludes
// length of the change number. Adding that would take it closer to 80,
// but probably not more than 80, until it's a huge number.
lineLength := 72.0
maxGraphWidth := uint(53)
maxNameLen := 0
maxChangeLen := 0
// Get the longest filename and longest total change.
var longestLength float64
var longestTotalChange float64
for _, fs := range fileStats {
if int(longestLength) < len(fs.Name) {
longestLength = float64(len(fs.Name))
scaleLinear := func(it, width, max uint) uint {
if it == 0 || max == 0 {
return 0
}
totalChange := fs.Addition + fs.Deletion
if int(longestTotalChange) < totalChange {
longestTotalChange = float64(totalChange)
return 1 + (it * (width - 1) / max)
}
for _, fs := range fileStats {
if len(fs.Name) > maxNameLen {
maxNameLen = len(fs.Name)
}
changes := strconv.Itoa(fs.Addition + fs.Deletion)
if len(changes) > maxChangeLen {
maxChangeLen = len(changes)
}
}
// Parts of the output:
// <pad><filename><pad>|<pad><changeNumber><pad><+++/---><newline>
// example: " main.go | 10 +++++++--- "
// <pad><filename><pad>
leftTextLength := padLength + longestLength + padLength
// <pad><number><pad><+++++/-----><newline>
// Excluding number length here.
rightTextLength := padLength + padLength + newlineLength
totalTextArea := leftTextLength + separatorLength + rightTextLength
heightOfHistogram := lineLength - totalTextArea
// Scale the histogram.
var scaleFactor float64
if longestTotalChange > heightOfHistogram {
// Scale down to heightOfHistogram.
scaleFactor = longestTotalChange / heightOfHistogram
} else {
scaleFactor = 1.0
}
finalOutput := ""
result := ""
for _, fs := range fileStats {
addn := float64(fs.Addition)
deln := float64(fs.Deletion)
adds := strings.Repeat("+", int(math.Floor(addn/scaleFactor)))
dels := strings.Repeat("-", int(math.Floor(deln/scaleFactor)))
finalOutput += fmt.Sprintf(" %s | %d %s%s\n", fs.Name, (fs.Addition + fs.Deletion), adds, dels)
}
add := uint(fs.Addition)
del := uint(fs.Deletion)
np := maxNameLen - len(fs.Name)
cp := maxChangeLen - len(strconv.Itoa(fs.Addition+fs.Deletion))
return finalOutput
total := add + del
if total > maxGraphWidth {
add = scaleLinear(add, maxGraphWidth, total)
del = scaleLinear(del, maxGraphWidth, total)
}
adds := strings.Repeat("+", int(add))
dels := strings.Repeat("-", int(del))
namePad := strings.Repeat(" ", np)
changePad := strings.Repeat(" ", cp)
result += fmt.Sprintf(" %s%s | %s%d %s%s\n", fs.Name, namePad, changePad, total, adds, dels)
}
return result
}
func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats {
@ -313,8 +304,8 @@ func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats {
// File is deleted.
cs.Name = from.Path()
} else if from.Path() != to.Path() {
// File is renamed. Not supported.
// cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path())
// File is renamed.
cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path())
} else {
cs.Name = from.Path()
}

View File

@ -403,10 +403,16 @@ func min(a, b int) int {
return b
}
const maxMatrixSize = 10000
func buildSimilarityMatrix(srcs, dsts []*Change, renameScore int) (similarityMatrix, error) {
// Allocate for the worst-case scenario where every pair has a score
// that we need to consider. We might not need that many.
matrix := make(similarityMatrix, 0, len(srcs)*len(dsts))
matrixSize := len(srcs) * len(dsts)
if matrixSize > maxMatrixSize {
matrixSize = maxMatrixSize
}
matrix := make(similarityMatrix, 0, matrixSize)
srcSizes := make([]int64, len(srcs))
dstSizes := make([]int64, len(dsts))
dstTooLarge := make(map[int]bool)
@ -735,10 +741,7 @@ func (i *similarityIndex) add(key int, cnt uint64) error {
// It's the same key, so increment the counter.
var err error
i.hashes[j], err = newKeyCountPair(key, v.count()+cnt)
if err != nil {
return err
}
return nil
return err
} else if j+1 >= len(i.hashes) {
j = 0
} else {

View File

@ -0,0 +1,102 @@
package object
import "bytes"
const (
signatureTypeUnknown signatureType = iota
signatureTypeOpenPGP
signatureTypeX509
signatureTypeSSH
)
var (
// openPGPSignatureFormat is the format of an OpenPGP signature.
openPGPSignatureFormat = signatureFormat{
[]byte("-----BEGIN PGP SIGNATURE-----"),
[]byte("-----BEGIN PGP MESSAGE-----"),
}
// x509SignatureFormat is the format of an X509 signature, which is
// a PKCS#7 (S/MIME) signature.
x509SignatureFormat = signatureFormat{
[]byte("-----BEGIN CERTIFICATE-----"),
[]byte("-----BEGIN SIGNED MESSAGE-----"),
}
// sshSignatureFormat is the format of an SSH signature.
sshSignatureFormat = signatureFormat{
[]byte("-----BEGIN SSH SIGNATURE-----"),
}
)
var (
// knownSignatureFormats is a map of known signature formats, indexed by
// their signatureType.
knownSignatureFormats = map[signatureType]signatureFormat{
signatureTypeOpenPGP: openPGPSignatureFormat,
signatureTypeX509: x509SignatureFormat,
signatureTypeSSH: sshSignatureFormat,
}
)
// signatureType represents the type of the signature.
type signatureType int8
// signatureFormat represents the beginning of a signature.
type signatureFormat [][]byte
// typeForSignature returns the type of the signature based on its format.
func typeForSignature(b []byte) signatureType {
for t, i := range knownSignatureFormats {
for _, begin := range i {
if bytes.HasPrefix(b, begin) {
return t
}
}
}
return signatureTypeUnknown
}
// parseSignedBytes returns the position of the last signature block found in
// the given bytes. If no signature block is found, it returns -1.
//
// When multiple signature blocks are found, the position of the last one is
// returned. Any tailing bytes after this signature block start should be
// considered part of the signature.
//
// Given this, it would be safe to use the returned position to split the bytes
// into two parts: the first part containing the message, the second part
// containing the signature.
//
// Example:
//
// message := []byte(`Message with signature
//
// -----BEGIN SSH SIGNATURE-----
// ...`)
//
// var signature string
// if pos, _ := parseSignedBytes(message); pos != -1 {
// signature = string(message[pos:])
// message = message[:pos]
// }
//
// This logic is on par with git's gpg-interface.c:parse_signed_buffer().
// https://github.com/git/git/blob/7c2ef319c52c4997256f5807564523dfd4acdfc7/gpg-interface.c#L668
func parseSignedBytes(b []byte) (int, signatureType) {
var n, match = 0, -1
var t signatureType
for n < len(b) {
var i = b[n:]
if st := typeForSignature(i); st != signatureTypeUnknown {
match = n
t = st
}
if eol := bytes.IndexByte(i, '\n'); eol >= 0 {
n += eol + 1
continue
}
// If we reach this point, we've reached the end.
break
}
return match, t
}

View File

@ -1,18 +1,16 @@
package object
import (
"bufio"
"bytes"
"fmt"
"io"
stdioutil "io/ioutil"
"strings"
"golang.org/x/crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/jesseduffield/go-git/v5/plumbing"
"github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
"github.com/jesseduffield/go-git/v5/utils/sync"
)
// Tag represents an annotated tag object. It points to a single git object of
@ -93,9 +91,9 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
}
defer ioutil.CheckClose(reader, &err)
r := bufPool.Get().(*bufio.Reader)
defer bufPool.Put(r)
r.Reset(reader)
r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r)
for {
var line []byte
line, err = r.ReadBytes('\n')
@ -128,40 +126,15 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
}
}
data, err := stdioutil.ReadAll(r)
data, err := io.ReadAll(r)
if err != nil {
return err
}
var pgpsig bool
// Check if data contains PGP signature.
if bytes.Contains(data, []byte(beginpgp)) {
// Split the lines at newline.
messageAndSig := bytes.Split(data, []byte("\n"))
for _, l := range messageAndSig {
if pgpsig {
if bytes.Contains(l, []byte(endpgp)) {
t.PGPSignature += endpgp + "\n"
break
} else {
t.PGPSignature += string(l) + "\n"
}
continue
}
// Check if it's the beginning of a PGP signature.
if bytes.Contains(l, []byte(beginpgp)) {
t.PGPSignature += beginpgp + "\n"
pgpsig = true
continue
}
t.Message += string(l) + "\n"
}
} else {
t.Message = string(data)
if sm, _ := parseSignedBytes(data); sm >= 0 {
t.PGPSignature = string(data[sm:])
data = data[:sm]
}
t.Message = string(data)
return nil
}
@ -304,7 +277,7 @@ func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
return nil, err
}
return openpgp.CheckArmoredDetachedSignature(keyring, er, signature)
return openpgp.CheckArmoredDetachedSignature(keyring, er, signature, nil)
}
// TagIter provides an iterator for a set of tags.

View File

@ -1,19 +1,20 @@
package object
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"path"
"path/filepath"
"sort"
"strings"
"github.com/jesseduffield/go-git/v5/plumbing"
"github.com/jesseduffield/go-git/v5/plumbing/filemode"
"github.com/jesseduffield/go-git/v5/plumbing/storer"
"github.com/jesseduffield/go-git/v5/utils/ioutil"
"github.com/jesseduffield/go-git/v5/utils/sync"
)
const (
@ -27,6 +28,7 @@ var (
ErrFileNotFound = errors.New("file not found")
ErrDirectoryNotFound = errors.New("directory not found")
ErrEntryNotFound = errors.New("entry not found")
ErrEntriesNotSorted = errors.New("entries in tree are not sorted")
)
// Tree is basically like a directory - it references a bunch of other trees
@ -230,9 +232,9 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
}
defer ioutil.CheckClose(reader, &err)
r := bufPool.Get().(*bufio.Reader)
defer bufPool.Put(r)
r.Reset(reader)
r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r)
for {
str, err := r.ReadString(' ')
if err != nil {
@ -270,7 +272,30 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
return nil
}
type TreeEntrySorter []TreeEntry
func (s TreeEntrySorter) Len() int {
return len(s)
}
func (s TreeEntrySorter) Less(i, j int) bool {
name1 := s[i].Name
name2 := s[j].Name
if s[i].Mode == filemode.Dir {
name1 += "/"
}
if s[j].Mode == filemode.Dir {
name2 += "/"
}
return name1 < name2
}
func (s TreeEntrySorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Encode transforms a Tree into a plumbing.EncodedObject.
// The tree entries must be sorted by name.
func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
o.SetType(plumbing.TreeObject)
w, err := o.Writer()
@ -279,7 +304,15 @@ func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
}
defer ioutil.CheckClose(w, &err)
if !sort.IsSorted(TreeEntrySorter(t.Entries)) {
return ErrEntriesNotSorted
}
for _, entry := range t.Entries {
if strings.IndexByte(entry.Name, 0) != -1 {
return fmt.Errorf("malformed filename %q", entry.Name)
}
if _, err = fmt.Fprintf(w, "%o %s", entry.Mode, entry.Name); err != nil {
return err
}

View File

@ -38,6 +38,10 @@ func NewTreeRootNode(t *Tree) noder.Noder {
}
}
func (t *treeNoder) Skip() bool {
return false
}
func (t *treeNoder) isRoot() bool {
return t.name == ""
}
@ -84,7 +88,9 @@ func (t *treeNoder) Children() ([]noder.Noder, error) {
}
}
return transformChildren(parent)
var err error
t.children, err = transformChildren(parent)
return t.children, err
}
// Returns the children of a tree as treenoders.