From b4368b2a2ca4103b1ff4e37c34a963127342747e Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 30 Jun 2021 09:25:19 +0100 Subject: [PATCH 01/62] plumbing: format/packfile, prevent large objects from being read into memory completely (#330) This PR adds code to prevent large objects from being read into memory from packfiles or the filesystem. Objects greater than 1Mb are now no longer directly stored in the cache or read completely into memory. This PR differs and improves the previous broken #323 by fixing several bugs in the reader and transparently wrapping ReaderAt as a Reader. Signed-off-by: Andrew Thornton --- plumbing/format/packfile/delta_test.go | 51 +++++ .../format/packfile/encoder_advanced_test.go | 2 +- plumbing/format/packfile/encoder_test.go | 2 +- plumbing/format/packfile/fsobject.go | 54 +++-- plumbing/format/packfile/packfile.go | 92 +++++++- plumbing/format/packfile/packfile_test.go | 12 +- plumbing/format/packfile/patch_delta.go | 210 ++++++++++++++++++ plumbing/format/packfile/scanner.go | 15 ++ storage/filesystem/dotgit/reader.go | 79 +++++++ storage/filesystem/object.go | 21 +- storage/filesystem/object_test.go | 63 +++++- storage/filesystem/storage.go | 3 + utils/ioutil/common.go | 40 ++++ 13 files changed, 601 insertions(+), 43 deletions(-) create mode 100644 storage/filesystem/dotgit/reader.go diff --git a/plumbing/format/packfile/delta_test.go b/plumbing/format/packfile/delta_test.go index 98f53f6d6..137e4859b 100644 --- a/plumbing/format/packfile/delta_test.go +++ b/plumbing/format/packfile/delta_test.go @@ -1,8 +1,11 @@ package packfile import ( + "bytes" + "io/ioutil" "math/rand" + "github.com/go-git/go-git/v5/plumbing" . "gopkg.in/check.v1" ) @@ -97,6 +100,32 @@ func (s *DeltaSuite) TestAddDelta(c *C) { } } +func (s *DeltaSuite) TestAddDeltaReader(c *C) { + for _, t := range s.testCases { + baseBuf := genBytes(t.base) + baseObj := &plumbing.MemoryObject{} + baseObj.Write(baseBuf) + + targetBuf := genBytes(t.target) + + delta := DiffDelta(baseBuf, targetBuf) + deltaRC := ioutil.NopCloser(bytes.NewReader(delta)) + + c.Log("Executing test case:", t.description) + + resultRC, err := ReaderFromDelta(baseObj, deltaRC) + c.Assert(err, IsNil) + + result, err := ioutil.ReadAll(resultRC) + c.Assert(err, IsNil) + + err = resultRC.Close() + c.Assert(err, IsNil) + + c.Assert(result, DeepEquals, targetBuf) + } +} + func (s *DeltaSuite) TestIncompleteDelta(c *C) { for _, t := range s.testCases { c.Log("Incomplete delta on:", t.description) @@ -125,3 +154,25 @@ func (s *DeltaSuite) TestMaxCopySizeDelta(c *C) { c.Assert(err, IsNil) c.Assert(result, DeepEquals, targetBuf) } + +func (s *DeltaSuite) TestMaxCopySizeDeltaReader(c *C) { + baseBuf := randBytes(maxCopySize) + baseObj := &plumbing.MemoryObject{} + baseObj.Write(baseBuf) + + targetBuf := baseBuf[0:] + targetBuf = append(targetBuf, byte(1)) + + delta := DiffDelta(baseBuf, targetBuf) + deltaRC := ioutil.NopCloser(bytes.NewReader(delta)) + + resultRC, err := ReaderFromDelta(baseObj, deltaRC) + c.Assert(err, IsNil) + + result, err := ioutil.ReadAll(resultRC) + c.Assert(err, IsNil) + + err = resultRC.Close() + c.Assert(err, IsNil) + c.Assert(result, DeepEquals, targetBuf) +} diff --git a/plumbing/format/packfile/encoder_advanced_test.go b/plumbing/format/packfile/encoder_advanced_test.go index 95db5c082..15c0fba40 100644 --- a/plumbing/format/packfile/encoder_advanced_test.go +++ b/plumbing/format/packfile/encoder_advanced_test.go @@ -105,7 +105,7 @@ func (s *EncoderAdvancedSuite) testEncodeDecode( _, err = f.Seek(0, io.SeekStart) c.Assert(err, IsNil) - p := NewPackfile(index, fs, f) + p := NewPackfile(index, fs, f, 0) decodeHash, err := p.ID() c.Assert(err, IsNil) diff --git a/plumbing/format/packfile/encoder_test.go b/plumbing/format/packfile/encoder_test.go index d2db892a6..c9d49c3b5 100644 --- a/plumbing/format/packfile/encoder_test.go +++ b/plumbing/format/packfile/encoder_test.go @@ -318,7 +318,7 @@ func packfileFromReader(c *C, buf *bytes.Buffer) (*Packfile, func()) { index, err := w.Index() c.Assert(err, IsNil) - return NewPackfile(index, fs, file), func() { + return NewPackfile(index, fs, file, 0), func() { c.Assert(file.Close(), IsNil) } } diff --git a/plumbing/format/packfile/fsobject.go b/plumbing/format/packfile/fsobject.go index c5edaf52e..a395d171c 100644 --- a/plumbing/format/packfile/fsobject.go +++ b/plumbing/format/packfile/fsobject.go @@ -7,19 +7,21 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/format/idxfile" + "github.com/go-git/go-git/v5/utils/ioutil" ) // FSObject is an object from the packfile on the filesystem. type FSObject struct { - hash plumbing.Hash - h *ObjectHeader - offset int64 - size int64 - typ plumbing.ObjectType - index idxfile.Index - fs billy.Filesystem - path string - cache cache.Object + hash plumbing.Hash + h *ObjectHeader + offset int64 + size int64 + typ plumbing.ObjectType + index idxfile.Index + fs billy.Filesystem + path string + cache cache.Object + largeObjectThreshold int64 } // NewFSObject creates a new filesystem object. @@ -32,16 +34,18 @@ func NewFSObject( fs billy.Filesystem, path string, cache cache.Object, + largeObjectThreshold int64, ) *FSObject { return &FSObject{ - hash: hash, - offset: offset, - size: contentSize, - typ: finalType, - index: index, - fs: fs, - path: path, - cache: cache, + hash: hash, + offset: offset, + size: contentSize, + typ: finalType, + index: index, + fs: fs, + path: path, + cache: cache, + largeObjectThreshold: largeObjectThreshold, } } @@ -62,7 +66,21 @@ func (o *FSObject) Reader() (io.ReadCloser, error) { return nil, err } - p := NewPackfileWithCache(o.index, nil, f, o.cache) + p := NewPackfileWithCache(o.index, nil, f, o.cache, o.largeObjectThreshold) + if o.largeObjectThreshold > 0 && o.size > o.largeObjectThreshold { + // We have a big object + h, err := p.objectHeaderAtOffset(o.offset) + if err != nil { + return nil, err + } + + r, err := p.getReaderDirect(h) + if err != nil { + _ = f.Close() + return nil, err + } + return ioutil.NewReadCloserWithCloser(r, f.Close), nil + } r, err := p.getObjectContent(o.offset) if err != nil { _ = f.Close() diff --git a/plumbing/format/packfile/packfile.go b/plumbing/format/packfile/packfile.go index ddd7f62fc..8dd6041d5 100644 --- a/plumbing/format/packfile/packfile.go +++ b/plumbing/format/packfile/packfile.go @@ -2,6 +2,8 @@ package packfile import ( "bytes" + "compress/zlib" + "fmt" "io" "os" @@ -35,11 +37,12 @@ const smallObjectThreshold = 16 * 1024 // Packfile allows retrieving information from inside a packfile. type Packfile struct { idxfile.Index - fs billy.Filesystem - file billy.File - s *Scanner - deltaBaseCache cache.Object - offsetToType map[int64]plumbing.ObjectType + fs billy.Filesystem + file billy.File + s *Scanner + deltaBaseCache cache.Object + offsetToType map[int64]plumbing.ObjectType + largeObjectThreshold int64 } // NewPackfileWithCache creates a new Packfile with the given object cache. @@ -50,6 +53,7 @@ func NewPackfileWithCache( fs billy.Filesystem, file billy.File, cache cache.Object, + largeObjectThreshold int64, ) *Packfile { s := NewScanner(file) return &Packfile{ @@ -59,6 +63,7 @@ func NewPackfileWithCache( s, cache, make(map[int64]plumbing.ObjectType), + largeObjectThreshold, } } @@ -66,8 +71,8 @@ func NewPackfileWithCache( // and packfile idx. // If the filesystem is provided, the packfile will return FSObjects, otherwise // it will return MemoryObjects. -func NewPackfile(index idxfile.Index, fs billy.Filesystem, file billy.File) *Packfile { - return NewPackfileWithCache(index, fs, file, cache.NewObjectLRUDefault()) +func NewPackfile(index idxfile.Index, fs billy.Filesystem, file billy.File, largeObjectThreshold int64) *Packfile { + return NewPackfileWithCache(index, fs, file, cache.NewObjectLRUDefault(), largeObjectThreshold) } // Get retrieves the encoded object in the packfile with the given hash. @@ -263,6 +268,7 @@ func (p *Packfile) getNextObject(h *ObjectHeader, hash plumbing.Hash) (plumbing. p.fs, p.file.Name(), p.deltaBaseCache, + p.largeObjectThreshold, ), nil } @@ -282,6 +288,50 @@ func (p *Packfile) getObjectContent(offset int64) (io.ReadCloser, error) { return obj.Reader() } +func asyncReader(p *Packfile) (io.ReadCloser, error) { + reader := ioutil.NewReaderUsingReaderAt(p.file, p.s.r.offset) + zr := zlibReaderPool.Get().(io.ReadCloser) + + if err := zr.(zlib.Resetter).Reset(reader, nil); err != nil { + return nil, fmt.Errorf("zlib reset error: %s", err) + } + + return ioutil.NewReadCloserWithCloser(zr, func() error { + zlibReaderPool.Put(zr) + return nil + }), nil + +} + +func (p *Packfile) getReaderDirect(h *ObjectHeader) (io.ReadCloser, error) { + switch h.Type { + case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: + return asyncReader(p) + case plumbing.REFDeltaObject: + deltaRc, err := asyncReader(p) + if err != nil { + return nil, err + } + r, err := p.readREFDeltaObjectContent(h, deltaRc) + if err != nil { + return nil, err + } + return r, nil + case plumbing.OFSDeltaObject: + deltaRc, err := asyncReader(p) + if err != nil { + return nil, err + } + r, err := p.readOFSDeltaObjectContent(h, deltaRc) + if err != nil { + return nil, err + } + return r, nil + default: + return nil, ErrInvalidObject.AddDetails("type %q", h.Type) + } +} + func (p *Packfile) getNextMemoryObject(h *ObjectHeader) (plumbing.EncodedObject, error) { var obj = new(plumbing.MemoryObject) obj.SetSize(h.Length) @@ -334,6 +384,20 @@ func (p *Packfile) fillREFDeltaObjectContent(obj plumbing.EncodedObject, ref plu return p.fillREFDeltaObjectContentWithBuffer(obj, ref, buf) } +func (p *Packfile) readREFDeltaObjectContent(h *ObjectHeader, deltaRC io.Reader) (io.ReadCloser, error) { + var err error + + base, ok := p.cacheGet(h.Reference) + if !ok { + base, err = p.Get(h.Reference) + if err != nil { + return nil, err + } + } + + return ReaderFromDelta(base, deltaRC) +} + func (p *Packfile) fillREFDeltaObjectContentWithBuffer(obj plumbing.EncodedObject, ref plumbing.Hash, buf *bytes.Buffer) error { var err error @@ -364,6 +428,20 @@ func (p *Packfile) fillOFSDeltaObjectContent(obj plumbing.EncodedObject, offset return p.fillOFSDeltaObjectContentWithBuffer(obj, offset, buf) } +func (p *Packfile) readOFSDeltaObjectContent(h *ObjectHeader, deltaRC io.Reader) (io.ReadCloser, error) { + hash, err := p.FindHash(h.OffsetReference) + if err != nil { + return nil, err + } + + base, err := p.objectAtOffset(h.OffsetReference, hash) + if err != nil { + return nil, err + } + + return ReaderFromDelta(base, deltaRC) +} + func (p *Packfile) fillOFSDeltaObjectContentWithBuffer(obj plumbing.EncodedObject, offset int64, buf *bytes.Buffer) error { hash, err := p.FindHash(offset) if err != nil { diff --git a/plumbing/format/packfile/packfile_test.go b/plumbing/format/packfile/packfile_test.go index 60c7c7341..6af88170b 100644 --- a/plumbing/format/packfile/packfile_test.go +++ b/plumbing/format/packfile/packfile_test.go @@ -111,7 +111,7 @@ func (s *PackfileSuite) SetUpTest(c *C) { s.idx = idxfile.NewMemoryIndex() c.Assert(idxfile.NewDecoder(s.f.Idx()).Decode(s.idx), IsNil) - s.p = packfile.NewPackfile(s.idx, fixtures.Filesystem, s.f.Packfile()) + s.p = packfile.NewPackfile(s.idx, fixtures.Filesystem, s.f.Packfile(), 0) } func (s *PackfileSuite) TearDownTest(c *C) { @@ -122,7 +122,7 @@ func (s *PackfileSuite) TestDecode(c *C) { fixtures.Basic().ByTag("packfile").Test(c, func(f *fixtures.Fixture) { index := getIndexFromIdxFile(f.Idx()) - p := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile()) + p := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) defer p.Close() for _, h := range expectedHashes { @@ -138,7 +138,7 @@ func (s *PackfileSuite) TestDecodeByTypeRefDelta(c *C) { index := getIndexFromIdxFile(f.Idx()) - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile()) + packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) defer packfile.Close() iter, err := packfile.GetByType(plumbing.CommitObject) @@ -171,7 +171,7 @@ func (s *PackfileSuite) TestDecodeByType(c *C) { for _, t := range ts { index := getIndexFromIdxFile(f.Idx()) - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile()) + packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) defer packfile.Close() iter, err := packfile.GetByType(t) @@ -189,7 +189,7 @@ func (s *PackfileSuite) TestDecodeByTypeConstructor(c *C) { f := fixtures.Basic().ByTag("packfile").One() index := getIndexFromIdxFile(f.Idx()) - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile()) + packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) defer packfile.Close() _, err := packfile.GetByType(plumbing.OFSDeltaObject) @@ -266,7 +266,7 @@ func (s *PackfileSuite) TestSize(c *C) { index := getIndexFromIdxFile(f.Idx()) - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile()) + packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) defer packfile.Close() // Get the size of binary.jpg, which is not delta-encoded. diff --git a/plumbing/format/packfile/patch_delta.go b/plumbing/format/packfile/patch_delta.go index 9e90f30a7..17da11e03 100644 --- a/plumbing/format/packfile/patch_delta.go +++ b/plumbing/format/packfile/patch_delta.go @@ -1,9 +1,11 @@ package packfile import ( + "bufio" "bytes" "errors" "io" + "math" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/utils/ioutil" @@ -73,6 +75,131 @@ func PatchDelta(src, delta []byte) ([]byte, error) { return b.Bytes(), nil } +func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadCloser, error) { + deltaBuf := bufio.NewReaderSize(deltaRC, 1024) + srcSz, err := decodeLEB128ByteReader(deltaBuf) + if err != nil { + if err == io.EOF { + return nil, ErrInvalidDelta + } + return nil, err + } + if srcSz != uint(base.Size()) { + return nil, ErrInvalidDelta + } + + targetSz, err := decodeLEB128ByteReader(deltaBuf) + if err != nil { + if err == io.EOF { + return nil, ErrInvalidDelta + } + return nil, err + } + remainingTargetSz := targetSz + + dstRd, dstWr := io.Pipe() + + go func() { + baseRd, err := base.Reader() + if err != nil { + _ = dstWr.CloseWithError(ErrInvalidDelta) + return + } + defer baseRd.Close() + + baseBuf := bufio.NewReader(baseRd) + basePos := uint(0) + + for { + cmd, err := deltaBuf.ReadByte() + if err == io.EOF { + _ = dstWr.CloseWithError(ErrInvalidDelta) + return + } + if err != nil { + _ = dstWr.CloseWithError(err) + return + } + + if isCopyFromSrc(cmd) { + offset, err := decodeOffsetByteReader(cmd, deltaBuf) + if err != nil { + _ = dstWr.CloseWithError(err) + return + } + sz, err := decodeSizeByteReader(cmd, deltaBuf) + if err != nil { + _ = dstWr.CloseWithError(err) + return + } + + if invalidSize(sz, targetSz) || + invalidOffsetSize(offset, sz, srcSz) { + _ = dstWr.Close() + return + } + + discard := offset - basePos + if basePos > offset { + _ = baseRd.Close() + baseRd, err = base.Reader() + if err != nil { + _ = dstWr.CloseWithError(ErrInvalidDelta) + return + } + baseBuf.Reset(baseRd) + discard = offset + } + for discard > math.MaxInt32 { + n, err := baseBuf.Discard(math.MaxInt32) + if err != nil { + _ = dstWr.CloseWithError(err) + return + } + basePos += uint(n) + discard -= uint(n) + } + for discard > 0 { + n, err := baseBuf.Discard(int(discard)) + if err != nil { + _ = dstWr.CloseWithError(err) + return + } + basePos += uint(n) + discard -= uint(n) + } + if _, err := io.Copy(dstWr, io.LimitReader(baseBuf, int64(sz))); err != nil { + _ = dstWr.CloseWithError(err) + return + } + remainingTargetSz -= sz + basePos += sz + } else if isCopyFromDelta(cmd) { + sz := uint(cmd) // cmd is the size itself + if invalidSize(sz, targetSz) { + _ = dstWr.CloseWithError(ErrInvalidDelta) + return + } + if _, err := io.Copy(dstWr, io.LimitReader(deltaBuf, int64(sz))); err != nil { + _ = dstWr.CloseWithError(err) + return + } + + remainingTargetSz -= sz + } else { + _ = dstWr.CloseWithError(ErrDeltaCmd) + return + } + if remainingTargetSz <= 0 { + _ = dstWr.Close() + return + } + } + }() + + return dstRd, nil +} + func patchDelta(dst *bytes.Buffer, src, delta []byte) error { if len(delta) < deltaSizeMin { return ErrInvalidDelta @@ -161,6 +288,25 @@ func decodeLEB128(input []byte) (uint, []byte) { return num, input[sz:] } +func decodeLEB128ByteReader(input io.ByteReader) (uint, error) { + var num, sz uint + for { + b, err := input.ReadByte() + if err != nil { + return 0, err + } + + num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks + sz++ + + if uint(b)&continuation == 0 { + break + } + } + + return num, nil +} + const ( payload = 0x7f // 0111 1111 continuation = 0x80 // 1000 0000 @@ -174,6 +320,40 @@ func isCopyFromDelta(cmd byte) bool { return (cmd&0x80) == 0 && cmd != 0 } +func decodeOffsetByteReader(cmd byte, delta io.ByteReader) (uint, error) { + var offset uint + if (cmd & 0x01) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + offset = uint(next) + } + if (cmd & 0x02) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + offset |= uint(next) << 8 + } + if (cmd & 0x04) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + offset |= uint(next) << 16 + } + if (cmd & 0x08) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + offset |= uint(next) << 24 + } + + return offset, nil +} + func decodeOffset(cmd byte, delta []byte) (uint, []byte, error) { var offset uint if (cmd & 0x01) != 0 { @@ -208,6 +388,36 @@ func decodeOffset(cmd byte, delta []byte) (uint, []byte, error) { return offset, delta, nil } +func decodeSizeByteReader(cmd byte, delta io.ByteReader) (uint, error) { + var sz uint + if (cmd & 0x10) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + sz = uint(next) + } + if (cmd & 0x20) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + sz |= uint(next) << 8 + } + if (cmd & 0x40) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + sz |= uint(next) << 16 + } + if sz == 0 { + sz = 0x10000 + } + + return sz, nil +} + func decodeSize(cmd byte, delta []byte) (uint, []byte, error) { var sz uint if (cmd & 0x10) != 0 { diff --git a/plumbing/format/packfile/scanner.go b/plumbing/format/packfile/scanner.go index 6e6a68788..5d9e8fb65 100644 --- a/plumbing/format/packfile/scanner.go +++ b/plumbing/format/packfile/scanner.go @@ -320,6 +320,21 @@ func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err erro return } +// ReadObject returns a reader for the object content and an error +func (s *Scanner) ReadObject() (io.ReadCloser, error) { + s.pendingObject = nil + zr := zlibReaderPool.Get().(io.ReadCloser) + + if err := zr.(zlib.Resetter).Reset(s.r, nil); err != nil { + return nil, fmt.Errorf("zlib reset error: %s", err) + } + + return ioutil.NewReadCloserWithCloser(zr, func() error { + zlibReaderPool.Put(zr) + return nil + }), nil +} + // ReadRegularObject reads and write a non-deltified object // from it zlib stream in an object entry in the packfile. func (s *Scanner) copyObject(w io.Writer) (n int64, err error) { diff --git a/storage/filesystem/dotgit/reader.go b/storage/filesystem/dotgit/reader.go new file mode 100644 index 000000000..a82ac94eb --- /dev/null +++ b/storage/filesystem/dotgit/reader.go @@ -0,0 +1,79 @@ +package dotgit + +import ( + "fmt" + "io" + "os" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/format/objfile" + "github.com/go-git/go-git/v5/utils/ioutil" +) + +var _ (plumbing.EncodedObject) = &EncodedObject{} + +type EncodedObject struct { + dir *DotGit + h plumbing.Hash + t plumbing.ObjectType + sz int64 +} + +func (e *EncodedObject) Hash() plumbing.Hash { + return e.h +} + +func (e *EncodedObject) Reader() (io.ReadCloser, error) { + f, err := e.dir.Object(e.h) + if err != nil { + if os.IsNotExist(err) { + return nil, plumbing.ErrObjectNotFound + } + + return nil, err + } + r, err := objfile.NewReader(f) + if err != nil { + return nil, err + } + + t, size, err := r.Header() + if err != nil { + _ = r.Close() + return nil, err + } + if t != e.t { + _ = r.Close() + return nil, objfile.ErrHeader + } + if size != e.sz { + _ = r.Close() + return nil, objfile.ErrHeader + } + return ioutil.NewReadCloserWithCloser(r, f.Close), nil +} + +func (e *EncodedObject) SetType(plumbing.ObjectType) {} + +func (e *EncodedObject) Type() plumbing.ObjectType { + return e.t +} + +func (e *EncodedObject) Size() int64 { + return e.sz +} + +func (e *EncodedObject) SetSize(int64) {} + +func (e *EncodedObject) Writer() (io.WriteCloser, error) { + return nil, fmt.Errorf("Not supported") +} + +func NewEncodedObject(dir *DotGit, h plumbing.Hash, t plumbing.ObjectType, size int64) *EncodedObject { + return &EncodedObject{ + dir: dir, + h: h, + t: t, + sz: size, + } +} diff --git a/storage/filesystem/object.go b/storage/filesystem/object.go index 0c25dad61..5c91bcd69 100644 --- a/storage/filesystem/object.go +++ b/storage/filesystem/object.go @@ -204,9 +204,9 @@ func (s *ObjectStorage) packfile(idx idxfile.Index, pack plumbing.Hash) (*packfi var p *packfile.Packfile if s.objectCache != nil { - p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache) + p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache, s.options.LargeObjectThreshold) } else { - p = packfile.NewPackfile(idx, s.dir.Fs(), f) + p = packfile.NewPackfile(idx, s.dir.Fs(), f, s.options.LargeObjectThreshold) } return p, s.storePackfileInCache(pack, p) @@ -389,7 +389,6 @@ func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedOb return cacheObj, nil } - obj = s.NewEncodedObject() r, err := objfile.NewReader(f) if err != nil { return nil, err @@ -402,6 +401,13 @@ func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedOb return nil, err } + if s.options.LargeObjectThreshold > 0 && size > s.options.LargeObjectThreshold { + obj = dotgit.NewEncodedObject(s.dir, h, t, size) + return obj, nil + } + + obj = s.NewEncodedObject() + obj.SetType(t) obj.SetSize(size) w, err := obj.Writer() @@ -595,6 +601,7 @@ func (s *ObjectStorage) buildPackfileIters( return newPackfileIter( s.dir.Fs(), pack, t, seen, s.index[h], s.objectCache, s.options.KeepDescriptors, + s.options.LargeObjectThreshold, ) }, }, nil @@ -684,6 +691,7 @@ func NewPackfileIter( idxFile billy.File, t plumbing.ObjectType, keepPack bool, + largeObjectThreshold int64, ) (storer.EncodedObjectIter, error) { idx := idxfile.NewMemoryIndex() if err := idxfile.NewDecoder(idxFile).Decode(idx); err != nil { @@ -695,7 +703,7 @@ func NewPackfileIter( } seen := make(map[plumbing.Hash]struct{}) - return newPackfileIter(fs, f, t, seen, idx, nil, keepPack) + return newPackfileIter(fs, f, t, seen, idx, nil, keepPack, largeObjectThreshold) } func newPackfileIter( @@ -706,12 +714,13 @@ func newPackfileIter( index idxfile.Index, cache cache.Object, keepPack bool, + largeObjectThreshold int64, ) (storer.EncodedObjectIter, error) { var p *packfile.Packfile if cache != nil { - p = packfile.NewPackfileWithCache(index, fs, f, cache) + p = packfile.NewPackfileWithCache(index, fs, f, cache, largeObjectThreshold) } else { - p = packfile.NewPackfile(index, fs, f) + p = packfile.NewPackfile(index, fs, f, largeObjectThreshold) } iter, err := p.GetByType(t) diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go index 22f5b0c2b..59b40d3c2 100644 --- a/storage/filesystem/object_test.go +++ b/storage/filesystem/object_test.go @@ -107,6 +107,27 @@ func (s *FsSuite) TestGetFromPackfileMaxOpenDescriptors(c *C) { c.Assert(err, IsNil) } +func (s *FsSuite) TestGetFromPackfileMaxOpenDescriptorsLargeObjectThreshold(c *C) { + fs := fixtures.ByTag(".git").ByTag("multi-packfile").One().DotGit() + o := NewObjectStorageWithOptions(dotgit.New(fs), cache.NewObjectLRUDefault(), Options{ + MaxOpenDescriptors: 1, + LargeObjectThreshold: 1, + }) + + expected := plumbing.NewHash("8d45a34641d73851e01d3754320b33bb5be3c4d3") + obj, err := o.getFromPackfile(expected, false) + c.Assert(err, IsNil) + c.Assert(obj.Hash(), Equals, expected) + + expected = plumbing.NewHash("e9cfa4c9ca160546efd7e8582ec77952a27b17db") + obj, err = o.getFromPackfile(expected, false) + c.Assert(err, IsNil) + c.Assert(obj.Hash(), Equals, expected) + + err = o.Close() + c.Assert(err, IsNil) +} + func (s *FsSuite) TestGetSizeOfObjectFile(c *C) { fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit() o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault()) @@ -160,6 +181,21 @@ func (s *FsSuite) TestGetFromPackfileMultiplePackfiles(c *C) { c.Assert(obj.Hash(), Equals, expected) } +func (s *FsSuite) TestGetFromPackfileMultiplePackfilesLargeObjectThreshold(c *C) { + fs := fixtures.ByTag(".git").ByTag("multi-packfile").One().DotGit() + o := NewObjectStorageWithOptions(dotgit.New(fs), cache.NewObjectLRUDefault(), Options{LargeObjectThreshold: 1}) + + expected := plumbing.NewHash("8d45a34641d73851e01d3754320b33bb5be3c4d3") + obj, err := o.getFromPackfile(expected, false) + c.Assert(err, IsNil) + c.Assert(obj.Hash(), Equals, expected) + + expected = plumbing.NewHash("e9cfa4c9ca160546efd7e8582ec77952a27b17db") + obj, err = o.getFromPackfile(expected, false) + c.Assert(err, IsNil) + c.Assert(obj.Hash(), Equals, expected) +} + func (s *FsSuite) TestIter(c *C) { fixtures.ByTag(".git").ByTag("packfile").Test(c, func(f *fixtures.Fixture) { fs := f.DotGit() @@ -179,6 +215,25 @@ func (s *FsSuite) TestIter(c *C) { }) } +func (s *FsSuite) TestIterLargeObjectThreshold(c *C) { + fixtures.ByTag(".git").ByTag("packfile").Test(c, func(f *fixtures.Fixture) { + fs := f.DotGit() + o := NewObjectStorageWithOptions(dotgit.New(fs), cache.NewObjectLRUDefault(), Options{LargeObjectThreshold: 1}) + + iter, err := o.IterEncodedObjects(plumbing.AnyObject) + c.Assert(err, IsNil) + + var count int32 + err = iter.ForEach(func(o plumbing.EncodedObject) error { + count++ + return nil + }) + + c.Assert(err, IsNil) + c.Assert(count, Equals, f.ObjectsCount) + }) +} + func (s *FsSuite) TestIterWithType(c *C) { fixtures.ByTag(".git").Test(c, func(f *fixtures.Fixture) { for _, t := range objectTypes { @@ -215,7 +270,7 @@ func (s *FsSuite) TestPackfileIter(c *C) { idxf, err := dg.ObjectPackIdx(h) c.Assert(err, IsNil) - iter, err := NewPackfileIter(fs, f, idxf, t, false) + iter, err := NewPackfileIter(fs, f, idxf, t, false, 0) c.Assert(err, IsNil) err = iter.ForEach(func(o plumbing.EncodedObject) error { @@ -298,7 +353,7 @@ func (s *FsSuite) TestPackfileIterKeepDescriptors(c *C) { idxf, err := dg.ObjectPackIdx(h) c.Assert(err, IsNil) - iter, err := NewPackfileIter(fs, f, idxf, t, true) + iter, err := NewPackfileIter(fs, f, idxf, t, true, 0) c.Assert(err, IsNil) err = iter.ForEach(func(o plumbing.EncodedObject) error { @@ -377,7 +432,7 @@ func BenchmarkPackfileIter(b *testing.B) { b.Fatal(err) } - iter, err := NewPackfileIter(fs, f, idxf, t, false) + iter, err := NewPackfileIter(fs, f, idxf, t, false, 0) if err != nil { b.Fatal(err) } @@ -425,7 +480,7 @@ func BenchmarkPackfileIterReadContent(b *testing.B) { b.Fatal(err) } - iter, err := NewPackfileIter(fs, f, idxf, t, false) + iter, err := NewPackfileIter(fs, f, idxf, t, false, 0) if err != nil { b.Fatal(err) } diff --git a/storage/filesystem/storage.go b/storage/filesystem/storage.go index 8b69b27b0..7e7a2c50f 100644 --- a/storage/filesystem/storage.go +++ b/storage/filesystem/storage.go @@ -34,6 +34,9 @@ type Options struct { // MaxOpenDescriptors is the max number of file descriptors to keep // open. If KeepDescriptors is true, all file descriptors will remain open. MaxOpenDescriptors int + // LargeObjectThreshold maximum object size (in bytes) that will be read in to memory. + // If left unset or set to 0 there is no limit + LargeObjectThreshold int64 } // NewStorage returns a new Storage backed by a given `fs.Filesystem` and cache. diff --git a/utils/ioutil/common.go b/utils/ioutil/common.go index b52e85a38..b0ace4e62 100644 --- a/utils/ioutil/common.go +++ b/utils/ioutil/common.go @@ -55,6 +55,28 @@ func NewReadCloser(r io.Reader, c io.Closer) io.ReadCloser { return &readCloser{Reader: r, closer: c} } +type readCloserCloser struct { + io.ReadCloser + closer func() error +} + +func (r *readCloserCloser) Close() (err error) { + defer func() { + if err == nil { + err = r.closer() + return + } + _ = r.closer() + }() + return r.ReadCloser.Close() +} + +// NewReadCloserWithCloser creates an `io.ReadCloser` with the given `io.ReaderCloser` and +// `io.Closer` that ensures that the closer is closed on close +func NewReadCloserWithCloser(r io.ReadCloser, c func() error) io.ReadCloser { + return &readCloserCloser{ReadCloser: r, closer: c} +} + type writeCloser struct { io.Writer closer io.Closer @@ -82,6 +104,24 @@ func WriteNopCloser(w io.Writer) io.WriteCloser { return writeNopCloser{w} } +type readerAtAsReader struct { + io.ReaderAt + offset int64 +} + +func (r *readerAtAsReader) Read(bs []byte) (int, error) { + n, err := r.ReaderAt.ReadAt(bs, r.offset) + r.offset += int64(n) + return n, err +} + +func NewReaderUsingReaderAt(r io.ReaderAt, offset int64) io.Reader { + return &readerAtAsReader{ + ReaderAt: r, + offset: offset, + } +} + // CheckClose calls Close on the given io.Closer. If the given *error points to // nil, it will be assigned the error returned by Close. Otherwise, any error // returned by Close will be ignored. CheckClose is usually called with defer. From 4ec1753b4e9324d455d3b55060020ce324e6ced2 Mon Sep 17 00:00:00 2001 From: Christopher Hunter Date: Fri, 23 Jul 2021 16:32:40 -0700 Subject: [PATCH 02/62] plumbing/storer/object: improve grammar Go Doc (#350) --- plumbing/storer/object.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plumbing/storer/object.go b/plumbing/storer/object.go index dfe309db1..d8a9c27a6 100644 --- a/plumbing/storer/object.go +++ b/plumbing/storer/object.go @@ -52,8 +52,8 @@ type DeltaObjectStorer interface { DeltaObject(plumbing.ObjectType, plumbing.Hash) (plumbing.EncodedObject, error) } -// Transactioner is a optional method for ObjectStorer, it enable transaction -// base write and read operations in the storage +// Transactioner is a optional method for ObjectStorer, it enables transactional read and write +// operations. type Transactioner interface { // Begin starts a transaction. Begin() Transaction @@ -87,8 +87,8 @@ type PackedObjectStorer interface { DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) error } -// PackfileWriter is a optional method for ObjectStorer, it enable direct write -// of packfile to the storage +// PackfileWriter is an optional method for ObjectStorer, it enables directly writing +// a packfile to storage. type PackfileWriter interface { // PackfileWriter returns a writer for writing a packfile to the storage // From 9acce5071f2093d02eb8a485fbe2c8ff6e92c958 Mon Sep 17 00:00:00 2001 From: Adrian Pronk Date: Sat, 24 Jul 2021 12:40:14 +1200 Subject: [PATCH 03/62] plumbing: config, Branch name with hash can be cloned. Fixes #309 --- plumbing/format/config/encoder.go | 2 +- plumbing/format/config/fixtures_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/plumbing/format/config/encoder.go b/plumbing/format/config/encoder.go index 4eac8968a..93dad1a56 100644 --- a/plumbing/format/config/encoder.go +++ b/plumbing/format/config/encoder.go @@ -59,7 +59,7 @@ func (e *Encoder) encodeSubsection(sectionName string, s *Subsection) error { func (e *Encoder) encodeOptions(opts Options) error { for _, o := range opts { pattern := "\t%s = %s\n" - if strings.Contains(o.Value, "\\") { + if strings.ContainsAny(o.Value, `"#\"`) || strings.HasPrefix(o.Value, " ") || strings.HasSuffix(o.Value, " ") { pattern = "\t%s = %q\n" } diff --git a/plumbing/format/config/fixtures_test.go b/plumbing/format/config/fixtures_test.go index f3533dfee..3255213a1 100644 --- a/plumbing/format/config/fixtures_test.go +++ b/plumbing/format/config/fixtures_test.go @@ -42,6 +42,24 @@ var fixtures = []*Fixture{ Text: "[core]\n\trepositoryformatversion = 0\n", Config: New().AddOption("core", "", "repositoryformatversion", "0"), }, + { + Raw: "[section]\n", + Text: `[section] + option1 = "has # hash" + option2 = "has \" quote" + option3 = "has \\ backslash" + option4 = " has leading spaces" + option5 = "has trailing spaces " + option6 = has no special characters +`, + Config: New(). + AddOption("section", "", "option1", `has # hash`). + AddOption("section", "", "option2", `has " quote`). + AddOption("section", "", "option3", `has \ backslash`). + AddOption("section", "", "option4", ` has leading spaces`). + AddOption("section", "", "option5", `has trailing spaces `). + AddOption("section", "", "option6", `has no special characters`), + }, { Raw: ` [sect1] From 437ae96746723ceac4cf6bc5bb730b4f848b79ad Mon Sep 17 00:00:00 2001 From: Adrian Pronk Date: Sat, 24 Jul 2021 12:54:53 +1200 Subject: [PATCH 04/62] plumbing: config, remove duplicated character in set --- plumbing/format/config/encoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plumbing/format/config/encoder.go b/plumbing/format/config/encoder.go index 93dad1a56..62921e068 100644 --- a/plumbing/format/config/encoder.go +++ b/plumbing/format/config/encoder.go @@ -59,7 +59,7 @@ func (e *Encoder) encodeSubsection(sectionName string, s *Subsection) error { func (e *Encoder) encodeOptions(opts Options) error { for _, o := range opts { pattern := "\t%s = %s\n" - if strings.ContainsAny(o.Value, `"#\"`) || strings.HasPrefix(o.Value, " ") || strings.HasSuffix(o.Value, " ") { + if strings.ContainsAny(o.Value, `"#\`) || strings.HasPrefix(o.Value, " ") || strings.HasSuffix(o.Value, " ") { pattern = "\t%s = %q\n" } From a55116466586f5396b34f5296abdd7c5e68b98b8 Mon Sep 17 00:00:00 2001 From: Adrian Pronk Date: Sun, 25 Jul 2021 17:58:36 +1200 Subject: [PATCH 05/62] plumbing: config, support correct escaping as per git-config rules --- plumbing/format/config/encoder.go | 17 +++++++++++------ plumbing/format/config/fixtures_test.go | 21 ++++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/plumbing/format/config/encoder.go b/plumbing/format/config/encoder.go index 62921e068..de069aed5 100644 --- a/plumbing/format/config/encoder.go +++ b/plumbing/format/config/encoder.go @@ -11,6 +11,10 @@ type Encoder struct { w io.Writer } +var ( + subsectionReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`) + valueReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`, "\n", `\n`, "\t", `\t`, "\b", `\b`) +) // NewEncoder returns a new encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { return &Encoder{w} @@ -48,8 +52,7 @@ func (e *Encoder) encodeSection(s *Section) error { } func (e *Encoder) encodeSubsection(sectionName string, s *Subsection) error { - //TODO: escape - if err := e.printf("[%s \"%s\"]\n", sectionName, s.Name); err != nil { + if err := e.printf("[%s \"%s\"]\n", sectionName, subsectionReplacer.Replace(s.Name)); err != nil { return err } @@ -58,12 +61,14 @@ func (e *Encoder) encodeSubsection(sectionName string, s *Subsection) error { func (e *Encoder) encodeOptions(opts Options) error { for _, o := range opts { - pattern := "\t%s = %s\n" - if strings.ContainsAny(o.Value, `"#\`) || strings.HasPrefix(o.Value, " ") || strings.HasSuffix(o.Value, " ") { - pattern = "\t%s = %q\n" + var value string + if strings.ContainsAny(o.Value, "#;\"\t\n\\") || strings.HasPrefix(o.Value, " ") || strings.HasSuffix(o.Value, " ") { + value = `"`+valueReplacer.Replace(o.Value)+`"` + } else { + value = o.Value } - if err := e.printf(pattern, o.Key, o.Value); err != nil { + if err := e.printf("\t%s = %s\n", o.Key, value); err != nil { return err } } diff --git a/plumbing/format/config/fixtures_test.go b/plumbing/format/config/fixtures_test.go index 3255213a1..899ddf714 100644 --- a/plumbing/format/config/fixtures_test.go +++ b/plumbing/format/config/fixtures_test.go @@ -48,17 +48,24 @@ var fixtures = []*Fixture{ option1 = "has # hash" option2 = "has \" quote" option3 = "has \\ backslash" - option4 = " has leading spaces" - option5 = "has trailing spaces " - option6 = has no special characters -`, + option4 = "has ; semicolon" + option5 = "has \n line-feed" + option6 = "has \t tab" + option7 = " has leading spaces" + option8 = "has trailing spaces " + option9 = has no special characters +`+"\toption10 = has unusual \x01\x7f\xc8\x80 characters\n", Config: New(). AddOption("section", "", "option1", `has # hash`). AddOption("section", "", "option2", `has " quote`). AddOption("section", "", "option3", `has \ backslash`). - AddOption("section", "", "option4", ` has leading spaces`). - AddOption("section", "", "option5", `has trailing spaces `). - AddOption("section", "", "option6", `has no special characters`), + AddOption("section", "", "option4", `has ; semicolon`). + AddOption("section", "", "option5", "has \n line-feed"). + AddOption("section", "", "option6", "has \t tab"). + AddOption("section", "", "option7", ` has leading spaces`). + AddOption("section", "", "option8", `has trailing spaces `). + AddOption("section", "", "option9", `has no special characters`). + AddOption("section", "", "option10", "has unusual \x01\x7f\u0200 characters"), }, { Raw: ` From 4efe4cbee9e0631d92ad91db23f1271058d03a46 Mon Sep 17 00:00:00 2001 From: Adrian Pronk Date: Mon, 26 Jul 2021 08:19:06 +1200 Subject: [PATCH 06/62] plumbing: config, fix broken unit tests --- plumbing/format/config/fixtures_test.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/plumbing/format/config/fixtures_test.go b/plumbing/format/config/fixtures_test.go index 899ddf714..2fa7840b0 100644 --- a/plumbing/format/config/fixtures_test.go +++ b/plumbing/format/config/fixtures_test.go @@ -43,8 +43,7 @@ var fixtures = []*Fixture{ Config: New().AddOption("core", "", "repositoryformatversion", "0"), }, { - Raw: "[section]\n", - Text: `[section] + Raw: `[section] option1 = "has # hash" option2 = "has \" quote" option3 = "has \\ backslash" @@ -54,7 +53,18 @@ var fixtures = []*Fixture{ option7 = " has leading spaces" option8 = "has trailing spaces " option9 = has no special characters -`+"\toption10 = has unusual \x01\x7f\xc8\x80 characters\n", + option10 = has unusual ` + "\x01\x7f\xc8\x80 characters\n", + Text: `[section] + option1 = "has # hash" + option2 = "has \" quote" + option3 = "has \\ backslash" + option4 = "has ; semicolon" + option5 = "has \n line-feed" + option6 = "has \t tab" + option7 = " has leading spaces" + option8 = "has trailing spaces " + option9 = has no special characters + option10 = has unusual ` + "\x01\x7f\xc8\x80 characters\n", Config: New(). AddOption("section", "", "option1", `has # hash`). AddOption("section", "", "option2", `has " quote`). From 5e64929185218f53cb45b0f89fb372e06aa172be Mon Sep 17 00:00:00 2001 From: Norwin Date: Wed, 15 Sep 2021 15:34:17 +0200 Subject: [PATCH 07/62] Add RemoteURL to {Fetch,Pull,Push}Options Can be used to override the URL to operate on: RemoteName will be ignored for the actual fetch --- options.go | 8 +++++++- remote.go | 17 +++++++++++++---- worktree.go | 1 + 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/options.go b/options.go index 70687965b..ce041892e 100644 --- a/options.go +++ b/options.go @@ -91,6 +91,8 @@ func (o *CloneOptions) Validate() error { type PullOptions struct { // Name of the remote to be pulled. If empty, uses the default. RemoteName string + // RemoteURL overrides the remote repo address with a custom URL + RemoteURL string // Remote branch to clone. If empty, uses HEAD. ReferenceName plumbing.ReferenceName // Fetch only ReferenceName if true. @@ -147,7 +149,9 @@ const ( type FetchOptions struct { // Name of the remote to fetch from. Defaults to origin. RemoteName string - RefSpecs []config.RefSpec + // RemoteURL overrides the remote repo address with a custom URL + RemoteURL string + RefSpecs []config.RefSpec // Depth limit fetching to the specified number of commits from the tip of // each remote branch history. Depth int @@ -192,6 +196,8 @@ func (o *FetchOptions) Validate() error { type PushOptions struct { // RemoteName is the name of the remote to be pushed to. RemoteName string + // RemoteURL overrides the remote repo address with a custom URL + RemoteURL string // RefSpecs specify what destination ref to update with what source // object. A refspec with empty src can be used to delete a reference. RefSpecs []config.RefSpec diff --git a/remote.go b/remote.go index 4a06106c5..22bb63acd 100644 --- a/remote.go +++ b/remote.go @@ -9,6 +9,7 @@ import ( "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/internal/url" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/format/packfile" @@ -103,7 +104,11 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { return fmt.Errorf("remote names don't match: %s != %s", o.RemoteName, r.c.Name) } - s, err := newSendPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle) + if o.RemoteURL == "" { + o.RemoteURL = r.c.URLs[0] + } + + s, err := newSendPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle) if err != nil { return err } @@ -183,12 +188,12 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { var hashesToPush []plumbing.Hash // Avoid the expensive revlist operation if we're only doing deletes. if !allDelete { - if r.c.IsFirstURLLocal() { + if url.IsLocalEndpoint(o.RemoteURL) { // If we're are pushing to a local repo, it might be much // faster to use a local storage layer to get the commits // to ignore, when calculating the object revlist. localStorer := filesystem.NewStorage( - osfs.New(r.c.URLs[0]), cache.NewObjectLRUDefault()) + osfs.New(o.RemoteURL), cache.NewObjectLRUDefault()) hashesToPush, err = revlist.ObjectsWithStorageForIgnores( r.s, localStorer, objects, haves) } else { @@ -314,7 +319,11 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen o.RefSpecs = r.c.Fetch } - s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle) + if o.RemoteURL == "" { + o.RemoteURL = r.c.URLs[0] + } + + s, err := newUploadPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle) if err != nil { return nil, err } diff --git a/worktree.go b/worktree.go index f23d9f170..362d10e65 100644 --- a/worktree.go +++ b/worktree.go @@ -73,6 +73,7 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error { fetchHead, err := remote.fetch(ctx, &FetchOptions{ RemoteName: o.RemoteName, + RemoteURL: o.RemoteURL, Depth: o.Depth, Auth: o.Auth, Progress: o.Progress, From 06a3e504bee6033d42690d01100edede077c7fe5 Mon Sep 17 00:00:00 2001 From: Sven Nebel Date: Fri, 10 Sep 2021 20:09:28 +0100 Subject: [PATCH 08/62] examples: add find-if-any-tag-point-head Signed-off-by: Sven Nebel --- _examples/README.md | 1 + _examples/find-if-any-tag-point-head/main.go | 38 ++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 _examples/find-if-any-tag-point-head/main.go diff --git a/_examples/README.md b/_examples/README.md index 92b9d4df4..3a4c539d0 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -19,6 +19,7 @@ Here you can find a list of annotated _go-git_ examples: - [branch](branch/main.go) - How to create and remove branches or any other kind of reference. - [tag](tag/main.go) - List/print repository tags. - [tag create and push](tag-create-push/main.go) - Create and push a new tag. +- [tag find if head is tagged](find-if-any-tag-point-head/main.go) - Find if `HEAD` is tagged. - [remotes](remotes/main.go) - Working with remotes: adding, removing, etc. - [progress](progress/main.go) - Printing the progress information from the sideband. - [revision](revision/main.go) - Solve a revision into a commit. diff --git a/_examples/find-if-any-tag-point-head/main.go b/_examples/find-if-any-tag-point-head/main.go new file mode 100644 index 000000000..834aea2bb --- /dev/null +++ b/_examples/find-if-any-tag-point-head/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "os" + + "github.com/go-git/go-git/v5" + . "github.com/go-git/go-git/v5/_examples" + "github.com/go-git/go-git/v5/plumbing" +) + +// Basic example of how to find if HEAD is tagged. +func main() { + CheckArgs("") + path := os.Args[1] + + // We instantiate a new repository targeting the given path (the .git folder) + r, err := git.PlainOpen(path) + CheckIfError(err) + + // Get HEAD reference to use for comparison later on. + ref, err := r.Head() + CheckIfError(err) + + tags, err := r.Tags() + CheckIfError(err) + + // List all tags, both lightweight tags and annotated tags and see if some tag points to HEAD reference. + err = tags.ForEach(func(t *plumbing.Reference) error { + // This technique should work for both lightweight and annotated tags. + revHash, err := r.ResolveRevision(plumbing.Revision(t.Name())) + CheckIfError(err) + if *revHash == ref.Hash() { + fmt.Printf("Found tag %s with hash %s pointing to HEAD %s\n", t.Name().Short(), revHash, ref.Hash()) + } + return nil + }) +} From c4b334d4679d6fc38e13b2d84af2d832f6c47566 Mon Sep 17 00:00:00 2001 From: Norwin Date: Tue, 28 Sep 2021 11:10:17 +0200 Subject: [PATCH 09/62] add tests --- remote_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/remote_test.go b/remote_test.go index 1efc9da70..f6601fb4f 100644 --- a/remote_test.go +++ b/remote_test.go @@ -46,6 +46,12 @@ func (s *RemoteSuite) TestFetchInvalidSchemaEndpoint(c *C) { c.Assert(err, ErrorMatches, ".*unsupported scheme.*") } +func (s *RemoteSuite) TestFetchOverriddenEndpoint(c *C) { + r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://perfectly-valid-url.example.com"}}) + err := r.Fetch(&FetchOptions{RemoteURL: "http://\\"}) + c.Assert(err, ErrorMatches, ".*invalid character.*") +} + func (s *RemoteSuite) TestFetchInvalidFetchOptions(c *C) { r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}}) invalid := config.RefSpec("^*$ñ") @@ -903,6 +909,12 @@ func (s *RemoteSuite) TestPushNonExistentEndpoint(c *C) { c.Assert(err, NotNil) } +func (s *RemoteSuite) TestPushOverriddenEndpoint(c *C) { + r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"http://perfectly-valid-url.example.com"}}) + err := r.Push(&PushOptions{RemoteURL: "http://\\"}) + c.Assert(err, ErrorMatches, ".*invalid character.*") +} + func (s *RemoteSuite) TestPushInvalidSchemaEndpoint(c *C) { r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"qux://foo"}}) err := r.Push(&PushOptions{}) From 5340c58e393abecadb651e5f7ef43a66ce34ac88 Mon Sep 17 00:00:00 2001 From: John Cai Date: Sat, 2 Oct 2021 22:51:36 -0400 Subject: [PATCH 10/62] git: add --follow-tags option for pushes This PR adds support for the --follow-tags option for pushes. --- common_test.go | 8 ++++++ options.go | 3 ++ remote.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ remote_test.go | 60 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+) diff --git a/common_test.go b/common_test.go index 5f5bc4cd2..b47f5bbff 100644 --- a/common_test.go +++ b/common_test.go @@ -198,3 +198,11 @@ func AssertReferences(c *C, r *Repository, expected map[string]string) { c.Assert(obtained, DeepEquals, expected) } } + +func AssertReferencesMissing(c *C, r *Repository, expected []string) { + for _, name := range expected { + _, err := r.Reference(plumbing.ReferenceName(name), false) + c.Assert(err, NotNil) + c.Assert(err, Equals, plumbing.ErrReferenceNotFound) + } +} diff --git a/options.go b/options.go index 70687965b..6137ae73f 100644 --- a/options.go +++ b/options.go @@ -213,6 +213,9 @@ type PushOptions struct { // RequireRemoteRefs only allows a remote ref to be updated if its current // value is the one specified here. RequireRemoteRefs []config.RefSpec + // FollowTags will send any annotated tags with a commit target reachable from + // the refs already being pushed + FollowTags bool } // Validate validates the fields and sets the default values. diff --git a/remote.go b/remote.go index 4a06106c5..5a5e3f3a6 100644 --- a/remote.go +++ b/remote.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "strings" "time" "github.com/go-git/go-billy/v5/osfs" @@ -225,6 +226,77 @@ func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool { return !ar.Capabilities.Supports(capability.OFSDelta) } +func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error { + tags := make(map[plumbing.Reference]struct{}) + // get a list of all tags locally + for _, ref := range localRefs { + if strings.HasPrefix(string(ref.Name()), "refs/tags") { + tags[*ref] = struct{}{} + } + } + + remoteRefIter, err := remoteRefs.IterReferences() + if err != nil { + return err + } + + // remove any that are already on the remote + if err := remoteRefIter.ForEach(func(reference *plumbing.Reference) error { + if _, ok := tags[*reference]; ok { + delete(tags, *reference) + } + + return nil + }); err != nil { + return err + } + + for tag, _ := range tags { + tagObject, err := object.GetObject(r.s, tag.Hash()) + var tagCommit *object.Commit + if err != nil { + return fmt.Errorf("get tag object: %w\n", err) + } + + if tagObject.Type() != plumbing.TagObject { + continue + } + + annotatedTag, ok := tagObject.(*object.Tag) + if !ok { + return errors.New("could not get annotated tag object") + } + + tagCommit, err = object.GetCommit(r.s, annotatedTag.Target) + if err != nil { + return fmt.Errorf("get annotated tag commit: %w\n", err) + } + + // only include tags that are reachable from one of the refs + // already being pushed + for _, cmd := range req.Commands { + if tag.Name() == cmd.Name { + continue + } + + if strings.HasPrefix(cmd.Name.String(), "refs/tags") { + continue + } + + c, err := object.GetCommit(r.s, cmd.New) + if err != nil { + return fmt.Errorf("get commit %v: %w", cmd.Name, err) + } + + if isAncestor, err := tagCommit.IsAncestor(c); err == nil && isAncestor { + req.Commands = append(req.Commands, &packp.Command{Name: tag.Name(), New: tag.Hash()}) + } + } + } + + return nil +} + func (r *Remote) newReferenceUpdateRequest( o *PushOptions, localRefs []*plumbing.Reference, @@ -246,6 +318,12 @@ func (r *Remote) newReferenceUpdateRequest( return nil, err } + if o.FollowTags { + if err := r.addReachableTags(localRefs, remoteRefs, req); err != nil { + return nil, err + } + } + return req, nil } diff --git a/remote_test.go b/remote_test.go index 1efc9da70..66ca6b3f1 100644 --- a/remote_test.go +++ b/remote_test.go @@ -591,6 +591,66 @@ func (s *RemoteSuite) TestPushTags(c *C) { }) } +func (s *RemoteSuite) TestPushFollowTags(c *C) { + url, clean := s.TemporalDir() + defer clean() + + server, err := PlainInit(url, true) + c.Assert(err, IsNil) + + fs := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One().DotGit() + sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) + + r := NewRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URLs: []string{url}, + }) + + localRepo := newRepository(sto, fs) + tipTag, err := localRepo.CreateTag( + "tip", + plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), + &CreateTagOptions{ + Message: "an annotated tag", + }, + ) + c.Assert(err, IsNil) + + initialTag, err := localRepo.CreateTag( + "initial-commit", + plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"), + &CreateTagOptions{ + Message: "a tag for the initial commit", + }, + ) + c.Assert(err, IsNil) + + _, err = localRepo.CreateTag( + "master-tag", + plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), + &CreateTagOptions{ + Message: "a tag with a commit not reachable from branch", + }, + ) + c.Assert(err, IsNil) + + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{"+refs/heads/branch:refs/heads/branch"}, + FollowTags: true, + }) + c.Assert(err, IsNil) + + AssertReferences(c, server, map[string]string{ + "refs/heads/branch": "e8d3ffab552895c19b9fcf7aa264d277cde33881", + "refs/tags/tip": tipTag.Hash().String(), + "refs/tags/initial-commit": initialTag.Hash().String(), + }) + + AssertReferencesMissing(c, server, []string{ + "refs/tags/master-tag", + }) +} + func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) { fs := fixtures.Basic().One().DotGit() sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) From aba274ca7daf59d07d9559e6f99ca18ef0b78c7b Mon Sep 17 00:00:00 2001 From: "paul.t" Date: Mon, 11 Oct 2021 12:48:29 +0100 Subject: [PATCH 11/62] resolve external reference deltas --- plumbing/format/packfile/parser.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go index 4b5a5708c..4c28a4ad0 100644 --- a/plumbing/format/packfile/parser.go +++ b/plumbing/format/packfile/parser.go @@ -287,6 +287,7 @@ func (p *Parser) resolveDeltas() error { if err := p.resolveObject(stdioutil.Discard, child, content); err != nil { return err } + p.resolveExternalRef(child) } // Remove the delta from the cache. @@ -299,6 +300,16 @@ func (p *Parser) resolveDeltas() error { return nil } +func (p *Parser) resolveExternalRef(o *objectInfo) { + if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef { + p.oiByHash[o.SHA1] = o + o.Children = ref.Children + for _, c := range o.Children { + c.Parent = o + } + } +} + func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { if !o.ExternalRef { // skip cache check for placeholder parents b, ok := p.cache.Get(o.Offset) From 51514482c696e7d3aadd551cd1308cedc7055ce0 Mon Sep 17 00:00:00 2001 From: "paul.t" Date: Mon, 11 Oct 2021 13:07:34 +0100 Subject: [PATCH 12/62] resolve external reference delta --- plumbing/format/packfile/parser.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go index 4b5a5708c..4c28a4ad0 100644 --- a/plumbing/format/packfile/parser.go +++ b/plumbing/format/packfile/parser.go @@ -287,6 +287,7 @@ func (p *Parser) resolveDeltas() error { if err := p.resolveObject(stdioutil.Discard, child, content); err != nil { return err } + p.resolveExternalRef(child) } // Remove the delta from the cache. @@ -299,6 +300,16 @@ func (p *Parser) resolveDeltas() error { return nil } +func (p *Parser) resolveExternalRef(o *objectInfo) { + if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef { + p.oiByHash[o.SHA1] = o + o.Children = ref.Children + for _, c := range o.Children { + c.Parent = o + } + } +} + func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { if !o.ExternalRef { // skip cache check for placeholder parents b, ok := p.cache.Get(o.Offset) From e729edb00d36e9bd19f99dfa493984233b0dccfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Bohn?= Date: Tue, 26 Oct 2021 15:49:15 +0200 Subject: [PATCH 13/62] plumbing: packp, Add encoding for push-options. Fixes #268. go-git: Add field `Options` to `PushOptions`, wire functionality. --- options.go | 2 ++ plumbing/protocol/packp/updreq.go | 6 ++++ plumbing/protocol/packp/updreq_encode.go | 18 +++++++++++ plumbing/protocol/packp/updreq_encode_test.go | 30 ++++++++++++++++++- remote.go | 7 +++++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/options.go b/options.go index 3bd687650..84bf94744 100644 --- a/options.go +++ b/options.go @@ -222,6 +222,8 @@ type PushOptions struct { // FollowTags will send any annotated tags with a commit target reachable from // the refs already being pushed FollowTags bool + // PushOptions sets options to be transferred to the server during push. + Options map[string]string } // Validate validates the fields and sets the default values. diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go index 4d927d8b8..46ad6fdc9 100644 --- a/plumbing/protocol/packp/updreq.go +++ b/plumbing/protocol/packp/updreq.go @@ -19,6 +19,7 @@ var ( type ReferenceUpdateRequest struct { Capabilities *capability.List Commands []*Command + Options []*Option Shallow *plumbing.Hash // Packfile contains an optional packfile reader. Packfile io.ReadCloser @@ -120,3 +121,8 @@ func (c *Command) validate() error { return nil } + +type Option struct { + Key string + Value string +} diff --git a/plumbing/protocol/packp/updreq_encode.go b/plumbing/protocol/packp/updreq_encode.go index 2545e935e..08a819e15 100644 --- a/plumbing/protocol/packp/updreq_encode.go +++ b/plumbing/protocol/packp/updreq_encode.go @@ -29,6 +29,12 @@ func (req *ReferenceUpdateRequest) Encode(w io.Writer) error { return err } + if req.Capabilities.Supports(capability.PushOptions) { + if err := req.encodeOptions(e, req.Options); err != nil { + return err + } + } + if req.Packfile != nil { if _, err := io.Copy(w, req.Packfile); err != nil { return err @@ -73,3 +79,15 @@ func formatCommand(cmd *Command) string { n := cmd.New.String() return fmt.Sprintf("%s %s %s", o, n, cmd.Name) } + +func (req *ReferenceUpdateRequest) encodeOptions(e *pktline.Encoder, + opts []*Option) error { + + for _, opt := range opts { + if err := e.Encodef("%s=%s", opt.Key, opt.Value); err != nil { + return err + } + } + + return e.Flush() +} diff --git a/plumbing/protocol/packp/updreq_encode_test.go b/plumbing/protocol/packp/updreq_encode_test.go index 5ad2b1bb7..6ba004310 100644 --- a/plumbing/protocol/packp/updreq_encode_test.go +++ b/plumbing/protocol/packp/updreq_encode_test.go @@ -5,9 +5,11 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/pktline" + "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" - . "gopkg.in/check.v1" "io/ioutil" + + . "gopkg.in/check.v1" ) type UpdReqEncodeSuite struct{} @@ -142,3 +144,29 @@ func (s *UpdReqEncodeSuite) TestWithPackfile(c *C) { s.testEncode(c, r, expected) } + +func (s *UpdReqEncodeSuite) TestPushOptions(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + name := plumbing.ReferenceName("myref") + + r := NewReferenceUpdateRequest() + r.Capabilities.Set(capability.PushOptions) + r.Commands = []*Command{ + {Name: name, Old: hash1, New: hash2}, + } + r.Options = []*Option{ + {Key: "SomeKey", Value: "SomeValue"}, + {Key: "AnotherKey", Value: "AnotherValue"}, + } + + expected := pktlines(c, + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00push-options", + pktline.FlushString, + "SomeKey=SomeValue", + "AnotherKey=AnotherValue", + pktline.FlushString, + ) + + s.testEncode(c, r, expected) +} diff --git a/remote.go b/remote.go index 9f2995d4f..aeef00525 100644 --- a/remote.go +++ b/remote.go @@ -319,6 +319,13 @@ func (r *Remote) newReferenceUpdateRequest( } } + if ar.Capabilities.Supports(capability.PushOptions) { + _ = req.Capabilities.Set(capability.PushOptions) + for k, v := range o.Options { + req.Options = append(req.Options, &packp.Option{Key: k, Value: v}) + } + } + if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune); err != nil { return nil, err } From 55b186dc6178ad9c47821aeb684073dbf6893ed3 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Wed, 27 Oct 2021 22:57:41 +0200 Subject: [PATCH 14/62] plumbing: gitignore, Read .git/info/exclude file too. --- plumbing/format/gitignore/dir.go | 25 +++++++++------- plumbing/format/gitignore/dir_test.go | 43 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go index 7cea50cd8..15bc9c779 100644 --- a/plumbing/format/gitignore/dir.go +++ b/plumbing/format/gitignore/dir.go @@ -13,13 +13,14 @@ import ( ) const ( - commentPrefix = "#" - coreSection = "core" - excludesfile = "excludesfile" - gitDir = ".git" - gitignoreFile = ".gitignore" - gitconfigFile = ".gitconfig" - systemFile = "/etc/gitconfig" + commentPrefix = "#" + coreSection = "core" + excludesfile = "excludesfile" + gitDir = ".git" + gitignoreFile = ".gitignore" + gitconfigFile = ".gitconfig" + systemFile = "/etc/gitconfig" + infoExcludeFile = gitDir + "/info/exclude" ) // readIgnoreFile reads a specific git ignore file. @@ -42,10 +43,14 @@ func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps [ return } -// ReadPatterns reads gitignore patterns recursively traversing through the directory -// structure. The result is in the ascending order of priority (last higher). +// ReadPatterns reads the .git/info/exclude and then the gitignore patterns +// recursively traversing through the directory structure. The result is in +// the ascending order of priority (last higher). func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) { - ps, _ = readIgnoreFile(fs, path, gitignoreFile) + ps, _ = readIgnoreFile(fs, path, infoExcludeFile) + + subps, _ := readIgnoreFile(fs, path, gitignoreFile) + ps = append(ps, subps...) var fis []os.FileInfo fis, err = fs.ReadDir(fs.Join(path...)) diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go index 94ed7befc..f2cf7a86a 100644 --- a/plumbing/format/gitignore/dir_test.go +++ b/plumbing/format/gitignore/dir_test.go @@ -11,6 +11,7 @@ import ( type MatcherSuite struct { GFS billy.Filesystem // git repository root + IEFS billy.Filesystem // git repository root using info/exclude instead RFS billy.Filesystem // root that contains user home MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry @@ -53,6 +54,39 @@ func (s *MatcherSuite) SetUpTest(c *C) { s.GFS = fs + // setup generic git repository root using info/exclude instead + fs = memfs.New() + err = fs.MkdirAll(".git/info", os.ModePerm) + c.Assert(err, IsNil) + f, err = fs.Create(".git/info/exclude") + c.Assert(err, IsNil) + _, err = f.Write([]byte("vendor/g*/\n")) + c.Assert(err, IsNil) + _, err = f.Write([]byte("ignore.crlf\r\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + + err = fs.MkdirAll("vendor", os.ModePerm) + c.Assert(err, IsNil) + f, err = fs.Create("vendor/.gitignore") + c.Assert(err, IsNil) + _, err = f.Write([]byte("!github.com/\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + + err = fs.MkdirAll("another", os.ModePerm) + c.Assert(err, IsNil) + err = fs.MkdirAll("ignore.crlf", os.ModePerm) + c.Assert(err, IsNil) + err = fs.MkdirAll("vendor/github.com", os.ModePerm) + c.Assert(err, IsNil) + err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm) + c.Assert(err, IsNil) + + s.IEFS = fs + // setup root that contains user home home, err := os.UserHomeDir() c.Assert(err, IsNil) @@ -179,6 +213,15 @@ func (s *MatcherSuite) TestDir_ReadPatterns(c *C) { c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true) c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true) c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false) + + ps, err = ReadPatterns(s.IEFS, nil) + c.Assert(err, IsNil) + c.Assert(ps, HasLen, 3) + + m = NewMatcher(ps) + c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true) + c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true) + c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false) } func (s *MatcherSuite) TestDir_LoadGlobalPatterns(c *C) { From 92c37d55cc818cd8dda9cc04eff80da154f9aa39 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Wed, 27 Oct 2021 23:09:07 +0200 Subject: [PATCH 15/62] better tests --- plumbing/format/gitignore/dir_test.go | 48 +++++---------------------- 1 file changed, 9 insertions(+), 39 deletions(-) diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go index f2cf7a86a..facc36d8e 100644 --- a/plumbing/format/gitignore/dir_test.go +++ b/plumbing/format/gitignore/dir_test.go @@ -11,7 +11,6 @@ import ( type MatcherSuite struct { GFS billy.Filesystem // git repository root - IEFS billy.Filesystem // git repository root using info/exclude instead RFS billy.Filesystem // root that contains user home MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry @@ -25,40 +24,17 @@ var _ = Suite(&MatcherSuite{}) func (s *MatcherSuite) SetUpTest(c *C) { // setup generic git repository root fs := memfs.New() - f, err := fs.Create(".gitignore") - c.Assert(err, IsNil) - _, err = f.Write([]byte("vendor/g*/\n")) - c.Assert(err, IsNil) - _, err = f.Write([]byte("ignore.crlf\r\n")) - c.Assert(err, IsNil) - err = f.Close() - c.Assert(err, IsNil) - err = fs.MkdirAll("vendor", os.ModePerm) + err := fs.MkdirAll(".git/info", os.ModePerm) c.Assert(err, IsNil) - f, err = fs.Create("vendor/.gitignore") + f, err := fs.Create(".git/info/exclude") c.Assert(err, IsNil) - _, err = f.Write([]byte("!github.com/\n")) + _, err = f.Write([]byte("exclude.crlf\r\n")) c.Assert(err, IsNil) err = f.Close() c.Assert(err, IsNil) - err = fs.MkdirAll("another", os.ModePerm) - c.Assert(err, IsNil) - err = fs.MkdirAll("ignore.crlf", os.ModePerm) - c.Assert(err, IsNil) - err = fs.MkdirAll("vendor/github.com", os.ModePerm) - c.Assert(err, IsNil) - err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm) - c.Assert(err, IsNil) - - s.GFS = fs - - // setup generic git repository root using info/exclude instead - fs = memfs.New() - err = fs.MkdirAll(".git/info", os.ModePerm) - c.Assert(err, IsNil) - f, err = fs.Create(".git/info/exclude") + f, err = fs.Create(".gitignore") c.Assert(err, IsNil) _, err = f.Write([]byte("vendor/g*/\n")) c.Assert(err, IsNil) @@ -78,6 +54,8 @@ func (s *MatcherSuite) SetUpTest(c *C) { err = fs.MkdirAll("another", os.ModePerm) c.Assert(err, IsNil) + err = fs.MkdirAll("exclude.crlf", os.ModePerm) + c.Assert(err, IsNil) err = fs.MkdirAll("ignore.crlf", os.ModePerm) c.Assert(err, IsNil) err = fs.MkdirAll("vendor/github.com", os.ModePerm) @@ -85,7 +63,7 @@ func (s *MatcherSuite) SetUpTest(c *C) { err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm) c.Assert(err, IsNil) - s.IEFS = fs + s.GFS = fs // setup root that contains user home home, err := os.UserHomeDir() @@ -207,18 +185,10 @@ func (s *MatcherSuite) SetUpTest(c *C) { func (s *MatcherSuite) TestDir_ReadPatterns(c *C) { ps, err := ReadPatterns(s.GFS, nil) c.Assert(err, IsNil) - c.Assert(ps, HasLen, 3) + c.Assert(ps, HasLen, 4) m := NewMatcher(ps) - c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true) - c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true) - c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false) - - ps, err = ReadPatterns(s.IEFS, nil) - c.Assert(err, IsNil) - c.Assert(ps, HasLen, 3) - - m = NewMatcher(ps) + c.Assert(m.Match([]string{"exclude.crlf"}, true), Equals, true) c.Assert(m.Match([]string{"ignore.crlf"}, true), Equals, true) c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true) c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false) From 617ae9f34f46b440f59979cdfc8a399e36decc32 Mon Sep 17 00:00:00 2001 From: Thibault Jamet Date: Wed, 26 May 2021 14:19:07 +0200 Subject: [PATCH 16/62] Add support to push commits per hashes Using plain git, the command `git push ${sha}:refs/heads/some-branch` actually ensures that the remote branch `some-branch` points to the commit `${sha}`. In the current version of go-git, this results in an "everything is up to date" error. When a source reference is not found, check the object storage to find the sha. If it is found, consider pushing this exact commit. fixes: #105 --- remote.go | 41 ++++++++++++++++++++++ remote_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/remote.go b/remote.go index 9f2995d4f..b74f43138 100644 --- a/remote.go +++ b/remote.go @@ -602,6 +602,10 @@ func (r *Remote) addOrUpdateReferences( if !rs.IsWildcard() { ref, ok := refsDict[rs.Src()] if !ok { + commit, err := object.GetCommit(r.s, plumbing.NewHash(rs.Src())) + if err == nil { + return r.addCommit(rs, remoteRefs, commit.Hash, req) + } return nil } @@ -656,6 +660,43 @@ func (r *Remote) deleteReferences(rs config.RefSpec, }) } +func (r *Remote) addCommit(rs config.RefSpec, + remoteRefs storer.ReferenceStorer, localCommit plumbing.Hash, + req *packp.ReferenceUpdateRequest) error { + + if rs.IsWildcard() { + return errors.New("can't use wildcard together with hash refspecs") + } + + cmd := &packp.Command{ + Name: rs.Dst(""), + Old: plumbing.ZeroHash, + New: localCommit, + } + remoteRef, err := remoteRefs.Reference(cmd.Name) + if err == nil { + if remoteRef.Type() != plumbing.HashReference { + //TODO: check actual git behavior here + return nil + } + + cmd.Old = remoteRef.Hash() + } else if err != plumbing.ErrReferenceNotFound { + return err + } + if cmd.Old == cmd.New { + return nil + } + if !rs.IsForceUpdate() { + if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil { + return err + } + } + + req.Commands = append(req.Commands, cmd) + return nil +} + func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference, req *packp.ReferenceUpdateRequest) error { diff --git a/remote_test.go b/remote_test.go index 0283e6464..df07c082d 100644 --- a/remote_test.go +++ b/remote_test.go @@ -5,12 +5,16 @@ import ( "context" "errors" "io" + "io/ioutil" + "os" + "path/filepath" "runtime" "time" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/protocol/packp" "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" "github.com/go-git/go-git/v5/plumbing/storer" @@ -1206,3 +1210,91 @@ func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) { c.Assert(err, IsNil) c.Assert(newRef, Not(DeepEquals), oldRef) } + +func (s *RemoteSuite) TestCanPushShasToReference(c *C) { + d, err := ioutil.TempDir("", "TestCanPushShasToReference") + c.Assert(err, IsNil) + if err != nil { + return + } + defer os.RemoveAll(d) + + // remote currently forces a plain path for path based remotes inside the PushContext function. + // This makes it impossible, in the current state to use memfs. + // For the sake of readability, use the same osFS everywhere and use plain git repositories on temporary files + remote, err := PlainInit(filepath.Join(d, "remote"), true) + c.Assert(err, IsNil) + c.Assert(remote, NotNil) + + repo, err := PlainInit(filepath.Join(d, "repo"), false) + c.Assert(err, IsNil) + c.Assert(repo, NotNil) + + fd, err := os.Create(filepath.Join(d, "repo", "README.md")) + c.Assert(err, IsNil) + if err != nil { + return + } + _, err = fd.WriteString("# test repo") + c.Assert(err, IsNil) + if err != nil { + return + } + err = fd.Close() + c.Assert(err, IsNil) + if err != nil { + return + } + + wt, err := repo.Worktree() + c.Assert(err, IsNil) + if err != nil { + return + } + + wt.Add("README.md") + sha, err := wt.Commit("test commit", &CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + Committer: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + c.Assert(err, IsNil) + if err != nil { + return + } + + gitremote, err := repo.CreateRemote(&config.RemoteConfig{ + Name: "local", + URLs: []string{filepath.Join(d, "remote")}, + }) + c.Assert(err, IsNil) + if err != nil { + return + } + + err = gitremote.Push(&PushOptions{ + RemoteName: "local", + RefSpecs: []config.RefSpec{ + // TODO: check with short hashes that this is still respected + config.RefSpec(sha.String() + ":refs/heads/branch"), + }, + }) + c.Assert(err, IsNil) + if err != nil { + return + } + + ref, err := remote.Reference(plumbing.ReferenceName("refs/heads/branch"), false) + c.Assert(err, IsNil) + if err != nil { + return + } + c.Assert(ref.Hash().String(), Equals, sha.String()) +} From 21d8d7e95144eca38e93120b575c0ebd436e0e24 Mon Sep 17 00:00:00 2001 From: Thibault Jamet Date: Tue, 1 Jun 2021 23:33:46 +0200 Subject: [PATCH 17/62] Document the push refspec format Taken from `git help push` and adapted to match the supported features only. Future iterations of this feature may include better support for git "SHA-1 expression", documented in `git help push` as: > any arbitrary "SHA-1 expression", such as master~4 or HEAD (see gitrevisions(7)). --- options.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/options.go b/options.go index 3bd687650..b71299b5a 100644 --- a/options.go +++ b/options.go @@ -198,8 +198,14 @@ type PushOptions struct { RemoteName string // RemoteURL overrides the remote repo address with a custom URL RemoteURL string - // RefSpecs specify what destination ref to update with what source - // object. A refspec with empty src can be used to delete a reference. + // RefSpecs specify what destination ref to update with what source object. + // + // The format of a parameter is an optional plus +, followed by + // the source object , followed by a colon :, followed by the destination ref . + // The is often the name of the branch you would want to push, but it can be a SHA-1. + // The tells which ref on the remote side is updated with this push. + // + // A refspec with empty src can be used to delete a reference. RefSpecs []config.RefSpec // Auth credentials, if required, to use with the remote repository. Auth transport.AuthMethod From b12009d8338ec2dd83e56fa44a9065cc270282f8 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Fri, 29 Oct 2021 09:47:08 +0200 Subject: [PATCH 18/62] Update github.com/xanzy/ssh-agent to v0.3.1 Commands used: go get github.com/xanzy/ssh-agent@latest go mod tidy --- go.mod | 7 +++---- go.sum | 31 ++++++++++++------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 402613f0f..ccb8facd1 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/go-git/go-git/v5 require ( - github.com/Microsoft/go-winio v0.4.16 // indirect github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 github.com/acomagu/bufpipe v1.0.3 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect @@ -19,10 +18,10 @@ require ( github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 github.com/mitchellh/go-homedir v1.1.0 github.com/sergi/go-diff v1.1.0 - github.com/xanzy/ssh-agent v0.3.0 - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b + github.com/xanzy/ssh-agent v0.3.1 + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/net v0.0.0-20210326060303-6b1517762897 - golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c golang.org/x/text v0.3.3 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 922724775..ab212c592 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= @@ -22,8 +21,6 @@ github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev 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/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.0 h1:KZL1OFdS+afiIjN4hr/zpj5cEtC0OJhbmTA18PsBb8c= -github.com/go-git/go-billy/v5 v5.3.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= @@ -38,7 +35,6 @@ github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LF github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -51,38 +47,35 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= +github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 h1:RX8C8PRZc2hTIod4ds8ij+/4RQX3AqhYj3uOHmyaz4E= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= From 7db545b14827462679760b2d584782d69695acf4 Mon Sep 17 00:00:00 2001 From: John Cai Date: Mon, 1 Nov 2021 18:11:08 -0400 Subject: [PATCH 19/62] Add ForceWithLease Push Option --force-with-lease allows a push to force push with some safety measures. If the ref on the remote is what we expect, then the force push is allowed to happen. See https://git-scm.com/docs/git-push#Documentation/git-push.txt---force-with-leaseltrefnamegt for more information --- options.go | 15 ++++++ remote.go | 43 ++++++++++++++--- remote_test.go | 127 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 6 deletions(-) diff --git a/options.go b/options.go index e54889f80..fee2e16cf 100644 --- a/options.go +++ b/options.go @@ -228,10 +228,25 @@ type PushOptions struct { // FollowTags will send any annotated tags with a commit target reachable from // the refs already being pushed FollowTags bool + // ForceWithLease allows a force push as long as the remote ref adheres to a "lease" + ForceWithLease *ForceWithLease // PushOptions sets options to be transferred to the server during push. Options map[string]string } +// ForceWithLease sets fields on the lease +// If neither RefName nor Hash are set, ForceWithLease protects +// all refs in the refspec by ensuring the ref of the remote in the local repsitory +// matches the one in the ref advertisement. +type ForceWithLease struct { + // RefName, when set will protect the ref by ensuring it matches the + // hash in the ref advertisement. + RefName plumbing.ReferenceName + // Hash is the expected object id of RefName. The push will be rejected unless this + // matches the corresponding object id of RefName in the refs advertisement. + Hash plumbing.Hash +} + // Validate validates the fields and sets the default values. func (o *PushOptions) Validate() error { if o.RemoteName == "" { diff --git a/remote.go b/remote.go index 426bde928..fecdebf4e 100644 --- a/remote.go +++ b/remote.go @@ -326,7 +326,7 @@ func (r *Remote) newReferenceUpdateRequest( } } - if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune); err != nil { + if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune, o.ForceWithLease); err != nil { return nil, err } @@ -568,6 +568,7 @@ func (r *Remote) addReferencesToUpdate( remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest, prune bool, + forceWithLease *ForceWithLease, ) error { // This references dictionary will be used to search references by name. refsDict := make(map[string]*plumbing.Reference) @@ -581,7 +582,7 @@ func (r *Remote) addReferencesToUpdate( return err } } else { - err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req) + err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req, forceWithLease) if err != nil { return err } @@ -603,6 +604,7 @@ func (r *Remote) addOrUpdateReferences( refsDict map[string]*plumbing.Reference, remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest, + forceWithLease *ForceWithLease, ) error { // If it is not a wilcard refspec we can directly search for the reference // in the references dictionary. @@ -616,11 +618,11 @@ func (r *Remote) addOrUpdateReferences( return nil } - return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req) + return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease) } for _, ref := range localRefs { - err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req) + err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease) if err != nil { return err } @@ -706,7 +708,7 @@ func (r *Remote) addCommit(rs config.RefSpec, func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference, - req *packp.ReferenceUpdateRequest) error { + req *packp.ReferenceUpdateRequest, forceWithLease *ForceWithLease) error { if localRef.Type() != plumbing.HashReference { return nil @@ -738,7 +740,11 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, return nil } - if !rs.IsForceUpdate() { + if forceWithLease != nil { + if err = r.checkForceWithLease(localRef, cmd, forceWithLease); err != nil { + return err + } + } else if !rs.IsForceUpdate() { if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil { return err } @@ -748,6 +754,31 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, return nil } +func (r *Remote) checkForceWithLease(localRef *plumbing.Reference, cmd *packp.Command, forceWithLease *ForceWithLease) error { + remotePrefix := fmt.Sprintf("refs/remotes/%s/", r.Config().Name) + + ref, err := storer.ResolveReference( + r.s, + plumbing.ReferenceName(remotePrefix+strings.Replace(localRef.Name().String(), "refs/heads/", "", -1))) + if err != nil { + return err + } + + if forceWithLease.RefName.String() == "" || (forceWithLease.RefName == cmd.Name) { + expectedOID := ref.Hash() + + if !forceWithLease.Hash.IsZero() { + expectedOID = forceWithLease.Hash + } + + if cmd.Old != expectedOID { + return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String()) + } + } + + return nil +} + func (r *Remote) references() ([]*plumbing.Reference, error) { var localRefs []*plumbing.Reference diff --git a/remote_test.go b/remote_test.go index df07c082d..ebe9a5580 100644 --- a/remote_test.go +++ b/remote_test.go @@ -816,6 +816,133 @@ func (s *RemoteSuite) TestPushForceWithOption(c *C) { c.Assert(newRef, Not(DeepEquals), oldRef) } +func (s *RemoteSuite) TestPushForceWithLease_success(c *C) { + testCases := []struct { + desc string + forceWithLease ForceWithLease + }{ + { + desc: "no arguments", + forceWithLease: ForceWithLease{}, + }, + { + desc: "ref name", + forceWithLease: ForceWithLease{ + RefName: plumbing.ReferenceName("refs/heads/branch"), + }, + }, + { + desc: "ref name and sha", + forceWithLease: ForceWithLease{ + RefName: plumbing.ReferenceName("refs/heads/branch"), + Hash: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), + }, + }, + } + + for _, tc := range testCases { + c.Log("Executing test cases:", tc.desc) + + f := fixtures.Basic().One() + sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + dstFs := f.DotGit() + dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) + + newCommit := plumbing.NewHashReference( + "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), + ) + c.Assert(sto.SetReference(newCommit), IsNil) + + ref, err := sto.Reference("refs/heads/branch") + c.Log(ref.String()) + + url := dstFs.Root() + r := NewRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URLs: []string{url}, + }) + + oldRef, err := dstSto.Reference("refs/heads/branch") + c.Assert(err, IsNil) + c.Assert(oldRef, NotNil) + + c.Assert(r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"}, + ForceWithLease: &ForceWithLease{}, + }), IsNil) + + newRef, err := dstSto.Reference("refs/heads/branch") + c.Assert(err, IsNil) + c.Assert(newRef, DeepEquals, newCommit) + } +} + +func (s *RemoteSuite) TestPushForceWithLease_failure(c *C) { + testCases := []struct { + desc string + forceWithLease ForceWithLease + }{ + { + desc: "no arguments", + forceWithLease: ForceWithLease{}, + }, + { + desc: "ref name", + forceWithLease: ForceWithLease{ + RefName: plumbing.ReferenceName("refs/heads/branch"), + }, + }, + { + desc: "ref name and sha", + forceWithLease: ForceWithLease{ + RefName: plumbing.ReferenceName("refs/heads/branch"), + Hash: plumbing.NewHash("152175bf7e5580299fa1f0ba41ef6474cc043b70"), + }, + }, + } + + for _, tc := range testCases { + c.Log("Executing test cases:", tc.desc) + + f := fixtures.Basic().One() + sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + c.Assert(sto.SetReference( + plumbing.NewHashReference( + "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), + ), + ), IsNil) + + dstFs := f.DotGit() + dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) + c.Assert(dstSto.SetReference( + plumbing.NewHashReference( + "refs/heads/branch", plumbing.NewHash("ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), + ), + ), IsNil) + + url := dstFs.Root() + r := NewRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URLs: []string{url}, + }) + + oldRef, err := dstSto.Reference("refs/heads/branch") + c.Assert(err, IsNil) + c.Assert(oldRef, NotNil) + + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"}, + ForceWithLease: &ForceWithLease{}, + }) + + c.Assert(err, DeepEquals, errors.New("non-fast-forward update: refs/heads/branch")) + + newRef, err := dstSto.Reference("refs/heads/branch") + c.Assert(err, IsNil) + c.Assert(newRef, Not(DeepEquals), plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")) + } +} + func (s *RemoteSuite) TestPushPrune(c *C) { fs := fixtures.Basic().One().DotGit() From 589a41ceedfa89e1ff334a969d1beb28cb731de9 Mon Sep 17 00:00:00 2001 From: John Cai Date: Wed, 3 Nov 2021 14:04:06 -0400 Subject: [PATCH 20/62] Add Atomic to push options push --atomic allows a push to succeed or fail atomically. If one ref fails, the whole push fails. This commit allows the user to set Atomic as an option for a push. --- options.go | 2 ++ plumbing/protocol/packp/updreq_encode_test.go | 19 +++++++++++++++++++ remote.go | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/options.go b/options.go index e54889f80..77b74e526 100644 --- a/options.go +++ b/options.go @@ -230,6 +230,8 @@ type PushOptions struct { FollowTags bool // PushOptions sets options to be transferred to the server during push. Options map[string]string + // Atomic sets option to be an atomic push + Atomic bool } // Validate validates the fields and sets the default values. diff --git a/plumbing/protocol/packp/updreq_encode_test.go b/plumbing/protocol/packp/updreq_encode_test.go index 6ba004310..4370b794f 100644 --- a/plumbing/protocol/packp/updreq_encode_test.go +++ b/plumbing/protocol/packp/updreq_encode_test.go @@ -170,3 +170,22 @@ func (s *UpdReqEncodeSuite) TestPushOptions(c *C) { s.testEncode(c, r, expected) } + +func (s *UpdReqEncodeSuite) TestPushAtomic(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + name := plumbing.ReferenceName("myref") + + r := NewReferenceUpdateRequest() + r.Capabilities.Set(capability.Atomic) + r.Commands = []*Command{ + {Name: name, Old: hash1, New: hash2}, + } + + expected := pktlines(c, + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00atomic", + pktline.FlushString, + ) + + s.testEncode(c, r, expected) +} diff --git a/remote.go b/remote.go index 426bde928..503ca61f8 100644 --- a/remote.go +++ b/remote.go @@ -326,6 +326,10 @@ func (r *Remote) newReferenceUpdateRequest( } } + if o.Atomic && ar.Capabilities.Supports(capability.Atomic) { + _ = req.Capabilities.Set(capability.Atomic) + } + if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune); err != nil { return nil, err } From a04ddf534f941bb531459dbdf98c875495272069 Mon Sep 17 00:00:00 2001 From: John Cai Date: Fri, 5 Nov 2021 14:57:38 -0400 Subject: [PATCH 21/62] Support v3 index Currently the index encoder does not support the v3 index format. This change adds support to the encoder. This helps to unlock sparse checkout. --- plumbing/format/index/encoder.go | 34 ++++++++++++++++++++------- plumbing/format/index/encoder_test.go | 26 ++++++++++++++++---- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/plumbing/format/index/encoder.go b/plumbing/format/index/encoder.go index 00d4e7a31..2c94d93fc 100644 --- a/plumbing/format/index/encoder.go +++ b/plumbing/format/index/encoder.go @@ -14,7 +14,7 @@ import ( var ( // EncodeVersionSupported is the range of supported index versions - EncodeVersionSupported uint32 = 2 + EncodeVersionSupported uint32 = 3 // ErrInvalidTimestamp is returned by Encode if a Index with a Entry with // negative timestamp values @@ -36,9 +36,9 @@ func NewEncoder(w io.Writer) *Encoder { // Encode writes the Index to the stream of the encoder. func (e *Encoder) Encode(idx *Index) error { - // TODO: support versions v3 and v4 + // TODO: support v4 // TODO: support extensions - if idx.Version != EncodeVersionSupported { + if idx.Version > EncodeVersionSupported { return ErrUnsupportedVersion } @@ -68,8 +68,12 @@ func (e *Encoder) encodeEntries(idx *Index) error { if err := e.encodeEntry(entry); err != nil { return err } + entryLength := entryHeaderLength + if entry.IntentToAdd || entry.SkipWorktree { + entryLength += 2 + } - wrote := entryHeaderLength + len(entry.Name) + wrote := entryLength + len(entry.Name) if err := e.padEntry(wrote); err != nil { return err } @@ -79,10 +83,6 @@ func (e *Encoder) encodeEntries(idx *Index) error { } func (e *Encoder) encodeEntry(entry *Entry) error { - if entry.IntentToAdd || entry.SkipWorktree { - return ErrUnsupportedVersion - } - sec, nsec, err := e.timeToUint32(&entry.CreatedAt) if err != nil { return err @@ -110,9 +110,25 @@ func (e *Encoder) encodeEntry(entry *Entry) error { entry.GID, entry.Size, entry.Hash[:], - flags, } + flagsFlow := []interface{}{flags} + + if entry.IntentToAdd || entry.SkipWorktree { + var extendedFlags uint16 + + if entry.IntentToAdd { + extendedFlags |= intentToAddMask + } + if entry.SkipWorktree { + extendedFlags |= skipWorkTreeMask + } + + flagsFlow = []interface{}{flags | entryExtended, extendedFlags} + } + + flow = append(flow, flagsFlow...) + if err := binary.Write(e.w, flow...); err != nil { return err } diff --git a/plumbing/format/index/encoder_test.go b/plumbing/format/index/encoder_test.go index b7a73cb14..25c24f14f 100644 --- a/plumbing/format/index/encoder_test.go +++ b/plumbing/format/index/encoder_test.go @@ -57,7 +57,7 @@ func (s *IndexSuite) TestEncode(c *C) { } func (s *IndexSuite) TestEncodeUnsupportedVersion(c *C) { - idx := &Index{Version: 3} + idx := &Index{Version: 4} buf := bytes.NewBuffer(nil) e := NewEncoder(buf) @@ -67,24 +67,40 @@ func (s *IndexSuite) TestEncodeUnsupportedVersion(c *C) { func (s *IndexSuite) TestEncodeWithIntentToAddUnsupportedVersion(c *C) { idx := &Index{ - Version: 2, + Version: 3, Entries: []*Entry{{IntentToAdd: true}}, } buf := bytes.NewBuffer(nil) e := NewEncoder(buf) err := e.Encode(idx) - c.Assert(err, Equals, ErrUnsupportedVersion) + c.Assert(err, IsNil) + + output := &Index{} + d := NewDecoder(buf) + err = d.Decode(output) + c.Assert(err, IsNil) + + c.Assert(cmp.Equal(idx, output), Equals, true) + c.Assert(output.Entries[0].IntentToAdd, Equals, true) } func (s *IndexSuite) TestEncodeWithSkipWorktreeUnsupportedVersion(c *C) { idx := &Index{ - Version: 2, + Version: 3, Entries: []*Entry{{SkipWorktree: true}}, } buf := bytes.NewBuffer(nil) e := NewEncoder(buf) err := e.Encode(idx) - c.Assert(err, Equals, ErrUnsupportedVersion) + c.Assert(err, IsNil) + + output := &Index{} + d := NewDecoder(buf) + err = d.Decode(output) + c.Assert(err, IsNil) + + c.Assert(cmp.Equal(idx, output), Equals, true) + c.Assert(output.Entries[0].SkipWorktree, Equals, true) } From b939cf8471d8c1aac1960a833c7275e5b79ed013 Mon Sep 17 00:00:00 2001 From: merlin Date: Sun, 7 Nov 2021 20:39:51 +0300 Subject: [PATCH 22/62] config: add support for branch description --- config/branch.go | 23 +++++++++++++++++++++++ config/config.go | 1 + 2 files changed, 24 insertions(+) diff --git a/config/branch.go b/config/branch.go index fe86cf542..a8fe0b5ab 100644 --- a/config/branch.go +++ b/config/branch.go @@ -2,6 +2,7 @@ package config import ( "errors" + "strings" "github.com/go-git/go-git/v5/plumbing" format "github.com/go-git/go-git/v5/plumbing/format/config" @@ -26,6 +27,12 @@ type Branch struct { // "true" and "interactive". "false" is undocumented and // typically represented by the non-existence of this field Rebase string + // Description explains what the branch is for. + // Multi-line explanations may be used. + // + // Original git command to edit: + // git branch --edit-description + Description string raw *format.Subsection } @@ -75,9 +82,20 @@ func (b *Branch) marshal() *format.Subsection { b.raw.SetOption(rebaseKey, b.Rebase) } + if b.Description == "" { + b.raw.RemoveOption(descriptionKey) + } else { + desc := quoteDescription(b.Description) + b.raw.SetOption(descriptionKey, desc) + } + return b.raw } +func quoteDescription(desc string) string { + return strings.ReplaceAll(desc, "\n", `\n`) +} + func (b *Branch) unmarshal(s *format.Subsection) error { b.raw = s @@ -85,6 +103,11 @@ func (b *Branch) unmarshal(s *format.Subsection) error { b.Remote = b.raw.Options.Get(remoteSection) b.Merge = plumbing.ReferenceName(b.raw.Options.Get(mergeKey)) b.Rebase = b.raw.Options.Get(rebaseKey) + b.Description = unquoteDescription(b.raw.Options.Get(descriptionKey)) return b.Validate() } + +func unquoteDescription(desc string) string { + return strings.ReplaceAll(desc, `\n`, "\n") +} diff --git a/config/config.go b/config/config.go index 1aee25a4c..a16a5e5f4 100644 --- a/config/config.go +++ b/config/config.go @@ -247,6 +247,7 @@ const ( rebaseKey = "rebase" nameKey = "name" emailKey = "email" + descriptionKey = "description" defaultBranchKey = "defaultBranch" // DefaultPackWindow holds the number of previous objects used to From e0567bda08fcee09a2c24010d63c10d5f933faad Mon Sep 17 00:00:00 2001 From: merlin Date: Mon, 8 Nov 2021 00:25:06 +0300 Subject: [PATCH 23/62] config: add tests for branch desc marshaling and unmarshaling --- config/config_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index 6f0242d96..91f7df2ae 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -50,6 +50,7 @@ func (s *ConfigSuite) TestUnmarshal(c *C) { [branch "master"] remote = origin merge = refs/heads/master + description = "Add support for branch description.\\n\\nEdit branch description: git branch --edit-description\\n" [init] defaultBranch = main [url "ssh://git@github.com/"] @@ -86,6 +87,7 @@ func (s *ConfigSuite) TestUnmarshal(c *C) { c.Assert(cfg.Submodules["qux"].Branch, Equals, "bar") c.Assert(cfg.Branches["master"].Remote, Equals, "origin") c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master")) + c.Assert(cfg.Branches["master"].Description, Equals, "Add support for branch description.\n\nEdit branch description: git branch --edit-description\n") c.Assert(cfg.Init.DefaultBranch, Equals, "main") } @@ -111,6 +113,7 @@ func (s *ConfigSuite) TestMarshal(c *C) { [branch "master"] remote = origin merge = refs/heads/master + description = "Add support for branch description.\\n\\nEdit branch description: git branch --edit-description\\n" [url "ssh://git@github.com/"] insteadOf = https://github.com/ [init] @@ -149,9 +152,10 @@ func (s *ConfigSuite) TestMarshal(c *C) { } cfg.Branches["master"] = &Branch{ - Name: "master", - Remote: "origin", - Merge: "refs/heads/master", + Name: "master", + Remote: "origin", + Merge: "refs/heads/master", + Description: "Add support for branch description.\n\nEdit branch description: git branch --edit-description\n", } cfg.URLs["ssh://git@github.com/"] = &URL{ @@ -364,4 +368,5 @@ func (s *ConfigSuite) TestRemoveUrlOptions(c *C) { if strings.Contains(string(buf), "url") { c.Fatal("conifg should not contain any url sections") } + c.Assert(err, IsNil) } From efc74e7730b7cfd72ae66602815e6acd67b2c01a Mon Sep 17 00:00:00 2001 From: merlin Date: Mon, 8 Nov 2021 00:33:17 +0300 Subject: [PATCH 24/62] config: describe reason for custom quote functions --- config/branch.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/branch.go b/config/branch.go index a8fe0b5ab..652270a28 100644 --- a/config/branch.go +++ b/config/branch.go @@ -92,6 +92,13 @@ func (b *Branch) marshal() *format.Subsection { return b.raw } +// hack to trigger conditional quoting in the +// plumbing/format/config/Encoder.encodeOptions +// +// Current Encoder implementation uses Go %q format if value contains a backslash character, +// which is not consistent with reference git implementation. +// git just replaces newline characters with \n, while Encoder prints them directly. +// Until value quoting fix, we should escape description value by replacing newline characters with \n. func quoteDescription(desc string) string { return strings.ReplaceAll(desc, "\n", `\n`) } @@ -108,6 +115,9 @@ func (b *Branch) unmarshal(s *format.Subsection) error { return b.Validate() } +// hack to enable conditional quoting in the +// plumbing/format/config/Encoder.encodeOptions +// goto quoteDescription for details. func unquoteDescription(desc string) string { return strings.ReplaceAll(desc, `\n`, "\n") } From f92011d95f98f5deea4959c7d432704a4300d3a8 Mon Sep 17 00:00:00 2001 From: John Cai Date: Thu, 4 Nov 2021 15:02:00 -0400 Subject: [PATCH 25/62] simplified sparse checkout This is the initial logic to support a simple sparse checkout where directories to be included can be specified in CheckoutOptions. This change doesn't fully support the sparse patterns, nor does this change include the optimization to collapse flie entries in ithe index that are excluded via the sparse checkout directory patterns included under the parent directory. --- options.go | 2 ++ plumbing/format/index/index.go | 18 +++++++++++++ plumbing/object/treenoder.go | 4 +++ repository_test.go | 31 ++++++++++++++++++++++ utils/merkletrie/difftree.go | 29 ++++++++++++++++++-- utils/merkletrie/filesystem/node.go | 4 +++ utils/merkletrie/index/node.go | 7 ++++- utils/merkletrie/internal/fsnoder/dir.go | 4 +++ utils/merkletrie/internal/fsnoder/file.go | 4 +++ utils/merkletrie/noder/noder.go | 1 + utils/merkletrie/noder/noder_test.go | 1 + utils/merkletrie/noder/path.go | 8 ++++++ worktree.go | 25 +++++++++++++----- worktree_test.go | 32 +++++++++++++++++++++++ 14 files changed, 160 insertions(+), 10 deletions(-) diff --git a/options.go b/options.go index e54889f80..4b18ec1b2 100644 --- a/options.go +++ b/options.go @@ -291,6 +291,8 @@ type CheckoutOptions struct { // target branch. Force and Keep are mutually exclusive, should not be both // set to true. Keep bool + // SparseCheckoutDirectories + SparseCheckoutDirectories []string } // Validate validates the fields and sets the default values. diff --git a/plumbing/format/index/index.go b/plumbing/format/index/index.go index 649416a2b..f4c7647d3 100644 --- a/plumbing/format/index/index.go +++ b/plumbing/format/index/index.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "path/filepath" + "strings" "time" "github.com/go-git/go-git/v5/plumbing" @@ -211,3 +212,20 @@ type EndOfIndexEntry struct { // their contents). Hash plumbing.Hash } + +// SkipUnless applies patterns in the form of A, A/B, A/B/C +// to the index to prevent the files from being checked out +func (i *Index) SkipUnless(patterns []string) { + for _, e := range i.Entries { + var include bool + for _, pattern := range patterns { + if strings.HasPrefix(e.Name, pattern) { + include = true + break + } + } + if !include { + e.SkipWorktree = true + } + } +} diff --git a/plumbing/object/treenoder.go b/plumbing/object/treenoder.go index b4891b957..6e7b334cb 100644 --- a/plumbing/object/treenoder.go +++ b/plumbing/object/treenoder.go @@ -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 == "" } diff --git a/repository_test.go b/repository_test.go index 2bc5c902c..668828e45 100644 --- a/repository_test.go +++ b/repository_test.go @@ -210,6 +210,37 @@ func (s *RepositorySuite) TestCloneWithTags(c *C) { c.Assert(count, Equals, 3) } +func (s *RepositorySuite) TestCloneSparse(c *C) { + fs := memfs.New() + r, err := Clone(memory.NewStorage(), fs, &CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + sparseCheckoutDirectories := []string{"go", "json", "php"} + c.Assert(w.Checkout(&CheckoutOptions{ + Branch: "refs/heads/master", + SparseCheckoutDirectories: sparseCheckoutDirectories, + }), IsNil) + + fis, err := fs.ReadDir(".") + c.Assert(err, IsNil) + for _, fi := range fis { + c.Assert(fi.IsDir(), Equals, true) + var oneOfSparseCheckoutDirs bool + + for _, sparseCheckoutDirectory := range sparseCheckoutDirectories { + if strings.HasPrefix(fi.Name(), sparseCheckoutDirectory) { + oneOfSparseCheckoutDirs = true + } + } + c.Assert(oneOfSparseCheckoutDirs, Equals, true) + } +} + func (s *RepositorySuite) TestCreateRemoteAndRemote(c *C) { r, _ := Init(memory.NewStorage(), nil) remote, err := r.CreateRemote(&config.RemoteConfig{ diff --git a/utils/merkletrie/difftree.go b/utils/merkletrie/difftree.go index bd084b2ab..9f5145a26 100644 --- a/utils/merkletrie/difftree.go +++ b/utils/merkletrie/difftree.go @@ -304,13 +304,38 @@ func DiffTreeContext(ctx context.Context, fromTree, toTree noder.Noder, return nil, err } case onlyToRemains: - if err = ret.AddRecursiveInsert(to); err != nil { - return nil, err + if to.Skip() { + if err = ret.AddRecursiveDelete(to); err != nil { + return nil, err + } + } else { + if err = ret.AddRecursiveInsert(to); err != nil { + return nil, err + } } if err = ii.nextTo(); err != nil { return nil, err } case bothHaveNodes: + if from.Skip() { + if err = ret.AddRecursiveDelete(from); err != nil { + return nil, err + } + if err := ii.nextBoth(); err != nil { + return nil, err + } + break + } + if to.Skip() { + if err = ret.AddRecursiveDelete(to); err != nil { + return nil, err + } + if err := ii.nextBoth(); err != nil { + return nil, err + } + break + } + if err = diffNodes(&ret, ii); err != nil { return nil, err } diff --git a/utils/merkletrie/filesystem/node.go b/utils/merkletrie/filesystem/node.go index 2fc3d7a63..ad169ff4a 100644 --- a/utils/merkletrie/filesystem/node.go +++ b/utils/merkletrie/filesystem/node.go @@ -61,6 +61,10 @@ func (n *node) IsDir() bool { return n.isDir } +func (n *node) Skip() bool { + return false +} + func (n *node) Children() ([]noder.Noder, error) { if err := n.calculateChildren(); err != nil { return nil, err diff --git a/utils/merkletrie/index/node.go b/utils/merkletrie/index/node.go index d05b0c694..c1809f7ec 100644 --- a/utils/merkletrie/index/node.go +++ b/utils/merkletrie/index/node.go @@ -19,6 +19,7 @@ type node struct { entry *index.Entry children []noder.Noder isDir bool + skip bool } // NewRootNode returns the root node of a computed tree from a index.Index, @@ -39,7 +40,7 @@ func NewRootNode(idx *index.Index) noder.Noder { continue } - n := &node{path: fullpath} + n := &node{path: fullpath, skip: e.SkipWorktree} if fullpath == e.Name { n.entry = e } else { @@ -58,6 +59,10 @@ func (n *node) String() string { return n.path } +func (n *node) Skip() bool { + return n.skip +} + // Hash the hash of a filesystem is a 24-byte slice, is the result of // concatenating the computed plumbing.Hash of the file as a Blob and its // plumbing.FileMode; that way the difftree algorithm will detect changes in the diff --git a/utils/merkletrie/internal/fsnoder/dir.go b/utils/merkletrie/internal/fsnoder/dir.go index 20a2aeebb..3a4c2424e 100644 --- a/utils/merkletrie/internal/fsnoder/dir.go +++ b/utils/merkletrie/internal/fsnoder/dir.go @@ -112,6 +112,10 @@ func (d *dir) NumChildren() (int, error) { return len(d.children), nil } +func (d *dir) Skip() bool { + return false +} + const ( dirStartMark = '(' dirEndMark = ')' diff --git a/utils/merkletrie/internal/fsnoder/file.go b/utils/merkletrie/internal/fsnoder/file.go index d53643f1a..0bb908b7a 100644 --- a/utils/merkletrie/internal/fsnoder/file.go +++ b/utils/merkletrie/internal/fsnoder/file.go @@ -55,6 +55,10 @@ func (f *file) NumChildren() (int, error) { return 0, nil } +func (f *file) Skip() bool { + return false +} + const ( fileStartMark = '<' fileEndMark = '>' diff --git a/utils/merkletrie/noder/noder.go b/utils/merkletrie/noder/noder.go index d6b3de4ad..6d22b8c14 100644 --- a/utils/merkletrie/noder/noder.go +++ b/utils/merkletrie/noder/noder.go @@ -53,6 +53,7 @@ type Noder interface { // implement NumChildren in O(1) while Children is usually more // complex. NumChildren() (int, error) + Skip() bool } // NoChildren represents the children of a noder without children. diff --git a/utils/merkletrie/noder/noder_test.go b/utils/merkletrie/noder/noder_test.go index 5e014fe9b..ccebdc9ee 100644 --- a/utils/merkletrie/noder/noder_test.go +++ b/utils/merkletrie/noder/noder_test.go @@ -25,6 +25,7 @@ func (n noderMock) Name() string { return n.name } func (n noderMock) IsDir() bool { return n.isDir } func (n noderMock) Children() ([]Noder, error) { return n.children, nil } func (n noderMock) NumChildren() (int, error) { return len(n.children), nil } +func (n noderMock) Skip() bool { return false } // Returns a sequence with the noders 3, 2, and 1 from the // following diagram: diff --git a/utils/merkletrie/noder/path.go b/utils/merkletrie/noder/path.go index 1c7ef54ee..6c1d36332 100644 --- a/utils/merkletrie/noder/path.go +++ b/utils/merkletrie/noder/path.go @@ -15,6 +15,14 @@ import ( // not be used. type Path []Noder +func (p Path) Skip() bool { + if len(p) > 0 { + return p.Last().Skip() + } + + return false +} + // String returns the full path of the final noder as a string, using // "/" as the separator. func (p Path) String() string { diff --git a/worktree.go b/worktree.go index 362d10e65..c974aed24 100644 --- a/worktree.go +++ b/worktree.go @@ -11,6 +11,8 @@ import ( "strings" "sync" + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/util" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" @@ -20,9 +22,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" "github.com/go-git/go-git/v5/utils/merkletrie" - - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/util" ) var ( @@ -183,6 +182,10 @@ func (w *Worktree) Checkout(opts *CheckoutOptions) error { return err } + if len(opts.SparseCheckoutDirectories) > 0 { + return w.ResetSparsely(ro, opts.SparseCheckoutDirectories) + } + return w.Reset(ro) } func (w *Worktree) createBranch(opts *CheckoutOptions) error { @@ -263,8 +266,7 @@ func (w *Worktree) setHEADToBranch(branch plumbing.ReferenceName, commit plumbin return w.r.Storer.SetReference(head) } -// Reset the worktree to a specified state. -func (w *Worktree) Reset(opts *ResetOptions) error { +func (w *Worktree) ResetSparsely(opts *ResetOptions, dirs []string) error { if err := opts.Validate(w.r); err != nil { return err } @@ -294,7 +296,7 @@ func (w *Worktree) Reset(opts *ResetOptions) error { } if opts.Mode == MixedReset || opts.Mode == MergeReset || opts.Mode == HardReset { - if err := w.resetIndex(t); err != nil { + if err := w.resetIndex(t, dirs); err != nil { return err } } @@ -308,8 +310,17 @@ func (w *Worktree) Reset(opts *ResetOptions) error { return nil } -func (w *Worktree) resetIndex(t *object.Tree) error { +// Reset the worktree to a specified state. +func (w *Worktree) Reset(opts *ResetOptions) error { + return w.ResetSparsely(opts, nil) +} + +func (w *Worktree) resetIndex(t *object.Tree, dirs []string) error { idx, err := w.r.Storer.Index() + if len(dirs) > 0 { + idx.SkipUnless(dirs) + } + if err != nil { return err } diff --git a/worktree_test.go b/worktree_test.go index 79cbefd77..a8f318797 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -10,6 +10,7 @@ import ( "path/filepath" "regexp" "runtime" + "strings" "testing" "time" @@ -417,6 +418,37 @@ func (s *WorktreeSuite) TestCheckoutSymlink(c *C) { c.Assert(err, IsNil) } +func (s *WorktreeSuite) TestCheckoutSparse(c *C) { + fs := memfs.New() + r, err := Clone(memory.NewStorage(), fs, &CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + sparseCheckoutDirectories := []string{"go", "json", "php"} + c.Assert(w.Checkout(&CheckoutOptions{ + SparseCheckoutDirectories: sparseCheckoutDirectories, + }), IsNil) + + fis, err := fs.ReadDir("/") + c.Assert(err, IsNil) + + for _, fi := range fis { + c.Assert(fi.IsDir(), Equals, true) + var oneOfSparseCheckoutDirs bool + + for _, sparseCheckoutDirectory := range sparseCheckoutDirectories { + if strings.HasPrefix(fi.Name(), sparseCheckoutDirectory) { + oneOfSparseCheckoutDirs = true + } + } + c.Assert(oneOfSparseCheckoutDirs, Equals, true) + } +} + func (s *WorktreeSuite) TestFilenameNormalization(c *C) { if runtime.GOOS == "windows" { c.Skip("windows paths may contain non utf-8 sequences") From 7391aa4c244439c246b269ef3e946273aba7ca8b Mon Sep 17 00:00:00 2001 From: "paul.t" Date: Tue, 9 Nov 2021 15:16:55 +0000 Subject: [PATCH 26/62] add codecommit packfile for testing external ref resolution --- plumbing/format/packfile/parser_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plumbing/format/packfile/parser_test.go b/plumbing/format/packfile/parser_test.go index b0b4af82a..9c7e218f7 100644 --- a/plumbing/format/packfile/parser_test.go +++ b/plumbing/format/packfile/parser_test.go @@ -132,6 +132,20 @@ func (s *ParserSuite) TestThinPack(c *C) { } +func (s *ParserSuite) TestResolveExternalRefsInThinPack(c *C) { + f, err := os.Open("testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack") + c.Assert(err, IsNil) + + scanner := packfile.NewScanner(f) + + obs := new(testObserver) + parser, err := packfile.NewParser(scanner, obs) + c.Assert(err, IsNil) + + _, err = parser.Parse() + c.Assert(err, IsNil) +} + type observerObject struct { hash string otype plumbing.ObjectType From b0f5eb894deb6d6a1051d697f0809082abfad395 Mon Sep 17 00:00:00 2001 From: "paul.t" Date: Tue, 9 Nov 2021 15:19:12 +0000 Subject: [PATCH 27/62] include example codecommit pack file --- ...733763ae7ee6efcf452d373d6fff77424fb1dcc.pack | Bin 0 -> 42029 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 plumbing/format/packfile/testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack diff --git a/plumbing/format/packfile/testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack b/plumbing/format/packfile/testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack new file mode 100644 index 0000000000000000000000000000000000000000..4a57a61551e6b42c324b98cda3f69ee93f8e5b03 GIT binary patch literal 42029 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 NrtdF4Kf9qF`Y(#ikz)V= literal 0 HcmV?d00001 From 07a8bcc71afb5814c00c7a7b19c29b0c493a18fd Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sat, 27 Nov 2021 14:26:38 -0800 Subject: [PATCH 28/62] Remove unused variables/types/functions [staticcheck](https://staticcheck.io/) reported a number of unused fields, functions, types, and variables across the code. Where possible, use them (assert unchecked errors in tests, for example) and otherwise remove them. --- common_test.go | 4 +--- config/config_test.go | 1 + plumbing/format/packfile/fsobject.go | 15 --------------- plumbing/format/packfile/packfile_test.go | 17 ----------------- plumbing/format/packfile/parser.go | 1 - plumbing/object/patch.go | 4 ---- plumbing/protocol/packp/common.go | 1 - plumbing/protocol/packp/updreq_encode.go | 4 ---- plumbing/protocol/packp/uppackresp.go | 1 - plumbing/revlist/revlist_test.go | 6 ------ plumbing/transport/client/client_test.go | 5 ----- plumbing/transport/internal/common/common.go | 5 ----- plumbing/transport/ssh/common_test.go | 3 +-- remote.go | 2 +- remote_test.go | 2 +- storage/filesystem/dotgit/dotgit_test.go | 2 +- storage/filesystem/object_test.go | 2 +- utils/merkletrie/noder/noder_test.go | 14 -------------- worktree_commit_test.go | 5 +++-- worktree_test.go | 3 ++- 20 files changed, 12 insertions(+), 85 deletions(-) diff --git a/common_test.go b/common_test.go index b47f5bbff..c0c4009e2 100644 --- a/common_test.go +++ b/common_test.go @@ -7,7 +7,6 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/format/packfile" - "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/memory" @@ -25,8 +24,7 @@ type BaseSuite struct { fixtures.Suite Repository *Repository - backupProtocol transport.Transport - cache map[string]*Repository + cache map[string]*Repository } func (s *BaseSuite) SetUpSuite(c *C) { diff --git a/config/config_test.go b/config/config_test.go index 6f0242d96..5a51bb38c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -361,6 +361,7 @@ func (s *ConfigSuite) TestRemoveUrlOptions(c *C) { cfg.Remotes["alt"].URLs = []string{} buf, err = cfg.Marshal() + c.Assert(err, IsNil) if strings.Contains(string(buf), "url") { c.Fatal("conifg should not contain any url sections") } diff --git a/plumbing/format/packfile/fsobject.go b/plumbing/format/packfile/fsobject.go index a395d171c..238339daf 100644 --- a/plumbing/format/packfile/fsobject.go +++ b/plumbing/format/packfile/fsobject.go @@ -13,7 +13,6 @@ import ( // FSObject is an object from the packfile on the filesystem. type FSObject struct { hash plumbing.Hash - h *ObjectHeader offset int64 size int64 typ plumbing.ObjectType @@ -118,17 +117,3 @@ func (o *FSObject) Type() plumbing.ObjectType { func (o *FSObject) Writer() (io.WriteCloser, error) { return nil, nil } - -type objectReader struct { - io.ReadCloser - f billy.File -} - -func (r *objectReader) Close() error { - if err := r.ReadCloser.Close(); err != nil { - _ = r.f.Close() - return err - } - - return r.f.Close() -} diff --git a/plumbing/format/packfile/packfile_test.go b/plumbing/format/packfile/packfile_test.go index 6af88170b..2eb099df6 100644 --- a/plumbing/format/packfile/packfile_test.go +++ b/plumbing/format/packfile/packfile_test.go @@ -8,7 +8,6 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/idxfile" "github.com/go-git/go-git/v5/plumbing/format/packfile" - "github.com/go-git/go-git/v5/plumbing/storer" . "gopkg.in/check.v1" ) @@ -236,22 +235,6 @@ var expectedHashes = []string{ "7e59600739c96546163833214c36459e324bad0a", } -func assertObjects(c *C, s storer.EncodedObjectStorer, expects []string) { - i, err := s.IterEncodedObjects(plumbing.AnyObject) - c.Assert(err, IsNil) - - var count int - err = i.ForEach(func(plumbing.EncodedObject) error { count++; return nil }) - c.Assert(err, IsNil) - c.Assert(count, Equals, len(expects)) - - for _, exp := range expects { - obt, err := s.EncodedObject(plumbing.AnyObject, plumbing.NewHash(exp)) - c.Assert(err, IsNil) - c.Assert(obt.Hash().String(), Equals, exp) - } -} - func getIndexFromIdxFile(r io.Reader) idxfile.Index { idx := idxfile.NewMemoryIndex() if err := idxfile.NewDecoder(r).Decode(idx); err != nil { diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go index 4b5a5708c..ee5c28984 100644 --- a/plumbing/format/packfile/parser.go +++ b/plumbing/format/packfile/parser.go @@ -46,7 +46,6 @@ type Parser struct { oi []*objectInfo oiByHash map[plumbing.Hash]*objectInfo oiByOffset map[int64]*objectInfo - hashOffset map[plumbing.Hash]int64 checksum plumbing.Hash cache *cache.BufferLRU diff --git a/plumbing/object/patch.go b/plumbing/object/patch.go index 56b62c191..06bc35bbc 100644 --- a/plumbing/object/patch.go +++ b/plumbing/object/patch.go @@ -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 diff --git a/plumbing/protocol/packp/common.go b/plumbing/protocol/packp/common.go index ab07ac8f7..fef50a450 100644 --- a/plumbing/protocol/packp/common.go +++ b/plumbing/protocol/packp/common.go @@ -19,7 +19,6 @@ var ( // common sp = []byte(" ") eol = []byte("\n") - eq = []byte{'='} // advertised-refs null = []byte("\x00") diff --git a/plumbing/protocol/packp/updreq_encode.go b/plumbing/protocol/packp/updreq_encode.go index 08a819e15..1205cfaf1 100644 --- a/plumbing/protocol/packp/updreq_encode.go +++ b/plumbing/protocol/packp/updreq_encode.go @@ -9,10 +9,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" ) -var ( - zeroHashString = plumbing.ZeroHash.String() -) - // Encode writes the ReferenceUpdateRequest encoding to the stream. func (req *ReferenceUpdateRequest) Encode(w io.Writer) error { if err := req.validate(); err != nil { diff --git a/plumbing/protocol/packp/uppackresp.go b/plumbing/protocol/packp/uppackresp.go index a9a7192ea..26ae61ed8 100644 --- a/plumbing/protocol/packp/uppackresp.go +++ b/plumbing/protocol/packp/uppackresp.go @@ -24,7 +24,6 @@ type UploadPackResponse struct { r io.ReadCloser isShallow bool isMultiACK bool - isOk bool } // NewUploadPackResponse create a new UploadPackResponse instance, the request diff --git a/plumbing/revlist/revlist_test.go b/plumbing/revlist/revlist_test.go index a1ee504e8..9f2f93b53 100644 --- a/plumbing/revlist/revlist_test.go +++ b/plumbing/revlist/revlist_test.go @@ -55,12 +55,6 @@ func (s *RevListSuite) SetUpTest(c *C) { s.Storer = sto } -func (s *RevListSuite) commit(c *C, h plumbing.Hash) *object.Commit { - commit, err := object.GetCommit(s.Storer, h) - c.Assert(err, IsNil) - return commit -} - func (s *RevListSuite) TestRevListObjects_Submodules(c *C) { submodules := map[string]bool{ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5": true, diff --git a/plumbing/transport/client/client_test.go b/plumbing/transport/client/client_test.go index 9ebe113b1..92db525a5 100644 --- a/plumbing/transport/client/client_test.go +++ b/plumbing/transport/client/client_test.go @@ -1,7 +1,6 @@ package client import ( - "fmt" "net/http" "testing" @@ -68,7 +67,3 @@ func (*dummyClient) NewReceivePackSession(*transport.Endpoint, transport.AuthMet transport.ReceivePackSession, error) { return nil, nil } - -func typeAsString(v interface{}) string { - return fmt.Sprintf("%T", v) -} diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index fdb148f59..d0e9a2974 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -428,11 +428,6 @@ func isRepoNotFoundError(s string) bool { return false } -var ( - nak = []byte("NAK") - eol = []byte("\n") -) - // uploadPack implements the git-upload-pack protocol. func uploadPack(w io.WriteCloser, r io.Reader, req *packp.UploadPackRequest) error { // TODO support multi_ack mode diff --git a/plumbing/transport/ssh/common_test.go b/plumbing/transport/ssh/common_test.go index e04a9c5dc..6d634d532 100644 --- a/plumbing/transport/ssh/common_test.go +++ b/plumbing/transport/ssh/common_test.go @@ -7,7 +7,6 @@ import ( "github.com/kevinburke/ssh_config" "golang.org/x/crypto/ssh" - stdssh "golang.org/x/crypto/ssh" . "gopkg.in/check.v1" ) @@ -99,7 +98,7 @@ func (s *SuiteCommon) TestIssue70(c *C) { uploadPack.SetUpSuite(c) config := &ssh.ClientConfig{ - HostKeyCallback: stdssh.InsecureIgnoreHostKey(), + HostKeyCallback: ssh.InsecureIgnoreHostKey(), } r := &runner{ config: config, diff --git a/remote.go b/remote.go index 426bde928..d54693ead 100644 --- a/remote.go +++ b/remote.go @@ -256,7 +256,7 @@ func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs st return err } - for tag, _ := range tags { + for tag := range tags { tagObject, err := object.GetObject(r.s, tag.Hash()) var tagCommit *object.Commit if err != nil { diff --git a/remote_test.go b/remote_test.go index df07c082d..e41d80212 100644 --- a/remote_test.go +++ b/remote_test.go @@ -875,7 +875,7 @@ func (s *RemoteSuite) TestPushPrune(c *C) { "refs/remotes/origin/master": ref.Hash().String(), }) - ref, err = server.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true) + _, err = server.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true) c.Assert(err, Equals, plumbing.ErrReferenceNotFound) } diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go index 4c2ae941c..1db1a0490 100644 --- a/storage/filesystem/dotgit/dotgit_test.go +++ b/storage/filesystem/dotgit/dotgit_test.go @@ -653,7 +653,7 @@ func (s *SuiteDotGit) TestObject(c *C) { fs.MkdirAll(incomingDirPath, os.FileMode(0755)) fs.Create(incomingFilePath) - file, err = dir.Object(plumbing.NewHash(incomingHash)) + _, err = dir.Object(plumbing.NewHash(incomingHash)) c.Assert(err, IsNil) } diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go index 59b40d3c2..9c5e31fa5 100644 --- a/storage/filesystem/object_test.go +++ b/storage/filesystem/object_test.go @@ -386,7 +386,7 @@ func (s *FsSuite) TestGetFromObjectFileSharedCache(c *C) { c.Assert(err, IsNil) c.Assert(obj.Hash(), Equals, expected) - obj, err = o2.EncodedObject(plumbing.CommitObject, expected) + _, err = o2.EncodedObject(plumbing.CommitObject, expected) c.Assert(err, Equals, plumbing.ErrObjectNotFound) } diff --git a/utils/merkletrie/noder/noder_test.go b/utils/merkletrie/noder/noder_test.go index 5e014fe9b..110e465cd 100644 --- a/utils/merkletrie/noder/noder_test.go +++ b/utils/merkletrie/noder/noder_test.go @@ -57,20 +57,6 @@ func childrenFixture() []Noder { return []Noder{c1, c2} } -// Returns the same as nodersFixture but sorted by name, this is: "1", -// "2" and then "3". -func sortedNodersFixture() []Noder { - n1 := &noderMock{ - name: "1", - hash: []byte{0x00, 0x01, 0x02}, - isDir: true, - children: childrenFixture(), - } - n2 := &noderMock{name: "2"} - n3 := &noderMock{name: "3"} - return []Noder{n1, n2, n3} // the same as nodersFixture but sorted by name -} - // returns nodersFixture as the path of "1". func pathFixture() Path { return Path(nodersFixture()) diff --git a/worktree_commit_test.go b/worktree_commit_test.go index 65d4b695d..097c6e5d3 100644 --- a/worktree_commit_test.go +++ b/worktree_commit_test.go @@ -212,10 +212,10 @@ func (s *WorktreeSuite) TestCommitTreeSort(c *C) { defer clean() st := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) - r, err := Init(st, nil) + _, err := Init(st, nil) c.Assert(err, IsNil) - r, _ = Clone(memory.NewStorage(), memfs.New(), &CloneOptions{ + r, _ := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{ URL: fs.Root(), }) @@ -296,6 +296,7 @@ func (s *WorktreeSuite) TestJustStoreObjectsNotAlreadyStored(c *C) { All: true, Author: defaultSignature(), }) + c.Assert(err, IsNil) c.Assert(hash, Equals, plumbing.NewHash("97c0c5177e6ac57d10e8ea0017f2d39b91e2b364")) // Step 3: Check diff --git a/worktree_test.go b/worktree_test.go index 79cbefd77..97dcc3055 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -183,7 +183,7 @@ func (s *WorktreeSuite) TestPullInSingleBranch(c *C) { c.Assert(err, IsNil) c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") - branch, err = r.Reference("refs/remotes/foo/branch", false) + _, err = r.Reference("refs/remotes/foo/branch", false) c.Assert(err, NotNil) storage := r.Storer.(*memory.Storage) @@ -555,6 +555,7 @@ func (s *WorktreeSuite) TestCheckoutRelativePathSubmoduleInitialized(c *C) { // test submodule path modules, err := w.readGitmodulesFile() + c.Assert(err, IsNil) c.Assert(modules.Submodules["basic"].URL, Equals, "../basic.git") c.Assert(modules.Submodules["itself"].URL, Equals, "../submodule.git") From cde3a0d0318303df384d8441c2cc08ca645f8e6a Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sat, 27 Nov 2021 16:18:48 -0800 Subject: [PATCH 29/62] storage/transactional: Use correct config in test The `cfg` loaded from `cs.Config` was unused. It looks like this assertion intended to validate that, not `temporalCfg`, which was already checked above this block. --- storage/transactional/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/transactional/config_test.go b/storage/transactional/config_test.go index 1f3a572f4..34d7763f6 100644 --- a/storage/transactional/config_test.go +++ b/storage/transactional/config_test.go @@ -54,7 +54,7 @@ func (s *ConfigSuite) TestSetConfigTemporal(c *C) { cfg, err = cs.Config() c.Assert(err, IsNil) - c.Assert(temporalCfg.Core.Worktree, Equals, "bar") + c.Assert(cfg.Core.Worktree, Equals, "bar") } func (s *ConfigSuite) TestCommit(c *C) { From 977668a25d210e8985a55ede0f2382a48b8ad2e2 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sat, 27 Nov 2021 14:32:18 -0800 Subject: [PATCH 30/62] transactional/ReferenceStorage: Drop packRefs field The packRefs field is unused. It is assigned to true from the `PackRefs()` method, but because the method is not on the pointer type, the assignment has no effect. var st ReferenceStorage fmt.Println(st.packRefs) // false st.PackRefs() fmt.Println(st.packRefs) // false Delete the unused field. --- storage/transactional/reference.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/storage/transactional/reference.go b/storage/transactional/reference.go index 3b009e2e6..1c0930755 100644 --- a/storage/transactional/reference.go +++ b/storage/transactional/reference.go @@ -15,9 +15,6 @@ type ReferenceStorage struct { // commit is requested, the entries are added when RemoveReference is called // and deleted if SetReference is called. deleted map[plumbing.ReferenceName]struct{} - // packRefs if true PackRefs is going to be called in the based storer when - // commit is called. - packRefs bool } // NewReferenceStorage returns a new ReferenceStorer based on a base storer and @@ -108,7 +105,6 @@ func (r ReferenceStorage) CountLooseRefs() (int, error) { // PackRefs honors the storer.ReferenceStorer interface. func (r ReferenceStorage) PackRefs() error { - r.packRefs = true return nil } From 557a1fdcaabd51899b9213175762ed9603409985 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sat, 27 Nov 2021 16:38:20 -0800 Subject: [PATCH 31/62] remote/addReachableTags: Remove guard before delete The membership check before attempting to `delete` from the `tags` map is unnecessary because the operation is a no-op if the item does not already exist in the map. --- remote.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/remote.go b/remote.go index d54693ead..9e710a3c5 100644 --- a/remote.go +++ b/remote.go @@ -247,10 +247,7 @@ func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs st // remove any that are already on the remote if err := remoteRefIter.ForEach(func(reference *plumbing.Reference) error { - if _, ok := tags[*reference]; ok { - delete(tags, *reference) - } - + delete(tags, *reference) return nil }); err != nil { return err From fe308ea0d0ff6c31f2a218f8b47d8ace124ea679 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sat, 27 Nov 2021 16:51:55 -0800 Subject: [PATCH 32/62] packp: Actions should have type Action Per the [Go Spec](https://go.dev/ref/spec#Constant_declarations), the following yields the type `Action` for `Bar` and `Baz` only if there is no `=`. const ( Foo Action = ... Bar Baz ) The following has the type `Action` for the first item, but not the rest. Those are untyped constants of the corresponding type. const ( Foo Action = ... Bar = ... Baz = ... ) This means that `packp.{Update, Delete, Invalid}` are currently untyped string constants, and not `Action` constants as was intended here. This change fixes these. --- plumbing/protocol/packp/updreq.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go index 46ad6fdc9..5dbd8ac73 100644 --- a/plumbing/protocol/packp/updreq.go +++ b/plumbing/protocol/packp/updreq.go @@ -87,9 +87,9 @@ type Action string const ( Create Action = "create" - Update = "update" - Delete = "delete" - Invalid = "invalid" + Update Action = "update" + Delete Action = "delete" + Invalid Action = "invalid" ) type Command struct { From 0dcebfb72bbdaf01554f938402e699d67937c5a0 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sat, 4 Dec 2021 15:16:09 -0800 Subject: [PATCH 33/62] error strings: Don't capitalize, use periods, or newlines Per [Go Code Review Comments][1], > Error strings should not be capitalized (unless beginning with proper > nouns or acronyms) or end with punctuation staticcheck's [ST1005][2] also complains about these. For example, ``` object_walker.go:63:10: error strings should not be capitalized (ST1005) object_walker.go:101:10: error strings should not be capitalized (ST1005) object_walker.go:101:10: error strings should not end with punctuation or a newline (ST1005) plumbing/format/commitgraph/file.go:17:26: error strings should not be capitalized (ST1005) ``` This fixes all instances of this issue reported by staticcheck. [1]: https://github.com/golang/go/wiki/CodeReviewComments#error-strings [2]: https://staticcheck.io/docs/checks/#ST1005 --- config/config.go | 2 +- object_walker.go | 4 ++-- plumbing/format/commitgraph/file.go | 6 +++--- plumbing/format/gitattributes/attributes.go | 2 +- plumbing/format/idxfile/decoder.go | 4 ++-- plumbing/object/change_adaptor.go | 4 ++-- prune.go | 2 +- remote.go | 4 ++-- repository.go | 4 ++-- storage/filesystem/dotgit/reader.go | 2 +- storage/memory/storage.go | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/config/config.go b/config/config.go index 1aee25a4c..1462c0ce1 100644 --- a/config/config.go +++ b/config/config.go @@ -150,7 +150,7 @@ func ReadConfig(r io.Reader) (*Config, error) { // 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.") + return nil, fmt.Errorf("LocalScope should be read from the a ConfigStorer") } files, err := Paths(scope) diff --git a/object_walker.go b/object_walker.go index 3fcdd2999..3a537bd80 100644 --- a/object_walker.go +++ b/object_walker.go @@ -60,7 +60,7 @@ func (p *objectWalker) walkObjectTree(hash plumbing.Hash) error { // Fetch the object. obj, err := object.GetObject(p.Storer, hash) if err != nil { - return fmt.Errorf("Getting object %s failed: %v", hash, err) + return fmt.Errorf("getting object %s failed: %v", hash, err) } // Walk all children depending on object type. switch obj := obj.(type) { @@ -98,7 +98,7 @@ func (p *objectWalker) walkObjectTree(hash plumbing.Hash) error { return p.walkObjectTree(obj.Target) default: // Error out on unhandled object types. - return fmt.Errorf("Unknown object %X %s %T\n", obj.ID(), obj.Type(), obj) + return fmt.Errorf("unknown object %X %s %T", obj.ID(), obj.Type(), obj) } return nil } diff --git a/plumbing/format/commitgraph/file.go b/plumbing/format/commitgraph/file.go index 0ce719823..1d25238f5 100644 --- a/plumbing/format/commitgraph/file.go +++ b/plumbing/format/commitgraph/file.go @@ -14,14 +14,14 @@ import ( var ( // ErrUnsupportedVersion is returned by OpenFileIndex when the commit graph // file version is not supported. - ErrUnsupportedVersion = errors.New("Unsupported version") + ErrUnsupportedVersion = errors.New("unsupported version") // ErrUnsupportedHash is returned by OpenFileIndex when the commit graph // hash function is not supported. Currently only SHA-1 is defined and // supported - ErrUnsupportedHash = errors.New("Unsupported hash algorithm") + ErrUnsupportedHash = errors.New("unsupported hash algorithm") // ErrMalformedCommitGraphFile is returned by OpenFileIndex when the commit // graph file is corrupted. - ErrMalformedCommitGraphFile = errors.New("Malformed commit graph file") + ErrMalformedCommitGraphFile = errors.New("malformed commit graph file") commitFileSignature = []byte{'C', 'G', 'P', 'H'} oidFanoutSignature = []byte{'O', 'I', 'D', 'F'} diff --git a/plumbing/format/gitattributes/attributes.go b/plumbing/format/gitattributes/attributes.go index d13c2a903..329e66731 100644 --- a/plumbing/format/gitattributes/attributes.go +++ b/plumbing/format/gitattributes/attributes.go @@ -15,7 +15,7 @@ const ( var ( ErrMacroNotAllowed = errors.New("macro not allowed") - ErrInvalidAttributeName = errors.New("Invalid attribute name") + ErrInvalidAttributeName = errors.New("invalid attribute name") ) type MatchAttribute struct { diff --git a/plumbing/format/idxfile/decoder.go b/plumbing/format/idxfile/decoder.go index 7768bd650..51a390489 100644 --- a/plumbing/format/idxfile/decoder.go +++ b/plumbing/format/idxfile/decoder.go @@ -12,9 +12,9 @@ import ( var ( // ErrUnsupportedVersion is returned by Decode when the idx file version // is not supported. - ErrUnsupportedVersion = errors.New("Unsupported version") + ErrUnsupportedVersion = errors.New("unsupported version") // ErrMalformedIdxFile is returned by Decode when the idx file is corrupted. - ErrMalformedIdxFile = errors.New("Malformed IDX file") + ErrMalformedIdxFile = errors.New("malformed IDX file") ) const ( diff --git a/plumbing/object/change_adaptor.go b/plumbing/object/change_adaptor.go index f70118828..b96ee84d9 100644 --- a/plumbing/object/change_adaptor.go +++ b/plumbing/object/change_adaptor.go @@ -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 diff --git a/prune.go b/prune.go index cc5907a14..8e35b994e 100644 --- a/prune.go +++ b/prune.go @@ -17,7 +17,7 @@ type PruneOptions struct { Handler PruneHandler } -var ErrLooseObjectsNotSupported = errors.New("Loose objects not supported") +var ErrLooseObjectsNotSupported = errors.New("loose objects not supported") // DeleteObject deletes an object from a repository. // The type conveniently matches PruneHandler. diff --git a/remote.go b/remote.go index 426bde928..dcb24b4bc 100644 --- a/remote.go +++ b/remote.go @@ -260,7 +260,7 @@ func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs st tagObject, err := object.GetObject(r.s, tag.Hash()) var tagCommit *object.Commit if err != nil { - return fmt.Errorf("get tag object: %w\n", err) + return fmt.Errorf("get tag object: %w", err) } if tagObject.Type() != plumbing.TagObject { @@ -274,7 +274,7 @@ func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs st tagCommit, err = object.GetCommit(r.s, annotatedTag.Target) if err != nil { - return fmt.Errorf("get annotated tag commit: %w\n", err) + return fmt.Errorf("get annotated tag commit: %w", err) } // only include tags that are reachable from one of the refs diff --git a/repository.go b/repository.go index d3fbf9759..e8eb53f70 100644 --- a/repository.go +++ b/repository.go @@ -56,7 +56,7 @@ var ( ErrWorktreeNotProvided = errors.New("worktree should be provided") ErrIsBareRepository = errors.New("worktree not available in a bare repository") ErrUnableToResolveCommit = errors.New("unable to resolve commit") - ErrPackedObjectsNotSupported = errors.New("Packed objects not supported") + ErrPackedObjectsNotSupported = errors.New("packed objects not supported") ) // Repository represents a git repository @@ -1547,7 +1547,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err } if c == nil { - return &plumbing.ZeroHash, fmt.Errorf(`No commit message match regexp : "%s"`, re.String()) + return &plumbing.ZeroHash, fmt.Errorf("no commit message match regexp: %q", re.String()) } commit = c diff --git a/storage/filesystem/dotgit/reader.go b/storage/filesystem/dotgit/reader.go index a82ac94eb..975f92ac6 100644 --- a/storage/filesystem/dotgit/reader.go +++ b/storage/filesystem/dotgit/reader.go @@ -66,7 +66,7 @@ func (e *EncodedObject) Size() int64 { func (e *EncodedObject) SetSize(int64) {} func (e *EncodedObject) Writer() (io.WriteCloser, error) { - return nil, fmt.Errorf("Not supported") + return nil, fmt.Errorf("not supported") } func NewEncodedObject(dir *DotGit, h plumbing.Hash, t plumbing.ObjectType, size int64) *EncodedObject { diff --git a/storage/memory/storage.go b/storage/memory/storage.go index a8e56697b..ef6a44551 100644 --- a/storage/memory/storage.go +++ b/storage/memory/storage.go @@ -193,7 +193,7 @@ func (o *ObjectStorage) DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) er return nil } -var errNotSupported = fmt.Errorf("Not supported") +var errNotSupported = fmt.Errorf("not supported") func (o *ObjectStorage) LooseObjectTime(hash plumbing.Hash) (time.Time, error) { return time.Time{}, errNotSupported From f438ca3483c785f8649f6dbd96f1a1db3c6a2eaa Mon Sep 17 00:00:00 2001 From: NeP <93044008+nep-1@users.noreply.github.com> Date: Fri, 10 Dec 2021 13:39:50 +0800 Subject: [PATCH 34/62] examples: remote fix typo (#408) --- _examples/remotes/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/remotes/main.go b/_examples/remotes/main.go index b1a91a9ef..d09957eae 100644 --- a/_examples/remotes/main.go +++ b/_examples/remotes/main.go @@ -33,7 +33,7 @@ func main() { CheckIfError(err) // List remotes from a repository - Info("git remotes -v") + Info("git remote -v") list, err := r.Remotes() CheckIfError(err) From 1a8f803f55879b5d0cb40e88cb5981d64555c1aa Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Thu, 9 Dec 2021 21:42:38 -0800 Subject: [PATCH 35/62] storage: filesystem, switch from os.SEEK_* to io.Seek* (#421) The `os.SEEK_*` constants have been deprecated since Go 1.7. It is now recommended to use the equivalent `io.Seek*` constants. Switch `os.SEEK_CUR` to `io.SeekCurrent`, and `os.SEEK_SET` to `io.SeekStart`. --- storage/filesystem/dotgit/dotgit_test.go | 7 ++++--- storage/filesystem/object_test.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go index 4c2ae941c..1a09fde74 100644 --- a/storage/filesystem/dotgit/dotgit_test.go +++ b/storage/filesystem/dotgit/dotgit_test.go @@ -3,6 +3,7 @@ package dotgit import ( "bufio" "encoding/hex" + "io" "io/ioutil" "os" "path/filepath" @@ -510,13 +511,13 @@ func (s *SuiteDotGit) TestObjectPackWithKeepDescriptors(c *C) { c.Assert(filepath.Ext(pack.Name()), Equals, ".pack") // Move to an specific offset - pack.Seek(42, os.SEEK_SET) + pack.Seek(42, io.SeekStart) pack2, err := dir.ObjectPack(plumbing.NewHash(f.PackfileHash)) c.Assert(err, IsNil) // If the file is the same the offset should be the same - offset, err := pack2.Seek(0, os.SEEK_CUR) + offset, err := pack2.Seek(0, io.SeekCurrent) c.Assert(err, IsNil) c.Assert(offset, Equals, int64(42)) @@ -527,7 +528,7 @@ func (s *SuiteDotGit) TestObjectPackWithKeepDescriptors(c *C) { c.Assert(err, IsNil) // If the file is opened again its offset should be 0 - offset, err = pack2.Seek(0, os.SEEK_CUR) + offset, err = pack2.Seek(0, io.SeekCurrent) c.Assert(err, IsNil) c.Assert(offset, Equals, int64(0)) diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go index 59b40d3c2..1c3267b68 100644 --- a/storage/filesystem/object_test.go +++ b/storage/filesystem/object_test.go @@ -71,7 +71,7 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) { pack1, err := dg.ObjectPack(packfiles[0]) c.Assert(err, IsNil) - pack1.Seek(42, os.SEEK_SET) + pack1.Seek(42, io.SeekStart) err = o.Close() c.Assert(err, IsNil) @@ -79,7 +79,7 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) { pack2, err := dg.ObjectPack(packfiles[0]) c.Assert(err, IsNil) - offset, err := pack2.Seek(0, os.SEEK_CUR) + offset, err := pack2.Seek(0, io.SeekCurrent) c.Assert(err, IsNil) c.Assert(offset, Equals, int64(0)) From 4ccea5bb8d8c12cb31dba22f36864ffb91529559 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Fri, 10 Dec 2021 01:03:47 -0800 Subject: [PATCH 36/62] Fix repository_test The test verifies the exact error message. --- repository_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repository_test.go b/repository_test.go index 2bc5c902c..39dac1436 100644 --- a/repository_test.go +++ b/repository_test.go @@ -2756,7 +2756,7 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) { datas := map[string]string{ "efs/heads/master~": "reference not found", "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`, - "HEAD^{/whatever}": `No commit message match regexp : "whatever"`, + "HEAD^{/whatever}": `no commit message match regexp: "whatever"`, "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83": "reference not found", } From bf57a4c6a53423d92aeeadf1bc02877cc80459d4 Mon Sep 17 00:00:00 2001 From: "paul.t" Date: Wed, 5 Jan 2022 06:54:15 +0000 Subject: [PATCH 37/62] remove packfile and align to test fixtures --- .gitignore | 1 + go.mod | 2 +- go.sum | 7 ++++--- plumbing/format/packfile/parser_test.go | 5 ++--- ...733763ae7ee6efcf452d373d6fff77424fb1dcc.pack | Bin 42029 -> 0 bytes 5 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 plumbing/format/packfile/testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack diff --git a/.gitignore b/.gitignore index 038dd9f1e..e8d25f548 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ coverage.out *~ coverage.txt profile.out +.tmp/ \ No newline at end of file diff --git a/go.mod b/go.mod index ccb8facd1..512cc59ee 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gliderlabs/ssh v0.2.2 github.com/go-git/gcfg v1.5.0 github.com/go-git/go-billy/v5 v5.3.1 - github.com/go-git/go-git-fixtures/v4 v4.2.1 + github.com/go-git/go-git-fixtures/v4 v4.3.1 github.com/google/go-cmp v0.3.0 github.com/imdario/mergo v0.3.12 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 diff --git a/go.sum b/go.sum index ab212c592..c4d7302b0 100644 --- a/go.sum +++ b/go.sum @@ -20,11 +20,12 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 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/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git-fixtures/v4 v4.3.0 h1:OAiwhtOsTVVUvVSLzwSO9PCCo/KPxGfn5aC3BV6d6TY= +github.com/go-git/go-git-fixtures/v4 v4.3.0/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= +github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= +github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= diff --git a/plumbing/format/packfile/parser_test.go b/plumbing/format/packfile/parser_test.go index 9c7e218f7..09f3f9704 100644 --- a/plumbing/format/packfile/parser_test.go +++ b/plumbing/format/packfile/parser_test.go @@ -133,10 +133,9 @@ func (s *ParserSuite) TestThinPack(c *C) { } func (s *ParserSuite) TestResolveExternalRefsInThinPack(c *C) { - f, err := os.Open("testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack") - c.Assert(err, IsNil) + extRefsThinPack := fixtures.ByTag("codecommit").One() - scanner := packfile.NewScanner(f) + scanner := packfile.NewScanner(extRefsThinPack.Packfile()) obs := new(testObserver) parser, err := packfile.NewParser(scanner, obs) diff --git a/plumbing/format/packfile/testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack b/plumbing/format/packfile/testdata/pack-9733763ae7ee6efcf452d373d6fff77424fb1dcc.pack deleted file mode 100644 index 4a57a61551e6b42c324b98cda3f69ee93f8e5b03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42029 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 NrtdF4Kf9qF`Y(#ikz)V= From 1b36bebee9fa72b96e13ced3e18132644d34e6be Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 19 Jan 2022 11:38:59 -0300 Subject: [PATCH 38/62] plumbing: protocol/pakp, update agent Signed-off-by: Carlos A Becker --- plumbing/protocol/packp/capability/capability.go | 2 +- plumbing/protocol/packp/ulreq_test.go | 2 +- plumbing/protocol/packp/updreq_test.go | 4 ++-- plumbing/protocol/packp/uppackreq_test.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plumbing/protocol/packp/capability/capability.go b/plumbing/protocol/packp/capability/capability.go index 8d6a56f53..871441238 100644 --- a/plumbing/protocol/packp/capability/capability.go +++ b/plumbing/protocol/packp/capability/capability.go @@ -238,7 +238,7 @@ const ( Filter Capability = "filter" ) -const DefaultAgent = "go-git/4.x" +const DefaultAgent = "go-git/5.x" var known = map[Capability]bool{ MultiACK: true, MultiACKDetailed: true, NoDone: true, ThinPack: true, diff --git a/plumbing/protocol/packp/ulreq_test.go b/plumbing/protocol/packp/ulreq_test.go index 0b3b61653..2797a4ea5 100644 --- a/plumbing/protocol/packp/ulreq_test.go +++ b/plumbing/protocol/packp/ulreq_test.go @@ -25,7 +25,7 @@ func (s *UlReqSuite) TestNewUploadRequestFromCapabilities(c *C) { r := NewUploadRequestFromCapabilities(cap) c.Assert(r.Capabilities.String(), Equals, - "multi_ack_detailed side-band-64k thin-pack ofs-delta agent=go-git/4.x", + "multi_ack_detailed side-band-64k thin-pack ofs-delta agent=go-git/5.x", ) } diff --git a/plumbing/protocol/packp/updreq_test.go b/plumbing/protocol/packp/updreq_test.go index c4ccbaf64..80e03fbe7 100644 --- a/plumbing/protocol/packp/updreq_test.go +++ b/plumbing/protocol/packp/updreq_test.go @@ -23,14 +23,14 @@ func (s *UpdReqSuite) TestNewReferenceUpdateRequestFromCapabilities(c *C) { r := NewReferenceUpdateRequestFromCapabilities(cap) c.Assert(r.Capabilities.String(), Equals, - "agent=go-git/4.x report-status", + "agent=go-git/5.x report-status", ) cap = capability.NewList() cap.Set(capability.Agent, "foo") r = NewReferenceUpdateRequestFromCapabilities(cap) - c.Assert(r.Capabilities.String(), Equals, "agent=go-git/4.x") + c.Assert(r.Capabilities.String(), Equals, "agent=go-git/5.x") cap = capability.NewList() diff --git a/plumbing/protocol/packp/uppackreq_test.go b/plumbing/protocol/packp/uppackreq_test.go index f723e3cc7..5a6eb2cc8 100644 --- a/plumbing/protocol/packp/uppackreq_test.go +++ b/plumbing/protocol/packp/uppackreq_test.go @@ -18,7 +18,7 @@ func (s *UploadPackRequestSuite) TestNewUploadPackRequestFromCapabilities(c *C) cap.Set(capability.Agent, "foo") r := NewUploadPackRequestFromCapabilities(cap) - c.Assert(r.Capabilities.String(), Equals, "agent=go-git/4.x") + c.Assert(r.Capabilities.String(), Equals, "agent=go-git/5.x") } func (s *UploadPackRequestSuite) TestIsEmpty(c *C) { From 935af59cf64fbe49eb8baba9fe80e6b236daf593 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 19 Jan 2022 15:51:13 +0100 Subject: [PATCH 39/62] Repository: don't crash accessing invalid pathinfo (#443) When fs.Stat returns an error, pathinfo may be nil. In such situations the only safe response seems to be to return the error to the caller. Without this fix, accessing pathinfo.IsDir() below would lead to a crash dereferencing a nil pointer. This crash can be reproduced by trying to initialize a Git repo with an invalid path name. Also see: https://github.com/muesli/gitty/issues/36 --- repository.go | 3 +++ repository_test.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/repository.go b/repository.go index e8eb53f70..7292df627 100644 --- a/repository.go +++ b/repository.go @@ -280,6 +280,9 @@ func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, pathinfo, err := fs.Stat("/") if !os.IsNotExist(err) { + if pathinfo == nil { + return nil, nil, err + } if !pathinfo.IsDir() && detect { fs = osfs.New(filepath.Dir(path)) } diff --git a/repository_test.go b/repository_test.go index e284df8d6..7a9db151d 100644 --- a/repository_test.go +++ b/repository_test.go @@ -2948,6 +2948,11 @@ func (s *RepositorySuite) TestBrokenMultipleShallowFetch(c *C) { c.Assert(err, IsNil) } +func (s *RepositorySuite) TestDotGitToOSFilesystemsInvalidPath(c *C) { + _, _, err := dotGitToOSFilesystems("\000", false) + c.Assert(err, NotNil) +} + func BenchmarkObjects(b *testing.B) { defer fixtures.Clean() From b333364cf3e8cfb13c9803d718f56291d7520fbb Mon Sep 17 00:00:00 2001 From: Michael Henriksen Date: Sat, 19 Feb 2022 15:12:42 +0100 Subject: [PATCH 40/62] revision: fix endless looping in revision parser Fixes a bug in the revision parser which caused an endless loop when parsing revisions with opening braces "{" but no closing braces "}". Example bad revisions: - ^{/ - ~@{ - @@{{0 --- internal/revision/parser.go | 4 ++++ internal/revision/parser_test.go | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/revision/parser.go b/internal/revision/parser.go index 8facf17ff..8a2a7190e 100644 --- a/internal/revision/parser.go +++ b/internal/revision/parser.go @@ -322,6 +322,8 @@ func (p *Parser) parseAt() (Revisioner, error) { } return AtDate{t}, nil + case tok == eof: + return nil, &ErrInvalidRevision{s: `missing "}" in @{} structure`} default: date += lit } @@ -424,6 +426,8 @@ func (p *Parser) parseCaretBraces() (Revisioner, error) { p.unscan() case tok != slash && start: return nil, &ErrInvalidRevision{fmt.Sprintf(`"%s" is not a valid revision suffix brace component`, lit)} + case tok == eof: + return nil, &ErrInvalidRevision{s: `missing "}" in ^{} structure`} case tok != cbrace: p.unscan() re += lit diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 98403cc23..3a77b2f11 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -183,7 +183,7 @@ func (s *ParserSuite) TestParseWithValidExpression(c *C) { } } -func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { +func (s *ParserSuite) TestParseWithInvalidExpression(c *C) { datas := map[string]error{ "..": &ErrInvalidRevision{`must not start with "."`}, "master^1master": &ErrInvalidRevision{`reference must be defined once at the beginning`}, @@ -198,6 +198,9 @@ func (s *ParserSuite) TestParseWithUnValidExpression(c *C) { "~1": &ErrInvalidRevision{`"~" or "^" statement must have a reference defined at the beginning`}, "master:/test": &ErrInvalidRevision{`":" statement is not valid, could be : :/`}, "master:0:README": &ErrInvalidRevision{`":" statement is not valid, could be : ::`}, + "^{/": &ErrInvalidRevision{`missing "}" in ^{} structure`}, + "~@{": &ErrInvalidRevision{`missing "}" in @{} structure`}, + "@@{{0": &ErrInvalidRevision{`missing "}" in @{} structure`}, } for s, e := range datas { @@ -230,7 +233,7 @@ func (s *ParserSuite) TestParseAtWithValidExpression(c *C) { } } -func (s *ParserSuite) TestParseAtWithUnValidExpression(c *C) { +func (s *ParserSuite) TestParseAtWithInvalidExpression(c *C) { datas := map[string]error{ "{test}": &ErrInvalidRevision{`wrong date "test" must fit ISO-8601 format : 2006-01-02T15:04:05Z`}, "{-1": &ErrInvalidRevision{`missing "}" in @{-n} structure`}, From 1115cb6a84ea40ae68962cb82a4bc2dbb44e0f4c Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Wed, 30 Mar 2022 12:32:26 +0200 Subject: [PATCH 41/62] plumbing: object, rename calculation uses too much memory The size of the similarity matrix is not limited and can be quite big when lots of files are deleted and added in a commit. Signed-off-by: Javi Fontan --- plumbing/object/rename.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plumbing/object/rename.go b/plumbing/object/rename.go index 7fed72c2f..0394613ff 100644 --- a/plumbing/object/rename.go +++ b/plumbing/object/rename.go @@ -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) From 4f916225cb2f5f96c4f046b139d2a597387b8f8e Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Sat, 9 Apr 2022 01:23:34 +0200 Subject: [PATCH 42/62] Worktree: use syscall.Timespec.Unix #437 Use the syscall method instead of repeating the type conversions for the syscall.Stat_t Atim/Atimespec/Ctim members. --- worktree_bsd.go | 2 +- worktree_linux.go | 2 +- worktree_unix_other.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/worktree_bsd.go b/worktree_bsd.go index d4ea32758..d4682eb83 100644 --- a/worktree_bsd.go +++ b/worktree_bsd.go @@ -12,7 +12,7 @@ import ( func init() { fillSystemInfo = func(e *index.Entry, sys interface{}) { if os, ok := sys.(*syscall.Stat_t); ok { - e.CreatedAt = time.Unix(int64(os.Atimespec.Sec), int64(os.Atimespec.Nsec)) + e.CreatedAt = time.Unix(os.Atimespec.Unix()) e.Dev = uint32(os.Dev) e.Inode = uint32(os.Ino) e.GID = os.Gid diff --git a/worktree_linux.go b/worktree_linux.go index cf0db2524..6fcace2f9 100644 --- a/worktree_linux.go +++ b/worktree_linux.go @@ -12,7 +12,7 @@ import ( func init() { fillSystemInfo = func(e *index.Entry, sys interface{}) { if os, ok := sys.(*syscall.Stat_t); ok { - e.CreatedAt = time.Unix(int64(os.Ctim.Sec), int64(os.Ctim.Nsec)) + e.CreatedAt = time.Unix(os.Ctim.Unix()) e.Dev = uint32(os.Dev) e.Inode = uint32(os.Ino) e.GID = os.Gid diff --git a/worktree_unix_other.go b/worktree_unix_other.go index f45966be9..5b16e70b7 100644 --- a/worktree_unix_other.go +++ b/worktree_unix_other.go @@ -12,7 +12,7 @@ import ( func init() { fillSystemInfo = func(e *index.Entry, sys interface{}) { if os, ok := sys.(*syscall.Stat_t); ok { - e.CreatedAt = time.Unix(int64(os.Atim.Sec), int64(os.Atim.Nsec)) + e.CreatedAt = time.Unix(os.Atim.Unix()) e.Dev = uint32(os.Dev) e.Inode = uint32(os.Ino) e.GID = os.Gid From 69aa78ab169e8fa5d96561462b0a07aa5030bad6 Mon Sep 17 00:00:00 2001 From: Tyler Christensen Date: Sat, 30 Apr 2022 16:06:10 -0600 Subject: [PATCH 43/62] plumbing: packp, Avoid duplicate encoding when overriding a Capability value. (#521) Previously, calling `Set($CAPABILITY, ...)` on a `capability.List` where `$CAPABILITY` was already present would correctly replace the existing value of that capability, but would also result in that capability being listed twice in the internal `l.sort` slice. This manifested publicly when the `List` was encoded as the same capability appearing twice with the same value in the encoded output. --- plumbing/protocol/packp/capability/list.go | 4 +++- plumbing/protocol/packp/capability/list_test.go | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/plumbing/protocol/packp/capability/list.go b/plumbing/protocol/packp/capability/list.go index f41ec799c..553d81cbe 100644 --- a/plumbing/protocol/packp/capability/list.go +++ b/plumbing/protocol/packp/capability/list.go @@ -86,7 +86,9 @@ func (l *List) Get(capability Capability) []string { // Set sets a capability removing the previous values func (l *List) Set(capability Capability, values ...string) error { - delete(l.m, capability) + if _, ok := l.m[capability]; ok { + l.m[capability].Values = l.m[capability].Values[:0] + } return l.Add(capability, values...) } diff --git a/plumbing/protocol/packp/capability/list_test.go b/plumbing/protocol/packp/capability/list_test.go index 61b0b13be..71181cbc9 100644 --- a/plumbing/protocol/packp/capability/list_test.go +++ b/plumbing/protocol/packp/capability/list_test.go @@ -122,6 +122,17 @@ func (s *SuiteCapabilities) TestSetEmpty(c *check.C) { c.Assert(cap.Get(Agent), check.HasLen, 1) } +func (s *SuiteCapabilities) TestSetDuplicate(c *check.C) { + cap := NewList() + err := cap.Set(Agent, "baz") + c.Assert(err, check.IsNil) + + err = cap.Set(Agent, "bar") + c.Assert(err, check.IsNil) + + c.Assert(cap.String(), check.Equals, "agent=bar") +} + func (s *SuiteCapabilities) TestGetEmpty(c *check.C) { cap := NewList() c.Assert(cap.Get(Agent), check.HasLen, 0) From bc1f419cebcf7505db31149fa459e9e3f8260e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 26 May 2022 16:24:02 +0100 Subject: [PATCH 44/62] all: replace go-homedir with os.UserHomeDir Added in Go 1.12, this means we need one less dependency. --- config/config.go | 3 +-- go.mod | 1 - go.sum | 4 ---- plumbing/transport/ssh/auth_method.go | 11 ++++++----- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/config/config.go b/config/config.go index 2a196d0fc..8051bc145 100644 --- a/config/config.go +++ b/config/config.go @@ -15,7 +15,6 @@ import ( "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/internal/url" format "github.com/go-git/go-git/v5/plumbing/format/config" - "github.com/mitchellh/go-homedir" ) const ( @@ -185,7 +184,7 @@ func Paths(scope Scope) ([]string, error) { files = append(files, filepath.Join(xdg, "git/config")) } - home, err := homedir.Dir() + home, err := os.UserHomeDir() if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 512cc59ee..1f0ceaf47 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jessevdk/go-flags v1.5.0 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 - github.com/mitchellh/go-homedir v1.1.0 github.com/sergi/go-diff v1.1.0 github.com/xanzy/ssh-agent v0.3.1 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 diff --git a/go.sum b/go.sum index c4d7302b0..c4295c5f4 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ 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/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.3.0 h1:OAiwhtOsTVVUvVSLzwSO9PCCo/KPxGfn5aC3BV6d6TY= -github.com/go-git/go-git-fixtures/v4 v4.3.0/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= @@ -45,8 +43,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/plumbing/transport/ssh/auth_method.go b/plumbing/transport/ssh/auth_method.go index 351466954..b4959d6d6 100644 --- a/plumbing/transport/ssh/auth_method.go +++ b/plumbing/transport/ssh/auth_method.go @@ -10,7 +10,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" - "github.com/mitchellh/go-homedir" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" @@ -224,11 +223,13 @@ func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) { // // If list of files is empty, then it will be read from the SSH_KNOWN_HOSTS // environment variable, example: -// /home/foo/custom_known_hosts_file:/etc/custom_known/hosts_file +// +// /home/foo/custom_known_hosts_file:/etc/custom_known/hosts_file // // If SSH_KNOWN_HOSTS is not set the following file locations will be used: -// ~/.ssh/known_hosts -// /etc/ssh/ssh_known_hosts +// +// ~/.ssh/known_hosts +// /etc/ssh/ssh_known_hosts func NewKnownHostsCallback(files ...string) (ssh.HostKeyCallback, error) { var err error @@ -251,7 +252,7 @@ func getDefaultKnownHostsFiles() ([]string, error) { return files, nil } - homeDirPath, err := homedir.Dir() + homeDirPath, err := os.UserHomeDir() if err != nil { return nil, err } From af1efaa7dfb2a33de9c15597dd2cc65ea626cf35 Mon Sep 17 00:00:00 2001 From: Jon Eskin Date: Wed, 10 Aug 2022 04:10:58 -0400 Subject: [PATCH 45/62] minor grammatical fixes --- config/refspec.go | 2 +- plumbing/format/config/section.go | 2 +- plumbing/memory.go | 4 ++-- plumbing/object/change.go | 2 +- plumbing/reference.go | 8 ++++---- plumbing/transport/git/common.go | 4 ++-- storage/filesystem/shallow.go | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/refspec.go b/config/refspec.go index 4bfaa37bb..e2cf8c97b 100644 --- a/config/refspec.go +++ b/config/refspec.go @@ -64,7 +64,7 @@ func (s RefSpec) IsExactSHA1() bool { return plumbing.IsHash(s.Src()) } -// Src return the src side. +// Src returns the src side. func (s RefSpec) Src() string { spec := string(s) diff --git a/plumbing/format/config/section.go b/plumbing/format/config/section.go index 07f72f35a..4625ac583 100644 --- a/plumbing/format/config/section.go +++ b/plumbing/format/config/section.go @@ -103,7 +103,7 @@ func (s *Section) RemoveSubsection(name string) *Section { return s } -// Option return the value for the specified key. Empty string is returned if +// Option returns the value for the specified key. Empty string is returned if // key does not exists. func (s *Section) Option(key string) string { return s.Options.Get(key) diff --git a/plumbing/memory.go b/plumbing/memory.go index 21337cc0d..6d11271dd 100644 --- a/plumbing/memory.go +++ b/plumbing/memory.go @@ -25,13 +25,13 @@ func (o *MemoryObject) Hash() Hash { return o.h } -// Type return the ObjectType +// Type returns the ObjectType func (o *MemoryObject) Type() ObjectType { return o.t } // SetType sets the ObjectType func (o *MemoryObject) SetType(t ObjectType) { o.t = t } -// Size return the size of the object +// Size returns the size of the object func (o *MemoryObject) Size() int64 { return o.sz } // SetSize set the object size, a content of the given size should be written diff --git a/plumbing/object/change.go b/plumbing/object/change.go index 8b119bc9c..3c619df86 100644 --- a/plumbing/object/change.go +++ b/plumbing/object/change.go @@ -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() diff --git a/plumbing/reference.go b/plumbing/reference.go index 08e908f1f..deb50676a 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -168,22 +168,22 @@ func NewHashReference(n ReferenceName, h Hash) *Reference { } } -// Type return the type of a reference +// Type returns the type of a reference func (r *Reference) Type() ReferenceType { return r.t } -// Name return the name of a reference +// Name returns the name of a reference func (r *Reference) Name() ReferenceName { return r.n } -// Hash return the hash of a hash reference +// Hash returns the hash of a hash reference func (r *Reference) Hash() Hash { return r.h } -// Target return the target of a symbolic reference +// Target returns the target of a symbolic reference func (r *Reference) Target() ReferenceName { return r.target } diff --git a/plumbing/transport/git/common.go b/plumbing/transport/git/common.go index 306aae261..c18d600c2 100644 --- a/plumbing/transport/git/common.go +++ b/plumbing/transport/git/common.go @@ -77,14 +77,14 @@ func (c *command) StderrPipe() (io.Reader, error) { return nil, nil } -// StdinPipe return the underlying connection as WriteCloser, wrapped to prevent +// StdinPipe returns the underlying connection as WriteCloser, wrapped to prevent // call to the Close function from the connection, a command execution in git // protocol can't be closed or killed func (c *command) StdinPipe() (io.WriteCloser, error) { return ioutil.WriteNopCloser(c.conn), nil } -// StdoutPipe return the underlying connection as Reader +// StdoutPipe returns the underlying connection as Reader func (c *command) StdoutPipe() (io.Reader, error) { return c.conn, nil } diff --git a/storage/filesystem/shallow.go b/storage/filesystem/shallow.go index afb600cf2..ac48fdfbb 100644 --- a/storage/filesystem/shallow.go +++ b/storage/filesystem/shallow.go @@ -34,7 +34,7 @@ func (s *ShallowStorage) SetShallow(commits []plumbing.Hash) error { return err } -// Shallow return the shallow commits reading from shallo file from .git +// Shallow returns the shallow commits reading from shallo file from .git func (s *ShallowStorage) Shallow() ([]plumbing.Hash, error) { f, err := s.dir.Shallow() if f == nil || err != nil { From c35b8082c863f2106de1c3c95ba9ed21d30f9371 Mon Sep 17 00:00:00 2001 From: Evan Elias Date: Mon, 20 Jun 2022 18:20:18 -0400 Subject: [PATCH 46/62] plumbing: transport/ssh, auto-populate ClientConfig.HostKeyAlgorithms. Fixes #411 This commit adjusts the transport/ssh logic in command.connect(), so that it now auto-populates ssh.ClientConfig.HostKeyAlgorithms. The algorithms are chosen based on the known host keys for the target host, as obtained from the known_hosts file. In order to look-up the algorithms from the known_hosts file, external module github.com/skeema/knownhosts is used. This package is just a thin wrapper around golang.org/x/crypto/ssh/knownhosts, adding an extra mechanism to query the known_hosts keys, implemented in a way which avoids duplication of any golang.org/x/crypto/ssh/knownhosts logic. Because HostKeyAlgorithms vary by target host, some related logic for setting HostKeyCallback has been moved out of the various AuthMethod implementations. This was necessary because the old HostKeyCallbackHelper is not host-specific. Since known_hosts handling isn't really tied to AuthMethod anyway, it seems reasonable to separate these. Previously-exported types/methods remain in place for backwards compat, but some of them are now unused. For testing approach, see pull request. Issue #411 can only be reproduced via end-to-end / integration testing, since it requires actually launching an SSH connection, in order to see the key mismatch error triggered from https://github.com/golang/go/issues/29286 as the root cause. --- go.mod | 7 +++--- go.sum | 14 +++++++---- plumbing/transport/ssh/auth_method.go | 35 +++++++++++++++------------ plumbing/transport/ssh/common.go | 24 +++++++++++++++++- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 1f0ceaf47..68bafbd4a 100644 --- a/go.mod +++ b/go.mod @@ -17,11 +17,12 @@ require ( github.com/jessevdk/go-flags v1.5.0 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 github.com/sergi/go-diff v1.1.0 + github.com/skeema/knownhosts v1.1.0 github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 - golang.org/x/net v0.0.0-20210326060303-6b1517762897 + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c - golang.org/x/text v0.3.3 + golang.org/x/text v0.3.6 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index c4295c5f4..4bda7c456 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= +github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -59,24 +61,26 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/plumbing/transport/ssh/auth_method.go b/plumbing/transport/ssh/auth_method.go index b4959d6d6..9d3bcd359 100644 --- a/plumbing/transport/ssh/auth_method.go +++ b/plumbing/transport/ssh/auth_method.go @@ -10,9 +10,9 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/skeema/knownhosts" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/knownhosts" ) const DefaultUsername = "git" @@ -43,7 +43,6 @@ const ( type KeyboardInteractive struct { User string Challenge ssh.KeyboardInteractiveChallenge - HostKeyCallbackHelper } func (a *KeyboardInteractive) Name() string { @@ -55,19 +54,18 @@ func (a *KeyboardInteractive) String() string { } func (a *KeyboardInteractive) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ + return &ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ a.Challenge, }, - }) + }, nil } // Password implements AuthMethod by using the given password. type Password struct { User string Password string - HostKeyCallbackHelper } func (a *Password) Name() string { @@ -79,10 +77,10 @@ func (a *Password) String() string { } func (a *Password) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ + return &ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ssh.Password(a.Password)}, - }) + }, nil } // PasswordCallback implements AuthMethod by using a callback @@ -90,7 +88,6 @@ func (a *Password) ClientConfig() (*ssh.ClientConfig, error) { type PasswordCallback struct { User string Callback func() (pass string, err error) - HostKeyCallbackHelper } func (a *PasswordCallback) Name() string { @@ -102,17 +99,16 @@ func (a *PasswordCallback) String() string { } func (a *PasswordCallback) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ + return &ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ssh.PasswordCallback(a.Callback)}, - }) + }, nil } // PublicKeys implements AuthMethod by using the given key pairs. type PublicKeys struct { User string Signer ssh.Signer - HostKeyCallbackHelper } // NewPublicKeys returns a PublicKeys from a PEM encoded private key. An @@ -151,10 +147,10 @@ func (a *PublicKeys) String() string { } func (a *PublicKeys) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ + return &ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ssh.PublicKeys(a.Signer)}, - }) + }, nil } func username() (string, error) { @@ -177,7 +173,6 @@ func username() (string, error) { type PublicKeysCallback struct { User string Callback func() (signers []ssh.Signer, err error) - HostKeyCallbackHelper } // NewSSHAgentAuth returns a PublicKeysCallback based on a SSH agent, it opens @@ -212,10 +207,10 @@ func (a *PublicKeysCallback) String() string { } func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) { - return a.SetHostKeyCallback(&ssh.ClientConfig{ + return &ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Callback)}, - }) + }, nil } // NewKnownHostsCallback returns ssh.HostKeyCallback based on a file based on a @@ -231,6 +226,11 @@ func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) { // ~/.ssh/known_hosts // /etc/ssh/ssh_known_hosts func NewKnownHostsCallback(files ...string) (ssh.HostKeyCallback, error) { + kh, err := newKnownHosts(files...) + return ssh.HostKeyCallback(kh), err +} + +func newKnownHosts(files ...string) (knownhosts.HostKeyCallback, error) { var err error if len(files) == 0 { @@ -286,6 +286,9 @@ func filterKnownHostsFiles(files ...string) ([]string, error) { // HostKeyCallbackHelper is a helper that provides common functionality to // configure HostKeyCallback into a ssh.ClientConfig. +// Deprecated in favor of SetConfigHostKeyFields (see common.go) which provides +// a mechanism for also setting ClientConfig.HostKeyAlgorithms for a specific +// host. type HostKeyCallbackHelper struct { // HostKeyCallback is the function type used for verifying server keys. // If nil default callback will be create using NewKnownHostsCallback diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go index 46e79134c..4b9ac0797 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -121,10 +121,15 @@ func (c *command) connect() error { if err != nil { return err } + hostWithPort := c.getHostWithPort() + config, err = SetConfigHostKeyFields(config, hostWithPort) + if err != nil { + return err + } overrideConfig(c.config, config) - c.client, err = dial("tcp", c.getHostWithPort(), config) + c.client, err = dial("tcp", hostWithPort, config) if err != nil { return err } @@ -162,6 +167,23 @@ func dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { return ssh.NewClient(c, chans, reqs), nil } +// SetConfigHostKeyFields sets cfg.HostKeyCallback and cfg.HostKeyAlgorithms +// based on OpenSSH known_hosts. cfg is modified in-place. hostWithPort must be +// supplied, since the algorithms will be set based on the known host keys for +// that specific host. Otherwise, golang.org/x/crypto/ssh can return an error +// upon connecting to a host whose *first* key is not known, even though other +// keys (of different types) are known and match properly. +// For background see https://github.com/go-git/go-git/issues/411 as well as +// https://github.com/golang/go/issues/29286 for root cause. +func SetConfigHostKeyFields(cfg *ssh.ClientConfig, hostWithPort string) (*ssh.ClientConfig, error) { + kh, err := newKnownHosts() + if err == nil { + cfg.HostKeyCallback = kh.HostKeyCallback() + cfg.HostKeyAlgorithms = kh.HostKeyAlgorithms(hostWithPort) + } + return cfg, err +} + func (c *command) getHostWithPort() string { if addr, found := c.doGetHostWithPortFromSSHConfig(); found { return addr From 36e1f5b74cb7fc57e6bbbcfeca66eb79a644c86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=9A=80=20Steven=20Ewing=20=F0=9F=8C=8C?= Date: Fri, 20 May 2022 14:05:47 -0700 Subject: [PATCH 47/62] plumbing: packp and server, Include the contents of `GO_GIT_USER_AGENT_EXTRA` as the git user agent. Fixes #529 --- .../protocol/packp/capability/capability.go | 15 ++++++++++++- .../packp/capability/capability_test.go | 22 +++++++++++++++++++ plumbing/protocol/packp/ulreq.go | 2 +- plumbing/protocol/packp/updreq.go | 2 +- plumbing/transport/server/server.go | 4 ++-- 5 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 plumbing/protocol/packp/capability/capability_test.go diff --git a/plumbing/protocol/packp/capability/capability.go b/plumbing/protocol/packp/capability/capability.go index 871441238..b52e8a49d 100644 --- a/plumbing/protocol/packp/capability/capability.go +++ b/plumbing/protocol/packp/capability/capability.go @@ -1,6 +1,11 @@ // Package capability defines the server and client capabilities. package capability +import ( + "fmt" + "os" +) + // Capability describes a server or client capability. type Capability string @@ -238,7 +243,15 @@ const ( Filter Capability = "filter" ) -const DefaultAgent = "go-git/5.x" +const userAgent = "go-git/5.x" + +// DefaultAgent provides the user agent string. +func DefaultAgent() string { + if envUserAgent, ok := os.LookupEnv("GO_GIT_USER_AGENT_EXTRA"); ok { + return fmt.Sprintf("%s %s", userAgent, envUserAgent) + } + return userAgent +} var known = map[Capability]bool{ MultiACK: true, MultiACKDetailed: true, NoDone: true, ThinPack: true, diff --git a/plumbing/protocol/packp/capability/capability_test.go b/plumbing/protocol/packp/capability/capability_test.go new file mode 100644 index 000000000..f1fd0282a --- /dev/null +++ b/plumbing/protocol/packp/capability/capability_test.go @@ -0,0 +1,22 @@ +package capability + +import ( + "fmt" + "os" + + check "gopkg.in/check.v1" +) + +var _ = check.Suite(&SuiteCapabilities{}) + +func (s *SuiteCapabilities) TestDefaultAgent(c *check.C) { + os.Unsetenv("GO_GIT_USER_AGENT_EXTRA") + ua := DefaultAgent() + c.Assert(ua, check.Equals, userAgent) +} + +func (s *SuiteCapabilities) TestEnvAgent(c *check.C) { + os.Setenv("GO_GIT_USER_AGENT_EXTRA", "abc xyz") + ua := DefaultAgent() + c.Assert(ua, check.Equals, fmt.Sprintf("%s %s", userAgent, "abc xyz")) +} diff --git a/plumbing/protocol/packp/ulreq.go b/plumbing/protocol/packp/ulreq.go index ddec06e99..344f8c7e3 100644 --- a/plumbing/protocol/packp/ulreq.go +++ b/plumbing/protocol/packp/ulreq.go @@ -95,7 +95,7 @@ func NewUploadRequestFromCapabilities(adv *capability.List) *UploadRequest { } if adv.Supports(capability.Agent) { - r.Capabilities.Set(capability.Agent, capability.DefaultAgent) + r.Capabilities.Set(capability.Agent, capability.DefaultAgent()) } return r diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go index 5dbd8ac73..8f39b39cb 100644 --- a/plumbing/protocol/packp/updreq.go +++ b/plumbing/protocol/packp/updreq.go @@ -59,7 +59,7 @@ func NewReferenceUpdateRequestFromCapabilities(adv *capability.List) *ReferenceU r := NewReferenceUpdateRequest() if adv.Supports(capability.Agent) { - r.Capabilities.Set(capability.Agent, capability.DefaultAgent) + r.Capabilities.Set(capability.Agent, capability.DefaultAgent()) } if adv.Supports(capability.ReportStatus) { diff --git a/plumbing/transport/server/server.go b/plumbing/transport/server/server.go index 8ab70fe70..11fa0c801 100644 --- a/plumbing/transport/server/server.go +++ b/plumbing/transport/server/server.go @@ -189,7 +189,7 @@ func (s *upSession) objectsToUpload(req *packp.UploadPackRequest) ([]plumbing.Ha } func (*upSession) setSupportedCapabilities(c *capability.List) error { - if err := c.Set(capability.Agent, capability.DefaultAgent); err != nil { + if err := c.Set(capability.Agent, capability.DefaultAgent()); err != nil { return err } @@ -355,7 +355,7 @@ func (s *rpSession) reportStatus() *packp.ReportStatus { } func (*rpSession) setSupportedCapabilities(c *capability.List) error { - if err := c.Set(capability.Agent, capability.DefaultAgent); err != nil { + if err := c.Set(capability.Agent, capability.DefaultAgent()); err != nil { return err } From 07b3c3d0d9f128bca9b788fb1e77eaec2dd78ce6 Mon Sep 17 00:00:00 2001 From: Quanyi Ma Date: Thu, 22 Sep 2022 16:27:09 +0800 Subject: [PATCH 48/62] Fix typos (#532) --- plumbing/format/packfile/scanner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plumbing/format/packfile/scanner.go b/plumbing/format/packfile/scanner.go index 5d9e8fb65..45d480c04 100644 --- a/plumbing/format/packfile/scanner.go +++ b/plumbing/format/packfile/scanner.go @@ -114,7 +114,7 @@ func (s *Scanner) Header() (version, objects uint32, err error) { return } -// readSignature reads an returns the signature field in the packfile. +// readSignature reads a returns the signature field in the packfile. func (s *Scanner) readSignature() ([]byte, error) { var sig = make([]byte, 4) if _, err := io.ReadFull(s.r, sig); err != nil { @@ -404,7 +404,7 @@ func (s *Scanner) Flush() error { // - Keeps track of the current read position, for when the underlying reader // isn't an io.SeekReader, but we still want to know the current offset. // - Writes to the hash writer what it reads, with the aid of a smaller buffer. -// The buffer helps avoid a performance penality for performing small writes +// The buffer helps avoid a performance penalty for performing small writes // to the crc32 hash writer. type scannerReader struct { reader io.Reader From f9b2cce5c9e6510fefb21ce54c07b59e83b6da8f Mon Sep 17 00:00:00 2001 From: cui fliter Date: Thu, 22 Sep 2022 16:27:41 +0800 Subject: [PATCH 49/62] *: fix some typos (#567) Signed-off-by: cui fliter Signed-off-by: cui fliter --- options.go | 4 ++-- plumbing/transport/common.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/options.go b/options.go index d6f0e9f11..648003548 100644 --- a/options.go +++ b/options.go @@ -218,7 +218,7 @@ type PushOptions struct { // Force allows the push to update a remote branch even when the local // branch does not descend from it. Force bool - // InsecureSkipTLS skips ssl verify if protocal is https + // InsecureSkipTLS skips ssl verify if protocol is https InsecureSkipTLS bool // CABundle specify additional ca bundle with system cert pool CABundle []byte @@ -607,7 +607,7 @@ func (o *CreateTagOptions) loadConfigTagger(r *Repository) error { type ListOptions struct { // Auth credentials, if required, to use with the remote repository. Auth transport.AuthMethod - // InsecureSkipTLS skips ssl verify if protocal is https + // InsecureSkipTLS skips ssl verify if protocol is https InsecureSkipTLS bool // CABundle specify additional ca bundle with system cert pool CABundle []byte diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index a9ee2caee..a2a78f028 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -112,7 +112,7 @@ type Endpoint struct { Port int // Path is the repository path. Path string - // InsecureSkipTLS skips ssl verify if protocal is https + // InsecureSkipTLS skips ssl verify if protocol is https InsecureSkipTLS bool // CaBundle specify additional ca bundle with system cert pool CaBundle []byte From 08cffa1efade914020497a73907763e8d3707a77 Mon Sep 17 00:00:00 2001 From: James Romeril Date: Thu, 22 Sep 2022 18:31:11 +1000 Subject: [PATCH 50/62] *: fix typographical error --- options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options.go b/options.go index 648003548..7e5c1b434 100644 --- a/options.go +++ b/options.go @@ -402,7 +402,7 @@ type LogOptions struct { // Show only those commits in which the specified file was inserted/updated. // It is equivalent to running `git log -- `. - // this field is kept for compatility, it can be replaced with PathFilter + // this field is kept for compatibility, it can be replaced with PathFilter FileName *string // Filter commits based on the path of files that are updated From bb0153156f05cec7adb294f814d6efb4122b5f41 Mon Sep 17 00:00:00 2001 From: Brian Mayer Date: Tue, 27 Sep 2022 12:47:45 -0300 Subject: [PATCH 51/62] Fixed some little typos --- plumbing/format/diff/patch.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plumbing/format/diff/patch.go b/plumbing/format/diff/patch.go index 39a66a1a8..c7678b01a 100644 --- a/plumbing/format/diff/patch.go +++ b/plumbing/format/diff/patch.go @@ -9,7 +9,7 @@ import ( type Operation int const ( - // Equal item represents a equals diff. + // Equal item represents an equals diff. Equal Operation = iota // Add item represents an insert diff. Add @@ -26,15 +26,15 @@ type Patch interface { Message() string } -// FilePatch represents the necessary steps to transform one file to another. +// FilePatch represents the necessary steps to transform one file into another. type FilePatch interface { // IsBinary returns true if this patch is representing a binary file. IsBinary() bool - // Files returns the from and to Files, with all the necessary metadata to + // Files returns the from and to Files, with all the necessary metadata // about them. If the patch creates a new file, "from" will be nil. // If the patch deletes a file, "to" will be nil. Files() (from, to File) - // Chunks returns a slice of ordered changes to transform "from" File to + // Chunks returns a slice of ordered changes to transform "from" File into // "to" File. If the file is a binary one, Chunks will be empty. Chunks() []Chunk } @@ -49,7 +49,7 @@ type File interface { Path() string } -// Chunk represents a portion of a file transformation to another. +// Chunk represents a portion of a file transformation into another. type Chunk interface { // Content contains the portion of the file. Content() string From 7dd5d8f39a3c31c8257c29a8cfa4ef2cf15d4a80 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Wed, 12 Oct 2022 17:24:33 +0200 Subject: [PATCH 52/62] plumbing: gitattributes, Avoid index out of range When a path is deeper than the single asterisk pattern the code would crash with a "index out of range". This change checks the length of the remaining pattern before it references an element of that slice. With a single trailing asterisk paths deeper than the pattern should not get the attributes. For example with the following `.gitattributes` file: thirdparty/* linguist-vendored This is how git handles it: $ git check-attr --all thirdparty/README.md thirdparty/README.md: diff: markdown thirdparty/README.md: linguist-vendored: set $ git check-attr --all thirdparty/package/README.md thirdparty/package/README.md: diff: markdown --- plumbing/format/gitattributes/pattern.go | 5 +++++ plumbing/format/gitattributes/pattern_test.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/plumbing/format/gitattributes/pattern.go b/plumbing/format/gitattributes/pattern.go index d961aba9c..f101f4725 100644 --- a/plumbing/format/gitattributes/pattern.go +++ b/plumbing/format/gitattributes/pattern.go @@ -52,6 +52,11 @@ func (p *pattern) Match(path []string) bool { var match, doublestar bool var err error for _, part := range path { + // path is deeper than pattern + if len(pattern) == 0 { + return false + } + // skip empty if pattern[0] == "" { pattern = pattern[1:] diff --git a/plumbing/format/gitattributes/pattern_test.go b/plumbing/format/gitattributes/pattern_test.go index f95be6e7e..981d56f56 100644 --- a/plumbing/format/gitattributes/pattern_test.go +++ b/plumbing/format/gitattributes/pattern_test.go @@ -174,6 +174,12 @@ func (s *PatternSuite) TestGlobMatch_tailingAsterisks_single(c *C) { c.Assert(r, Equals, true) } +func (s *PatternSuite) TestGlobMatch_tailingAsterisk_single(c *C) { + p := ParsePattern("/*lue/*", nil) + r := p.Match([]string{"value", "volcano", "tail"}) + c.Assert(r, Equals, false) +} + func (s *PatternSuite) TestGlobMatch_tailingAsterisks_exactMatch(c *C) { p := ParsePattern("/*lue/vol?ano/**", nil) r := p.Match([]string{"value", "volcano"}) From 123cdde6f2f6282cb779e03745d384833ac1265b Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 26 Oct 2022 18:12:39 +0100 Subject: [PATCH 53/62] Use Sync.Pool pointers to optimise memory usage Signed-off-by: Paulo Gomes --- plumbing/format/objfile/writer.go | 15 +++++++++++-- plumbing/format/packfile/parser_test.go | 28 +++++++++++++++++++++++++ plumbing/format/packfile/patch_delta.go | 5 +++-- plumbing/format/packfile/scanner.go | 14 ++++++++----- storage/filesystem/object.go | 14 ++++++++++++- worktree.go | 8 ++++--- 6 files changed, 71 insertions(+), 13 deletions(-) diff --git a/plumbing/format/objfile/writer.go b/plumbing/format/objfile/writer.go index 2a96a4370..248f81bef 100644 --- a/plumbing/format/objfile/writer.go +++ b/plumbing/format/objfile/writer.go @@ -5,6 +5,7 @@ import ( "errors" "io" "strconv" + "sync" "github.com/go-git/go-git/v5/plumbing" ) @@ -18,9 +19,9 @@ var ( // not close the underlying io.Writer. type Writer struct { raw io.Writer - zlib io.WriteCloser hasher plumbing.Hasher multi io.Writer + zlib io.WriteCloser closed bool pending int64 // number of unwritten bytes @@ -31,12 +32,21 @@ type Writer struct { // The returned Writer implements io.WriteCloser. Close should be called when // finished with the Writer. Close will not close the underlying io.Writer. func NewWriter(w io.Writer) *Writer { + zlib := zlibPool.Get().(*zlib.Writer) + zlib.Reset(w) + return &Writer{ raw: w, - zlib: zlib.NewWriter(w), + zlib: zlib, } } +var zlibPool = sync.Pool{ + New: func() interface{} { + return zlib.NewWriter(nil) + }, +} + // WriteHeader writes the type and the size and prepares to accept the object's // contents. If an invalid t is provided, plumbing.ErrInvalidType is returned. If a // negative size is provided, ErrNegativeSize is returned. @@ -100,6 +110,7 @@ func (w *Writer) Hash() plumbing.Hash { // Calling Close does not close the wrapped io.Writer originally passed to // NewWriter. func (w *Writer) Close() error { + defer zlibPool.Put(w.zlib) if err := w.zlib.Close(); err != nil { return err } diff --git a/plumbing/format/packfile/parser_test.go b/plumbing/format/packfile/parser_test.go index 09f3f9704..651d05f3d 100644 --- a/plumbing/format/packfile/parser_test.go +++ b/plumbing/format/packfile/parser_test.go @@ -10,8 +10,10 @@ import ( fixtures "github.com/go-git/go-git-fixtures/v4" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/format/packfile" "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/go-git/go-git/v5/storage/filesystem" . "gopkg.in/check.v1" ) @@ -248,3 +250,29 @@ func BenchmarkParseBasic(b *testing.B) { } } } + +func BenchmarkParser(b *testing.B) { + f := fixtures.Basic().One() + defer fixtures.Clean() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + b.StopTimer() + scanner := packfile.NewScanner(f.Packfile()) + fs := osfs.New(os.TempDir()) + storage := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) + + parser, err := packfile.NewParserWithStorage(scanner, storage) + if err != nil { + b.Error(err) + } + + b.StartTimer() + _, err = parser.Parse() + + b.StopTimer() + if err != nil { + b.Error(err) + } + } +} diff --git a/plumbing/format/packfile/patch_delta.go b/plumbing/format/packfile/patch_delta.go index 17da11e03..053466ddf 100644 --- a/plumbing/format/packfile/patch_delta.go +++ b/plumbing/format/packfile/patch_delta.go @@ -53,9 +53,10 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) { target.SetSize(int64(dst.Len())) - b := byteSlicePool.Get().([]byte) + bufp := byteSlicePool.Get().(*[]byte) + b := *bufp _, err = io.CopyBuffer(w, dst, b) - byteSlicePool.Put(b) + byteSlicePool.Put(bufp) return err } diff --git a/plumbing/format/packfile/scanner.go b/plumbing/format/packfile/scanner.go index 45d480c04..b655594b7 100644 --- a/plumbing/format/packfile/scanner.go +++ b/plumbing/format/packfile/scanner.go @@ -346,15 +346,17 @@ func (s *Scanner) copyObject(w io.Writer) (n int64, err error) { } defer ioutil.CheckClose(zr, &err) - buf := byteSlicePool.Get().([]byte) + bufp := byteSlicePool.Get().(*[]byte) + buf := *bufp n, err = io.CopyBuffer(w, zr, buf) - byteSlicePool.Put(buf) + byteSlicePool.Put(bufp) return } var byteSlicePool = sync.Pool{ New: func() interface{} { - return make([]byte, 32*1024) + b := make([]byte, 32*1024) + return &b }, } @@ -387,9 +389,11 @@ func (s *Scanner) Checksum() (plumbing.Hash, error) { // Close reads the reader until io.EOF func (s *Scanner) Close() error { - buf := byteSlicePool.Get().([]byte) + bufp := byteSlicePool.Get().(*[]byte) + buf := *bufp _, err := io.CopyBuffer(stdioutil.Discard, s.r, buf) - byteSlicePool.Put(buf) + byteSlicePool.Put(bufp) + return err } diff --git a/storage/filesystem/object.go b/storage/filesystem/object.go index 5c91bcd69..21667fa5a 100644 --- a/storage/filesystem/object.go +++ b/storage/filesystem/object.go @@ -4,6 +4,7 @@ import ( "bytes" "io" "os" + "sync" "time" "github.com/go-git/go-git/v5/plumbing" @@ -419,10 +420,21 @@ func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedOb s.objectCache.Put(obj) - _, err = io.Copy(w, r) + bufp := copyBufferPool.Get().(*[]byte) + buf := *bufp + _, err = io.CopyBuffer(w, r, buf) + copyBufferPool.Put(bufp) + return obj, err } +var copyBufferPool = sync.Pool{ + New: func() interface{} { + b := make([]byte, 32*1024) + return &b + }, +} + // Get returns the object with the given hash, by searching for it in // the packfile. func (s *ObjectStorage) getFromPackfile(h plumbing.Hash, canBeDelta bool) ( diff --git a/worktree.go b/worktree.go index c974aed24..98116ca52 100644 --- a/worktree.go +++ b/worktree.go @@ -534,7 +534,8 @@ func (w *Worktree) checkoutChangeRegularFile(name string, var copyBufferPool = sync.Pool{ New: func() interface{} { - return make([]byte, 32*1024) + b := make([]byte, 32*1024) + return &b }, } @@ -561,9 +562,10 @@ func (w *Worktree) checkoutFile(f *object.File) (err error) { } defer ioutil.CheckClose(to, &err) - buf := copyBufferPool.Get().([]byte) + bufp := copyBufferPool.Get().(*[]byte) + buf := *bufp _, err = io.CopyBuffer(to, from, buf) - copyBufferPool.Put(buf) + copyBufferPool.Put(bufp) return } From ffa7e69efb8c4ba8d4e08ec4c65e49e2228fd88b Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Mon, 7 Nov 2022 14:42:00 +0000 Subject: [PATCH 54/62] Optimise Reference.String() Decreases allocations and bytes per operation by using string builder with a predefined size. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One additional allocation has been removed by using its own implementation of Strings(). The reason behind this was due to the fact the calls to .String() are more recurrent than .Strings() and the performance impact was worth the code duplication. Benchmark results: cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz name old time/op new time/op delta ReferenceStringSymbolic-16 140ns ± 4% 40ns ± 9% -71.19% (p=0.008 n=5+5) ReferenceStringHash-16 174ns ±14% 85ns ± 4% -51.13% (p=0.008 n=5+5) ReferenceStringInvalid-16 48.9ns ± 2% 1.5ns ± 3% -96.96% (p=0.008 n=5+5) name old alloc/op new alloc/op delta ReferenceStringSymbolic-16 88.0B ± 0% 32.0B ± 0% -63.64% (p=0.008 n=5+5) ReferenceStringHash-16 176B ± 0% 144B ± 0% -18.18% (p=0.008 n=5+5) ReferenceStringInvalid-16 0.00B 0.00B ~ (all equal) name old allocs/op new allocs/op delta ReferenceStringSymbolic-16 4.00 ± 0% 1.00 ± 0% -75.00% (p=0.008 n=5+5) ReferenceStringHash-16 5.00 ± 0% 3.00 ± 0% -40.00% (p=0.008 n=5+5) ReferenceStringInvalid-16 0.00 0.00 ~ (all equal) Signed-off-by: Paulo Gomes --- plumbing/reference.go | 19 +++++++++++++++++-- plumbing/reference_test.go | 24 +++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/plumbing/reference.go b/plumbing/reference.go index deb50676a..eef11e827 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -204,6 +204,21 @@ func (r *Reference) Strings() [2]string { } func (r *Reference) String() string { - s := r.Strings() - return fmt.Sprintf("%s %s", s[1], s[0]) + ref := "" + switch r.Type() { + case HashReference: + ref = r.Hash().String() + case SymbolicReference: + ref = symrefPrefix + r.Target().String() + default: + return "" + } + + name := r.Name().String() + var v strings.Builder + v.Grow(len(ref) + len(name) + 1) + v.WriteString(ref) + v.WriteString(" ") + v.WriteString(name) + return v.String() } diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go index b3ccf5340..e69076ff6 100644 --- a/plumbing/reference_test.go +++ b/plumbing/reference_test.go @@ -1,6 +1,10 @@ package plumbing -import . "gopkg.in/check.v1" +import ( + "testing" + + . "gopkg.in/check.v1" +) type ReferenceSuite struct{} @@ -98,3 +102,21 @@ func (s *ReferenceSuite) TestIsTag(c *C) { r := ReferenceName("refs/tags/v3.1.") c.Assert(r.IsTag(), Equals, true) } + +func benchMarkReferenceString(r *Reference, b *testing.B) { + for n := 0; n < b.N; n++ { + r.String() + } +} + +func BenchmarkReferenceStringSymbolic(b *testing.B) { + benchMarkReferenceString(NewSymbolicReference("v3.1.1", "refs/tags/v3.1.1"), b) +} + +func BenchmarkReferenceStringHash(b *testing.B) { + benchMarkReferenceString(NewHashReference("v3.1.1", NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")), b) +} + +func BenchmarkReferenceStringInvalid(b *testing.B) { + benchMarkReferenceString(&Reference{}, b) +} From 9490da0f86a12269abb2099e2ead1f20eec166d2 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 4 Nov 2022 12:44:40 +0000 Subject: [PATCH 55/62] Optimize zlib reader and consolidate sync.pools Expands on the optimisations from https://github.com/fluxcd/go-git/pull/5 and ensures that zlib reader does not need to recreate a deflate dictionary at every use. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The use of sync pools was consolidated into a new sync utils package. name old time/op new time/op delta Parser-16 7.51ms ± 3% 7.71ms ± 6% ~ (p=0.222 n=5+5) name old alloc/op new alloc/op delta Parser-16 4.65MB ± 3% 1.90MB ± 3% -59.06% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Parser-16 3.48k ± 0% 3.32k ± 0% -4.57% (p=0.016 n=5+4) Signed-off-by: Paulo Gomes --- plumbing/format/objfile/reader.go | 17 +++--- plumbing/format/objfile/writer.go | 16 ++---- plumbing/format/packfile/common.go | 18 ------ plumbing/format/packfile/diff_delta.go | 21 +++---- plumbing/format/packfile/packfile.go | 34 ++++++------ plumbing/format/packfile/parser.go | 19 ++++--- plumbing/format/packfile/patch_delta.go | 18 +++--- plumbing/format/packfile/scanner.go | 54 ++++++++---------- plumbing/object/commit.go | 7 +-- plumbing/object/common.go | 12 ---- plumbing/object/tag.go | 8 +-- plumbing/object/tree.go | 8 +-- utils/sync/bufio.go | 29 ++++++++++ utils/sync/bufio_test.go | 26 +++++++++ utils/sync/bytes.go | 51 +++++++++++++++++ utils/sync/bytes_test.go | 49 ++++++++++++++++ utils/sync/zlib.go | 74 +++++++++++++++++++++++++ utils/sync/zlib_test.go | 74 +++++++++++++++++++++++++ worktree.go | 16 ++---- 19 files changed, 398 insertions(+), 153 deletions(-) delete mode 100644 plumbing/object/common.go create mode 100644 utils/sync/bufio.go create mode 100644 utils/sync/bufio_test.go create mode 100644 utils/sync/bytes.go create mode 100644 utils/sync/bytes_test.go create mode 100644 utils/sync/zlib.go create mode 100644 utils/sync/zlib_test.go diff --git a/plumbing/format/objfile/reader.go b/plumbing/format/objfile/reader.go index b6b2ca06d..d7932f4ea 100644 --- a/plumbing/format/objfile/reader.go +++ b/plumbing/format/objfile/reader.go @@ -1,13 +1,13 @@ package objfile import ( - "compress/zlib" "errors" "io" "strconv" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/packfile" + "github.com/go-git/go-git/v5/utils/sync" ) var ( @@ -20,20 +20,22 @@ var ( // Reader implements io.ReadCloser. Close should be called when finished with // the Reader. Close will not close the underlying io.Reader. type Reader struct { - multi io.Reader - zlib io.ReadCloser - hasher plumbing.Hasher + multi io.Reader + zlib io.Reader + zlibref sync.ZLibReader + hasher plumbing.Hasher } // NewReader returns a new Reader reading from r. func NewReader(r io.Reader) (*Reader, error) { - zlib, err := zlib.NewReader(r) + zlib, err := sync.GetZlibReader(r) if err != nil { return nil, packfile.ErrZLib.AddDetails(err.Error()) } return &Reader{ - zlib: zlib, + zlib: zlib.Reader, + zlibref: zlib, }, nil } @@ -110,5 +112,6 @@ func (r *Reader) Hash() plumbing.Hash { // Close releases any resources consumed by the Reader. Calling Close does not // close the wrapped io.Reader originally passed to NewReader. func (r *Reader) Close() error { - return r.zlib.Close() + sync.PutZlibReader(r.zlibref) + return nil } diff --git a/plumbing/format/objfile/writer.go b/plumbing/format/objfile/writer.go index 248f81bef..0d0f15492 100644 --- a/plumbing/format/objfile/writer.go +++ b/plumbing/format/objfile/writer.go @@ -5,9 +5,9 @@ import ( "errors" "io" "strconv" - "sync" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/utils/sync" ) var ( @@ -21,7 +21,7 @@ type Writer struct { raw io.Writer hasher plumbing.Hasher multi io.Writer - zlib io.WriteCloser + zlib *zlib.Writer closed bool pending int64 // number of unwritten bytes @@ -32,21 +32,13 @@ type Writer struct { // The returned Writer implements io.WriteCloser. Close should be called when // finished with the Writer. Close will not close the underlying io.Writer. func NewWriter(w io.Writer) *Writer { - zlib := zlibPool.Get().(*zlib.Writer) - zlib.Reset(w) - + zlib := sync.GetZlibWriter(w) return &Writer{ raw: w, zlib: zlib, } } -var zlibPool = sync.Pool{ - New: func() interface{} { - return zlib.NewWriter(nil) - }, -} - // WriteHeader writes the type and the size and prepares to accept the object's // contents. If an invalid t is provided, plumbing.ErrInvalidType is returned. If a // negative size is provided, ErrNegativeSize is returned. @@ -110,7 +102,7 @@ func (w *Writer) Hash() plumbing.Hash { // Calling Close does not close the wrapped io.Writer originally passed to // NewWriter. func (w *Writer) Close() error { - defer zlibPool.Put(w.zlib) + defer sync.PutZlibWriter(w.zlib) if err := w.zlib.Close(); err != nil { return err } diff --git a/plumbing/format/packfile/common.go b/plumbing/format/packfile/common.go index df423ad50..36c5ef5b8 100644 --- a/plumbing/format/packfile/common.go +++ b/plumbing/format/packfile/common.go @@ -1,10 +1,7 @@ package packfile import ( - "bytes" - "compress/zlib" "io" - "sync" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" @@ -61,18 +58,3 @@ func WritePackfileToObjectStorage( return err } - -var bufPool = sync.Pool{ - New: func() interface{} { - return bytes.NewBuffer(nil) - }, -} - -var zlibInitBytes = []byte{0x78, 0x9c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01} - -var zlibReaderPool = sync.Pool{ - New: func() interface{} { - r, _ := zlib.NewReader(bytes.NewReader(zlibInitBytes)) - return r - }, -} diff --git a/plumbing/format/packfile/diff_delta.go b/plumbing/format/packfile/diff_delta.go index 1951b34ef..2c7a33581 100644 --- a/plumbing/format/packfile/diff_delta.go +++ b/plumbing/format/packfile/diff_delta.go @@ -5,6 +5,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) // See https://github.com/jelmer/dulwich/blob/master/dulwich/pack.py and @@ -43,18 +44,16 @@ func getDelta(index *deltaIndex, base, target plumbing.EncodedObject) (o plumbin defer ioutil.CheckClose(tr, &err) - bb := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(bb) - bb.Reset() + bb := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(bb) _, err = bb.ReadFrom(br) if err != nil { return nil, err } - tb := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(tb) - tb.Reset() + tb := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(tb) _, err = tb.ReadFrom(tr) if err != nil { @@ -80,9 +79,8 @@ func DiffDelta(src, tgt []byte) []byte { } func diffDelta(index *deltaIndex, src []byte, tgt []byte) []byte { - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - buf.Reset() + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) buf.Write(deltaEncodeSize(len(src))) buf.Write(deltaEncodeSize(len(tgt))) @@ -90,9 +88,8 @@ func diffDelta(index *deltaIndex, src []byte, tgt []byte) []byte { index.init(src) } - ibuf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(ibuf) - ibuf.Reset() + ibuf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(ibuf) for i := 0; i < len(tgt); i++ { offset, l := index.findMatch(src, tgt, i) diff --git a/plumbing/format/packfile/packfile.go b/plumbing/format/packfile/packfile.go index 8dd6041d5..685270225 100644 --- a/plumbing/format/packfile/packfile.go +++ b/plumbing/format/packfile/packfile.go @@ -2,7 +2,6 @@ package packfile import ( "bytes" - "compress/zlib" "fmt" "io" "os" @@ -13,6 +12,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/format/idxfile" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) var ( @@ -138,9 +138,8 @@ func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) { case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: return h.Length, nil case plumbing.REFDeltaObject, plumbing.OFSDeltaObject: - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - buf.Reset() + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) if _, _, err := p.s.NextObject(buf); err != nil { return 0, err @@ -227,9 +226,9 @@ func (p *Packfile) getNextObject(h *ObjectHeader, hash plumbing.Hash) (plumbing. // For delta objects we read the delta data and apply the small object // optimization only if the expanded version of the object still meets // the small object threshold condition. - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - buf.Reset() + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) + if _, _, err := p.s.NextObject(buf); err != nil { return nil, err } @@ -290,14 +289,13 @@ func (p *Packfile) getObjectContent(offset int64) (io.ReadCloser, error) { func asyncReader(p *Packfile) (io.ReadCloser, error) { reader := ioutil.NewReaderUsingReaderAt(p.file, p.s.r.offset) - zr := zlibReaderPool.Get().(io.ReadCloser) - - if err := zr.(zlib.Resetter).Reset(reader, nil); err != nil { + zr, err := sync.GetZlibReader(reader) + if err != nil { return nil, fmt.Errorf("zlib reset error: %s", err) } - return ioutil.NewReadCloserWithCloser(zr, func() error { - zlibReaderPool.Put(zr) + return ioutil.NewReadCloserWithCloser(zr.Reader, func() error { + sync.PutZlibReader(zr) return nil }), nil @@ -373,9 +371,9 @@ func (p *Packfile) fillRegularObjectContent(obj plumbing.EncodedObject) (err err } func (p *Packfile) fillREFDeltaObjectContent(obj plumbing.EncodedObject, ref plumbing.Hash) error { - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - buf.Reset() + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) + _, _, err := p.s.NextObject(buf) if err != nil { return err @@ -417,9 +415,9 @@ func (p *Packfile) fillREFDeltaObjectContentWithBuffer(obj plumbing.EncodedObjec } func (p *Packfile) fillOFSDeltaObjectContent(obj plumbing.EncodedObject, offset int64) error { - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - buf.Reset() + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) + _, _, err := p.s.NextObject(buf) if err != nil { return err diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go index 9ec838e45..522c146f2 100644 --- a/plumbing/format/packfile/parser.go +++ b/plumbing/format/packfile/parser.go @@ -10,6 +10,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) var ( @@ -175,7 +176,8 @@ func (p *Parser) init() error { } func (p *Parser) indexObjects() error { - buf := new(bytes.Buffer) + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) for i := uint32(0); i < p.count; i++ { buf.Reset() @@ -219,6 +221,7 @@ func (p *Parser) indexObjects() error { ota = newBaseObject(oh.Offset, oh.Length, t) } + buf.Grow(int(oh.Length)) _, crc, err := p.scanner.NextObject(buf) if err != nil { return err @@ -264,7 +267,9 @@ func (p *Parser) indexObjects() error { } func (p *Parser) resolveDeltas() error { - buf := &bytes.Buffer{} + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) + for _, obj := range p.oi { buf.Reset() err := p.get(obj, buf) @@ -346,9 +351,8 @@ func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { } if o.DiskType.IsDelta() { - b := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(b) - b.Reset() + b := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(b) err := p.get(o.Parent, b) if err != nil { return err @@ -382,9 +386,8 @@ func (p *Parser) resolveObject( if !o.DiskType.IsDelta() { return nil } - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - buf.Reset() + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) err := p.readData(buf, o) if err != nil { return err diff --git a/plumbing/format/packfile/patch_delta.go b/plumbing/format/packfile/patch_delta.go index 053466ddf..f00562d63 100644 --- a/plumbing/format/packfile/patch_delta.go +++ b/plumbing/format/packfile/patch_delta.go @@ -9,6 +9,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) // See https://github.com/git/git/blob/49fa3dc76179e04b0833542fa52d0f287a4955ac/delta.h @@ -34,18 +35,16 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) { defer ioutil.CheckClose(w, &err) - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - buf.Reset() + buf := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(buf) _, err = buf.ReadFrom(r) if err != nil { return err } src := buf.Bytes() - dst := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(dst) - dst.Reset() + dst := sync.GetBytesBuffer() + defer sync.PutBytesBuffer(dst) err = patchDelta(dst, src, delta) if err != nil { return err @@ -53,10 +52,9 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) { target.SetSize(int64(dst.Len())) - bufp := byteSlicePool.Get().(*[]byte) - b := *bufp - _, err = io.CopyBuffer(w, dst, b) - byteSlicePool.Put(bufp) + b := sync.GetByteSlice() + _, err = io.CopyBuffer(w, dst, *b) + sync.PutByteSlice(b) return err } diff --git a/plumbing/format/packfile/scanner.go b/plumbing/format/packfile/scanner.go index b655594b7..9ebb84a24 100644 --- a/plumbing/format/packfile/scanner.go +++ b/plumbing/format/packfile/scanner.go @@ -3,17 +3,16 @@ package packfile import ( "bufio" "bytes" - "compress/zlib" "fmt" "hash" "hash/crc32" "io" stdioutil "io/ioutil" - "sync" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/utils/binary" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) var ( @@ -323,14 +322,14 @@ func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err erro // ReadObject returns a reader for the object content and an error func (s *Scanner) ReadObject() (io.ReadCloser, error) { s.pendingObject = nil - zr := zlibReaderPool.Get().(io.ReadCloser) + zr, err := sync.GetZlibReader(s.r) - if err := zr.(zlib.Resetter).Reset(s.r, nil); err != nil { + if err != nil { return nil, fmt.Errorf("zlib reset error: %s", err) } - return ioutil.NewReadCloserWithCloser(zr, func() error { - zlibReaderPool.Put(zr) + return ioutil.NewReadCloserWithCloser(zr.Reader, func() error { + sync.PutZlibReader(zr) return nil }), nil } @@ -338,28 +337,20 @@ func (s *Scanner) ReadObject() (io.ReadCloser, error) { // ReadRegularObject reads and write a non-deltified object // from it zlib stream in an object entry in the packfile. func (s *Scanner) copyObject(w io.Writer) (n int64, err error) { - zr := zlibReaderPool.Get().(io.ReadCloser) - defer zlibReaderPool.Put(zr) + zr, err := sync.GetZlibReader(s.r) + defer sync.PutZlibReader(zr) - if err = zr.(zlib.Resetter).Reset(s.r, nil); err != nil { + if err != nil { return 0, fmt.Errorf("zlib reset error: %s", err) } - defer ioutil.CheckClose(zr, &err) - bufp := byteSlicePool.Get().(*[]byte) - buf := *bufp - n, err = io.CopyBuffer(w, zr, buf) - byteSlicePool.Put(bufp) + defer ioutil.CheckClose(zr.Reader, &err) + buf := sync.GetByteSlice() + n, err = io.CopyBuffer(w, zr.Reader, *buf) + sync.PutByteSlice(buf) return } -var byteSlicePool = sync.Pool{ - New: func() interface{} { - b := make([]byte, 32*1024) - return &b - }, -} - // SeekFromStart sets a new offset from start, returns the old position before // the change. func (s *Scanner) SeekFromStart(offset int64) (previous int64, err error) { @@ -389,10 +380,9 @@ func (s *Scanner) Checksum() (plumbing.Hash, error) { // Close reads the reader until io.EOF func (s *Scanner) Close() error { - bufp := byteSlicePool.Get().(*[]byte) - buf := *bufp - _, err := io.CopyBuffer(stdioutil.Discard, s.r, buf) - byteSlicePool.Put(bufp) + buf := sync.GetByteSlice() + _, err := io.CopyBuffer(stdioutil.Discard, s.r, *buf) + sync.PutByteSlice(buf) return err } @@ -403,13 +393,13 @@ func (s *Scanner) Flush() error { } // scannerReader has the following characteristics: -// - Provides an io.SeekReader impl for bufio.Reader, when the underlying -// reader supports it. -// - Keeps track of the current read position, for when the underlying reader -// isn't an io.SeekReader, but we still want to know the current offset. -// - Writes to the hash writer what it reads, with the aid of a smaller buffer. -// The buffer helps avoid a performance penalty for performing small writes -// to the crc32 hash writer. +// - Provides an io.SeekReader impl for bufio.Reader, when the underlying +// reader supports it. +// - Keeps track of the current read position, for when the underlying reader +// isn't an io.SeekReader, but we still want to know the current offset. +// - Writes to the hash writer what it reads, with the aid of a smaller buffer. +// The buffer helps avoid a performance penalty for performing small writes +// to the crc32 hash writer. type scannerReader struct { reader io.Reader crc io.Writer diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index 7a1b8e5ae..d2f718408 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -1,7 +1,6 @@ package object import ( - "bufio" "bytes" "context" "errors" @@ -14,6 +13,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) const ( @@ -180,9 +180,8 @@ 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 pgpsig bool diff --git a/plumbing/object/common.go b/plumbing/object/common.go deleted file mode 100644 index 3591f5f0a..000000000 --- a/plumbing/object/common.go +++ /dev/null @@ -1,12 +0,0 @@ -package object - -import ( - "bufio" - "sync" -) - -var bufPool = sync.Pool{ - New: func() interface{} { - return bufio.NewReader(nil) - }, -} diff --git a/plumbing/object/tag.go b/plumbing/object/tag.go index 216010d91..84066f768 100644 --- a/plumbing/object/tag.go +++ b/plumbing/object/tag.go @@ -1,7 +1,6 @@ package object import ( - "bufio" "bytes" "fmt" "io" @@ -13,6 +12,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) // Tag represents an annotated tag object. It points to a single git object of @@ -93,9 +93,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') diff --git a/plumbing/object/tree.go b/plumbing/object/tree.go index 5e6378ca4..e9f7666b8 100644 --- a/plumbing/object/tree.go +++ b/plumbing/object/tree.go @@ -1,7 +1,6 @@ package object import ( - "bufio" "context" "errors" "fmt" @@ -14,6 +13,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/go-git/go-git/v5/utils/sync" ) const ( @@ -230,9 +230,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 { diff --git a/utils/sync/bufio.go b/utils/sync/bufio.go new file mode 100644 index 000000000..5009ea804 --- /dev/null +++ b/utils/sync/bufio.go @@ -0,0 +1,29 @@ +package sync + +import ( + "bufio" + "io" + "sync" +) + +var bufioReader = sync.Pool{ + New: func() interface{} { + return bufio.NewReader(nil) + }, +} + +// GetBufioReader returns a *bufio.Reader that is managed by a sync.Pool. +// Returns a bufio.Reader that is resetted with reader and ready for use. +// +// After use, the *bufio.Reader should be put back into the sync.Pool +// by calling PutBufioReader. +func GetBufioReader(reader io.Reader) *bufio.Reader { + r := bufioReader.Get().(*bufio.Reader) + r.Reset(reader) + return r +} + +// PutBufioReader puts reader back into its sync.Pool. +func PutBufioReader(reader *bufio.Reader) { + bufioReader.Put(reader) +} diff --git a/utils/sync/bufio_test.go b/utils/sync/bufio_test.go new file mode 100644 index 000000000..e70f3d803 --- /dev/null +++ b/utils/sync/bufio_test.go @@ -0,0 +1,26 @@ +package sync + +import ( + "io" + "strings" + "testing" +) + +func TestGetAndPutBufioReader(t *testing.T) { + wanted := "someinput" + r := GetBufioReader(strings.NewReader(wanted)) + if r == nil { + t.Error("nil was not expected") + } + + got, err := r.ReadString(0) + if err != nil && err != io.EOF { + t.Errorf("unexpected error reading string: %v", err) + } + + if wanted != got { + t.Errorf("wanted %q got %q", wanted, got) + } + + PutBufioReader(r) +} diff --git a/utils/sync/bytes.go b/utils/sync/bytes.go new file mode 100644 index 000000000..dd06fc0bc --- /dev/null +++ b/utils/sync/bytes.go @@ -0,0 +1,51 @@ +package sync + +import ( + "bytes" + "sync" +) + +var ( + byteSlice = sync.Pool{ + New: func() interface{} { + b := make([]byte, 16*1024) + return &b + }, + } + bytesBuffer = sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(nil) + }, + } +) + +// GetByteSlice returns a *[]byte that is managed by a sync.Pool. +// The initial slice length will be 16384 (16kb). +// +// After use, the *[]byte should be put back into the sync.Pool +// by calling PutByteSlice. +func GetByteSlice() *[]byte { + buf := byteSlice.Get().(*[]byte) + return buf +} + +// PutByteSlice puts buf back into its sync.Pool. +func PutByteSlice(buf *[]byte) { + byteSlice.Put(buf) +} + +// GetBytesBuffer returns a *bytes.Buffer that is managed by a sync.Pool. +// Returns a buffer that is resetted and ready for use. +// +// After use, the *bytes.Buffer should be put back into the sync.Pool +// by calling PutBytesBuffer. +func GetBytesBuffer() *bytes.Buffer { + buf := bytesBuffer.Get().(*bytes.Buffer) + buf.Reset() + return buf +} + +// PutBytesBuffer puts buf back into its sync.Pool. +func PutBytesBuffer(buf *bytes.Buffer) { + bytesBuffer.Put(buf) +} diff --git a/utils/sync/bytes_test.go b/utils/sync/bytes_test.go new file mode 100644 index 000000000..b233429ce --- /dev/null +++ b/utils/sync/bytes_test.go @@ -0,0 +1,49 @@ +package sync + +import ( + "testing" +) + +func TestGetAndPutBytesBuffer(t *testing.T) { + buf := GetBytesBuffer() + if buf == nil { + t.Error("nil was not expected") + } + + initialLen := buf.Len() + buf.Grow(initialLen * 2) + grownLen := buf.Len() + + PutBytesBuffer(buf) + + buf = GetBytesBuffer() + if buf.Len() != grownLen { + t.Error("bytes buffer was not reused") + } + + buf2 := GetBytesBuffer() + if buf2.Len() != initialLen { + t.Errorf("new bytes buffer length: wanted %d got %d", initialLen, buf2.Len()) + } +} + +func TestGetAndPutByteSlice(t *testing.T) { + slice := GetByteSlice() + if slice == nil { + t.Error("nil was not expected") + } + + wanted := 16 * 1024 + got := len(*slice) + if wanted != got { + t.Errorf("byte slice length: wanted %d got %d", wanted, got) + } + + newByteSlice := make([]byte, wanted*2) + PutByteSlice(&newByteSlice) + + newSlice := GetByteSlice() + if len(*newSlice) != len(newByteSlice) { + t.Error("byte slice was not reused") + } +} diff --git a/utils/sync/zlib.go b/utils/sync/zlib.go new file mode 100644 index 000000000..c61388595 --- /dev/null +++ b/utils/sync/zlib.go @@ -0,0 +1,74 @@ +package sync + +import ( + "bytes" + "compress/zlib" + "io" + "sync" +) + +var ( + zlibInitBytes = []byte{0x78, 0x9c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01} + zlibReader = sync.Pool{ + New: func() interface{} { + r, _ := zlib.NewReader(bytes.NewReader(zlibInitBytes)) + return ZLibReader{ + Reader: r.(zlibReadCloser), + } + }, + } + zlibWriter = sync.Pool{ + New: func() interface{} { + return zlib.NewWriter(nil) + }, + } +) + +type zlibReadCloser interface { + io.ReadCloser + zlib.Resetter +} + +type ZLibReader struct { + dict *[]byte + Reader zlibReadCloser +} + +// GetZlibReader returns a ZLibReader that is managed by a sync.Pool. +// Returns a ZLibReader that is resetted using a dictionary that is +// also managed by a sync.Pool. +// +// After use, the ZLibReader should be put back into the sync.Pool +// by calling PutZlibReader. +func GetZlibReader(r io.Reader) (ZLibReader, error) { + z := zlibReader.Get().(ZLibReader) + z.dict = GetByteSlice() + + err := z.Reader.Reset(r, *z.dict) + + return z, err +} + +// PutZlibReader puts z back into its sync.Pool, first closing the reader. +// The Byte slice dictionary is also put back into its sync.Pool. +func PutZlibReader(z ZLibReader) { + z.Reader.Close() + PutByteSlice(z.dict) + zlibReader.Put(z) +} + +// GetZlibWriter returns a *zlib.Writer that is managed by a sync.Pool. +// Returns a writer that is resetted with w and ready for use. +// +// After use, the *zlib.Writer should be put back into the sync.Pool +// by calling PutZlibWriter. +func GetZlibWriter(w io.Writer) *zlib.Writer { + z := zlibWriter.Get().(*zlib.Writer) + z.Reset(w) + return z +} + +// PutZlibWriter puts w back into its sync.Pool. +func PutZlibWriter(w *zlib.Writer) { + zlibWriter.Put(w) +} diff --git a/utils/sync/zlib_test.go b/utils/sync/zlib_test.go new file mode 100644 index 000000000..b736fb221 --- /dev/null +++ b/utils/sync/zlib_test.go @@ -0,0 +1,74 @@ +package sync + +import ( + "bytes" + "compress/zlib" + "io" + "testing" +) + +func TestGetAndPutZlibReader(t *testing.T) { + _, err := GetZlibReader(bytes.NewReader(zlibInitBytes)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + dict := &[]byte{} + reader := FakeZLibReader{} + PutZlibReader(ZLibReader{dict: dict, Reader: &reader}) + + if !reader.wasClosed { + t.Errorf("reader was not closed") + } + + z2, err := GetZlibReader(bytes.NewReader(zlibInitBytes)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if dict != z2.dict { + t.Errorf("zlib dictionary was not reused") + } + + if &reader != z2.Reader { + t.Errorf("zlib reader was not reused") + } + + if !reader.wasReset { + t.Errorf("reader was not reset") + } +} + +func TestGetAndPutZlibWriter(t *testing.T) { + w := GetZlibWriter(nil) + if w == nil { + t.Errorf("nil was not expected") + } + + newW := zlib.NewWriter(nil) + PutZlibWriter(newW) + + w2 := GetZlibWriter(nil) + if w2 != newW { + t.Errorf("zlib writer was not reused") + } +} + +type FakeZLibReader struct { + wasClosed bool + wasReset bool +} + +func (f *FakeZLibReader) Reset(r io.Reader, dict []byte) error { + f.wasReset = true + return nil +} + +func (f *FakeZLibReader) Read(p []byte) (n int, err error) { + return 0, nil +} + +func (f *FakeZLibReader) Close() error { + f.wasClosed = true + return nil +} diff --git a/worktree.go b/worktree.go index 98116ca52..02f90a9e6 100644 --- a/worktree.go +++ b/worktree.go @@ -9,7 +9,6 @@ import ( "os" "path/filepath" "strings" - "sync" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" @@ -22,6 +21,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/ioutil" "github.com/go-git/go-git/v5/utils/merkletrie" + "github.com/go-git/go-git/v5/utils/sync" ) var ( @@ -532,13 +532,6 @@ func (w *Worktree) checkoutChangeRegularFile(name string, return nil } -var copyBufferPool = sync.Pool{ - New: func() interface{} { - b := make([]byte, 32*1024) - return &b - }, -} - func (w *Worktree) checkoutFile(f *object.File) (err error) { mode, err := f.Mode.ToOSFileMode() if err != nil { @@ -562,10 +555,9 @@ func (w *Worktree) checkoutFile(f *object.File) (err error) { } defer ioutil.CheckClose(to, &err) - bufp := copyBufferPool.Get().(*[]byte) - buf := *bufp - _, err = io.CopyBuffer(to, from, buf) - copyBufferPool.Put(bufp) + buf := sync.GetByteSlice() + _, err = io.CopyBuffer(to, from, *buf) + sync.PutByteSlice(buf) return } From a2c309de872dc18053acb186b1ec125d1f723a90 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 4 Nov 2022 11:04:29 +0000 Subject: [PATCH 56/62] tests: Replace time.sleep with eventually The previous approach was intermittently flake, leading to different results based on external results. The check for goroutines numbers now checks for less or equal, as the goal of the assertion is to confirm no goroutine is being leaked. Signed-off-by: Paulo Gomes --- remote_test.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/remote_test.go b/remote_test.go index d0c8fa8e0..751c89a1b 100644 --- a/remote_test.go +++ b/remote_test.go @@ -535,10 +535,22 @@ func (s *RemoteSuite) TestPushContext(c *C) { }) c.Assert(err, IsNil) - // let the goroutine from pushHashes finish and check that the number of - // goroutines is the same as before - time.Sleep(100 * time.Millisecond) - c.Assert(runtime.NumGoroutine(), Equals, numGoroutines) + eventually(c, func() bool { + return runtime.NumGoroutine() <= numGoroutines + }) +} + +func eventually(c *C, condition func() bool) { + select { + case <-time.After(5 * time.Second): + default: + if condition() { + break + } + time.Sleep(100 * time.Millisecond) + } + + c.Assert(condition(), Equals, true) } func (s *RemoteSuite) TestPushContextCanceled(c *C) { @@ -566,10 +578,9 @@ func (s *RemoteSuite) TestPushContextCanceled(c *C) { }) c.Assert(err, Equals, context.Canceled) - // let the goroutine from pushHashes finish and check that the number of - // goroutines is the same as before - time.Sleep(100 * time.Millisecond) - c.Assert(runtime.NumGoroutine(), Equals, numGoroutines) + eventually(c, func() bool { + return runtime.NumGoroutine() <= numGoroutines + }) } func (s *RemoteSuite) TestPushTags(c *C) { From 9c7d2df0f3b85745bea4d764e51ab9e811bf644d Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 16 Nov 2022 10:59:05 +0000 Subject: [PATCH 57/62] Allow unsupported multi_ack capability Azure DevOps requires capabilities multi_ack / multi_ack_detailed, which are not fully implemented and by default are included in transport.UnsupportedCapabilities. The initial clone operations require a full download of the repository, and therefore those unsupported capabilities are not as crucial, so by removing them from that list allows for the first clone to work successfully. Additional fetches will yield issues, therefore to support that repository users have to work from a clean clone until those capabilities are fully supported. Commits and pushes back into the repository have also been tested and work fine. This change adds an example for cloning Azure DevOps repositories. Signed-off-by: Paulo Gomes --- _examples/azure_devops/main.go | 56 ++++++++++++++++++++++ plumbing/protocol/packp/srvresp.go | 28 +++++++---- plumbing/protocol/packp/srvresp_test.go | 17 ++++++- plumbing/protocol/packp/uppackresp.go | 2 +- plumbing/protocol/packp/uppackresp_test.go | 6 ++- 5 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 _examples/azure_devops/main.go diff --git a/_examples/azure_devops/main.go b/_examples/azure_devops/main.go new file mode 100644 index 000000000..9c02ca080 --- /dev/null +++ b/_examples/azure_devops/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "os" + + git "github.com/go-git/go-git/v5" + . "github.com/go-git/go-git/v5/_examples" + "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" +) + +func main() { + CheckArgs("", "", "", "") + url, directory, username, password := os.Args[1], os.Args[2], os.Args[3], os.Args[4] + + // Clone the given repository to the given directory + Info("git clone %s %s", url, directory) + + // Azure DevOps requires capabilities multi_ack / multi_ack_detailed, + // which are not fully implemented and by default are included in + // transport.UnsupportedCapabilities. + // + // The initial clone operations require a full download of the repository, + // and therefore those unsupported capabilities are not as crucial, so + // by removing them from that list allows for the first clone to work + // successfully. + // + // Additional fetches will yield issues, therefore work always from a clean + // clone until those capabilities are fully supported. + // + // New commits and pushes against a remote worked without any issues. + transport.UnsupportedCapabilities = []capability.Capability{ + capability.ThinPack, + } + + r, err := git.PlainClone(directory, false, &git.CloneOptions{ + Auth: &http.BasicAuth{ + Username: username, + Password: password, + }, + URL: url, + Progress: os.Stdout, + }) + CheckIfError(err) + + // ... retrieving the branch being pointed by HEAD + ref, err := r.Head() + CheckIfError(err) + // ... retrieving the commit object + commit, err := r.CommitObject(ref.Hash()) + CheckIfError(err) + + fmt.Println(commit) +} diff --git a/plumbing/protocol/packp/srvresp.go b/plumbing/protocol/packp/srvresp.go index b3a7ee804..8cd0a7247 100644 --- a/plumbing/protocol/packp/srvresp.go +++ b/plumbing/protocol/packp/srvresp.go @@ -21,11 +21,6 @@ type ServerResponse struct { // Decode decodes the response into the struct, isMultiACK should be true, if // the request was done with multi_ack or multi_ack_detailed capabilities. func (r *ServerResponse) Decode(reader *bufio.Reader, isMultiACK bool) error { - // TODO: implement support for multi_ack or multi_ack_detailed responses - if isMultiACK { - return errors.New("multi_ack and multi_ack_detailed are not supported") - } - s := pktline.NewScanner(reader) for s.Scan() { @@ -48,7 +43,23 @@ func (r *ServerResponse) Decode(reader *bufio.Reader, isMultiACK bool) error { } } - return s.Err() + // isMultiACK is true when the remote server advertises the related + // capabilities when they are not in transport.UnsupportedCapabilities. + // + // Users may decide to remove multi_ack and multi_ack_detailed from the + // unsupported capabilities list, which allows them to do initial clones + // from Azure DevOps. + // + // Follow-up fetches may error, therefore errors are wrapped with additional + // information highlighting that this capabilities are not supported by go-git. + // + // TODO: Implement support for multi_ack or multi_ack_detailed responses. + err := s.Err() + if err != nil && isMultiACK { + return fmt.Errorf("multi_ack and multi_ack_detailed are not supported: %w", err) + } + + return err } // stopReading detects when a valid command such as ACK or NAK is found to be @@ -113,8 +124,9 @@ func (r *ServerResponse) decodeACKLine(line []byte) error { } // Encode encodes the ServerResponse into a writer. -func (r *ServerResponse) Encode(w io.Writer) error { - if len(r.ACKs) > 1 { +func (r *ServerResponse) Encode(w io.Writer, isMultiACK bool) error { + if len(r.ACKs) > 1 && !isMultiACK { + // For further information, refer to comments in the Decode func above. return errors.New("multi_ack and multi_ack_detailed are not supported") } diff --git a/plumbing/protocol/packp/srvresp_test.go b/plumbing/protocol/packp/srvresp_test.go index 02fab424e..aa0af528a 100644 --- a/plumbing/protocol/packp/srvresp_test.go +++ b/plumbing/protocol/packp/srvresp_test.go @@ -72,8 +72,21 @@ func (s *ServerResponseSuite) TestDecodeMalformed(c *C) { c.Assert(err, NotNil) } +// multi_ack isn't fully implemented, this ensures that Decode ignores that fact, +// as in some circumstances that's OK to assume so. +// +// TODO: Review as part of multi_ack implementation. func (s *ServerResponseSuite) TestDecodeMultiACK(c *C) { + raw := "" + + "0031ACK 1111111111111111111111111111111111111111\n" + + "0031ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e5\n" + + "00080PACK\n" + sr := &ServerResponse{} - err := sr.Decode(bufio.NewReader(bytes.NewBuffer(nil)), true) - c.Assert(err, NotNil) + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), true) + c.Assert(err, IsNil) + + c.Assert(sr.ACKs, HasLen, 2) + c.Assert(sr.ACKs[0], Equals, plumbing.NewHash("1111111111111111111111111111111111111111")) + c.Assert(sr.ACKs[1], Equals, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) } diff --git a/plumbing/protocol/packp/uppackresp.go b/plumbing/protocol/packp/uppackresp.go index 26ae61ed8..a485cb7b2 100644 --- a/plumbing/protocol/packp/uppackresp.go +++ b/plumbing/protocol/packp/uppackresp.go @@ -78,7 +78,7 @@ func (r *UploadPackResponse) Encode(w io.Writer) (err error) { } } - if err := r.ServerResponse.Encode(w); err != nil { + if err := r.ServerResponse.Encode(w, r.isMultiACK); err != nil { return err } diff --git a/plumbing/protocol/packp/uppackresp_test.go b/plumbing/protocol/packp/uppackresp_test.go index 260dc5748..3f87804a5 100644 --- a/plumbing/protocol/packp/uppackresp_test.go +++ b/plumbing/protocol/packp/uppackresp_test.go @@ -59,6 +59,10 @@ func (s *UploadPackResponseSuite) TestDecodeMalformed(c *C) { c.Assert(err, NotNil) } +// multi_ack isn't fully implemented, this ensures that Decode ignores that fact, +// as in some circumstances that's OK to assume so. +// +// TODO: Review as part of multi_ack implementation. func (s *UploadPackResponseSuite) TestDecodeMultiACK(c *C) { req := NewUploadPackRequest() req.Capabilities.Set(capability.MultiACK) @@ -67,7 +71,7 @@ func (s *UploadPackResponseSuite) TestDecodeMultiACK(c *C) { defer res.Close() err := res.Decode(ioutil.NopCloser(bytes.NewBuffer(nil))) - c.Assert(err, NotNil) + c.Assert(err, IsNil) } func (s *UploadPackResponseSuite) TestReadNoDecode(c *C) { From 7c37589e95f6a88e470bf91d3a0ef8536702f3f4 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 25 Nov 2022 14:07:01 +0000 Subject: [PATCH 58/62] sha1: Add collision resistent implementation Implement the same SHA1 collision resistent algorithm used by both the Git CLI and libgit2. Only commits with input that match the unavoidable bit conditions will be further processed, which will result in different hashes. Which is the same behaviour experienced in the Git CLI and Libgit2. Users can override the hash algorithm used with: hash.RegisterHash(crypto.SHA1, sha1.New) xref links: https://github.com/libgit2/libgit2/pull/4136/commits/2dfd1294f7a694bfa9e864a9489ae3cb318a5ed0 https://github.com/git/git/commit/28dc98e343ca4eb370a29ceec4c19beac9b5c01e Signed-off-by: Paulo Gomes --- go.mod | 1 + go.sum | 2 + plumbing/format/commitgraph/encoder.go | 6 +- plumbing/format/idxfile/encoder.go | 6 +- plumbing/format/index/decoder.go | 6 +- plumbing/format/index/encoder.go | 6 +- plumbing/format/packfile/encoder.go | 5 +- plumbing/hash.go | 7 +- plumbing/hash/hash.go | 59 ++++++++++++++ plumbing/hash/hash_test.go | 103 +++++++++++++++++++++++++ 10 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 plumbing/hash/hash.go create mode 100644 plumbing/hash/hash_test.go diff --git a/go.mod b/go.mod index 68bafbd4a..ee0c196dd 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jessevdk/go-flags v1.5.0 github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 + github.com/pjbgf/sha1cd v0.2.0 github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.1.0 github.com/xanzy/ssh-agent v0.3.1 diff --git a/go.sum b/go.sum index 4bda7c456..775e89a90 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pjbgf/sha1cd v0.2.0 h1:gIsJVwjbRviE4gydidGztxH1IlJQoYBcCrwG4Dz8wvM= +github.com/pjbgf/sha1cd v0.2.0/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/plumbing/format/commitgraph/encoder.go b/plumbing/format/commitgraph/encoder.go index d34076fc3..bcf7d030a 100644 --- a/plumbing/format/commitgraph/encoder.go +++ b/plumbing/format/commitgraph/encoder.go @@ -1,11 +1,11 @@ package commitgraph import ( - "crypto/sha1" - "hash" + "crypto" "io" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/utils/binary" ) @@ -17,7 +17,7 @@ type Encoder struct { // NewEncoder returns a new stream encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { - h := sha1.New() + h := hash.New(crypto.SHA1) mw := io.MultiWriter(w, h) return &Encoder{mw, h} } diff --git a/plumbing/format/idxfile/encoder.go b/plumbing/format/idxfile/encoder.go index 26b2e4d6b..6ac445ff6 100644 --- a/plumbing/format/idxfile/encoder.go +++ b/plumbing/format/idxfile/encoder.go @@ -1,10 +1,10 @@ package idxfile import ( - "crypto/sha1" - "hash" + "crypto" "io" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/utils/binary" ) @@ -16,7 +16,7 @@ type Encoder struct { // NewEncoder returns a new stream encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { - h := sha1.New() + h := hash.New(crypto.SHA1) mw := io.MultiWriter(w, h) return &Encoder{mw, h} } diff --git a/plumbing/format/index/decoder.go b/plumbing/format/index/decoder.go index 036b6365e..c4da20c08 100644 --- a/plumbing/format/index/decoder.go +++ b/plumbing/format/index/decoder.go @@ -3,15 +3,15 @@ package index import ( "bufio" "bytes" - "crypto/sha1" + "crypto" "errors" - "hash" "io" "io/ioutil" "strconv" "time" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/utils/binary" ) @@ -49,7 +49,7 @@ type Decoder struct { // NewDecoder returns a new decoder that reads from r. func NewDecoder(r io.Reader) *Decoder { - h := sha1.New() + h := hash.New(crypto.SHA1) return &Decoder{ r: io.TeeReader(r, h), hash: h, diff --git a/plumbing/format/index/encoder.go b/plumbing/format/index/encoder.go index 2c94d93fc..a91537870 100644 --- a/plumbing/format/index/encoder.go +++ b/plumbing/format/index/encoder.go @@ -2,13 +2,13 @@ package index import ( "bytes" - "crypto/sha1" + "crypto" "errors" - "hash" "io" "sort" "time" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/utils/binary" ) @@ -29,7 +29,7 @@ type Encoder struct { // NewEncoder returns a new encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { - h := sha1.New() + h := hash.New(crypto.SHA1) mw := io.MultiWriter(w, h) return &Encoder{mw, h} } diff --git a/plumbing/format/packfile/encoder.go b/plumbing/format/packfile/encoder.go index 5501f8861..a8a7e967b 100644 --- a/plumbing/format/packfile/encoder.go +++ b/plumbing/format/packfile/encoder.go @@ -2,11 +2,12 @@ package packfile import ( "compress/zlib" - "crypto/sha1" + "crypto" "fmt" "io" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/utils/binary" "github.com/go-git/go-git/v5/utils/ioutil" @@ -28,7 +29,7 @@ type Encoder struct { // OFSDeltaObject. To use Reference deltas, set useRefDeltas to true. func NewEncoder(w io.Writer, s storer.EncodedObjectStorer, useRefDeltas bool) *Encoder { h := plumbing.Hasher{ - Hash: sha1.New(), + Hash: hash.New(crypto.SHA1), } mw := io.MultiWriter(w, h) ow := newOffsetWriter(mw) diff --git a/plumbing/hash.go b/plumbing/hash.go index afc602a9e..2fab7593b 100644 --- a/plumbing/hash.go +++ b/plumbing/hash.go @@ -2,11 +2,12 @@ package plumbing import ( "bytes" - "crypto/sha1" + "crypto" "encoding/hex" - "hash" "sort" "strconv" + + "github.com/go-git/go-git/v5/plumbing/hash" ) // Hash SHA1 hashed content @@ -46,7 +47,7 @@ type Hasher struct { } func NewHasher(t ObjectType, size int64) Hasher { - h := Hasher{sha1.New()} + h := Hasher{hash.New(crypto.SHA1)} h.Write(t.Bytes()) h.Write([]byte(" ")) h.Write([]byte(strconv.FormatInt(size, 10))) diff --git a/plumbing/hash/hash.go b/plumbing/hash/hash.go new file mode 100644 index 000000000..fe3bf7655 --- /dev/null +++ b/plumbing/hash/hash.go @@ -0,0 +1,59 @@ +// package hash provides a way for managing the +// underlying hash implementations used across go-git. +package hash + +import ( + "crypto" + "fmt" + "hash" + + "github.com/pjbgf/sha1cd/cgo" +) + +// algos is a map of hash algorithms. +var algos = map[crypto.Hash]func() hash.Hash{} + +func init() { + reset() +} + +// reset resets the default algos value. Can be used after running tests +// that registers new algorithms to avoid side effects. +func reset() { + // For performance reasons the cgo version of the collision + // detection algorithm is being used. + algos[crypto.SHA1] = cgo.New +} + +// RegisterHash allows for the hash algorithm used to be overriden. +// This ensures the hash selection for go-git must be explicit, when +// overriding the default value. +func RegisterHash(h crypto.Hash, f func() hash.Hash) error { + if f == nil { + return fmt.Errorf("cannot register hash: f is nil") + } + + switch h { + case crypto.SHA1: + algos[h] = f + default: + return fmt.Errorf("unsupported hash function: %v", h) + } + return nil +} + +// Hash is the same as hash.Hash. This allows consumers +// to not having to import this package alongside "hash". +type Hash interface { + hash.Hash +} + +// New returns a new Hash for the given hash function. +// It panics if the hash function is not registered. +func New(h crypto.Hash) Hash { + hh, ok := algos[h] + if !ok { + panic(fmt.Sprintf("hash algorithm not registered: %v", h)) + } + return hh() +} diff --git a/plumbing/hash/hash_test.go b/plumbing/hash/hash_test.go new file mode 100644 index 000000000..f70ad117e --- /dev/null +++ b/plumbing/hash/hash_test.go @@ -0,0 +1,103 @@ +package hash + +import ( + "crypto" + "crypto/sha1" + "crypto/sha512" + "encoding/hex" + "hash" + "strings" + "testing" +) + +func TestRegisterHash(t *testing.T) { + // Reset default hash to avoid side effects. + defer reset() + + tests := []struct { + name string + hash crypto.Hash + new func() hash.Hash + wantErr string + }{ + { + name: "sha1", + hash: crypto.SHA1, + new: sha1.New, + }, + { + name: "sha1", + hash: crypto.SHA1, + wantErr: "cannot register hash: f is nil", + }, + { + name: "sha512", + hash: crypto.SHA512, + new: sha512.New, + wantErr: "unsupported hash function", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := RegisterHash(tt.hash, tt.new) + if tt.wantErr == "" && err != nil { + t.Errorf("unexpected error: %v", err) + } else if tt.wantErr != "" && err == nil { + t.Errorf("expected error: %v got: nil", tt.wantErr) + } else if err != nil && !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("expected error: %v got: %v", tt.wantErr, err) + } + }) + } +} + +// Verifies that the SHA1 implementation used is collision-resistant +// by default. +func TestSha1Collision(t *testing.T) { + defer reset() + + tests := []struct { + name string + content string + hash string + before func() + }{ + { + name: "sha-mbles-1: with collision detection", + content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", + hash: "4f3d9be4a472c4dae83c6314aa6c36a064c1fd14", + }, + { + name: "sha-mbles-1: with default SHA1", + content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", + hash: "8ac60ba76f1999a1ab70223f225aefdc78d4ddc0", + before: func() { + RegisterHash(crypto.SHA1, sha1.New) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.before != nil { + tt.before() + } + + h := New(crypto.SHA1) + data, err := hex.DecodeString(tt.content) + if err != nil { + t.Fatal(err) + } + + h.Reset() + h.Write(data) + sum := h.Sum(nil) + got := hex.EncodeToString(sum) + + if tt.hash != got { + t.Errorf("\n got: %q\nwanted: %q", got, tt.hash) + } + }) + } +} From 67fb0c07842a7e7d4f1efdced4bf6586ddabcd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Tue, 29 Nov 2022 10:11:56 +0100 Subject: [PATCH 59/62] .github: update go version --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1644dcfc9..befb2c84b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.15.x, 1.16.x] + go-version: [1.18.x, 1.19.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} @@ -33,14 +33,14 @@ jobs: run: make test-coverage - name: Convert coverage to lcov - if: matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.14.x' + if: matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.19.x' uses: jandelgado/gcov2lcov-action@v1.0.0 with: infile: coverage.out outfile: coverage.lcov - name: Coveralls - if: matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.14.x' + if: matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.19.x' uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} From b80af01b18983c2e8fc709fbb348e5ece025a831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Tue, 29 Nov 2022 10:52:07 +0100 Subject: [PATCH 60/62] Update test.yml --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index befb2c84b..cefbbdf6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,6 +42,7 @@ jobs: - name: Coveralls if: matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.19.x' uses: coverallsapp/github-action@master + continue-on-error: true with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: coverage.lcov From 6629ba68b5b9df17999b470f37d7e4070b2bf885 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 25 Nov 2022 16:39:02 +0000 Subject: [PATCH 61/62] Update dependencies Mitigates known supply chain CVEs: golang.org/x/crypto: - GO-2021-0356 - GO-2022-0968 golang.org/x/net: - GO-2021-0238 - GO-2022-0236 - GO-2022-0288 - GO-2022-0969 golang.org/x/sys: - GO-2022-0493 golang.org/x/text: - GO-2021-0113 - GO-2022-1059 Updates other dependencies that have no backwards compatibility issues. Signed-off-by: Paulo Gomes --- go.mod | 25 ++++++++--------- go.sum | 89 ++++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 70 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index ee0c196dd..b214c8a8d 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,28 @@ module github.com/go-git/go-git/v5 require ( - github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 + github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 github.com/acomagu/bufpipe v1.0.3 - github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/emirpasic/gods v1.12.0 - github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect - github.com/gliderlabs/ssh v0.2.2 + github.com/emirpasic/gods v1.18.1 + github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.0 github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-git-fixtures/v4 v4.3.1 - github.com/google/go-cmp v0.3.0 - github.com/imdario/mergo v0.3.12 + github.com/google/go-cmp v0.5.9 + github.com/imdario/mergo v0.3.13 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jessevdk/go-flags v1.5.0 - github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 + github.com/kevinburke/ssh_config v1.2.0 github.com/pjbgf/sha1cd v0.2.0 + github.com/pkg/errors v0.9.1 // indirect github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.1.0 - github.com/xanzy/ssh-agent v0.3.1 - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c - golang.org/x/text v0.3.6 + github.com/xanzy/ssh-agent v0.3.2 + golang.org/x/crypto v0.3.0 + golang.org/x/net v0.2.0 + golang.org/x/sys v0.2.0 + golang.org/x/text v0.4.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 775e89a90..2c00f0092 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,40 @@ -github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= +github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= 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/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -60,15 +61,27 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= -github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= +github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -76,14 +89,29 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -93,7 +121,6 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f2d68c45d3c3b3e2ce046206d3e3269991df61e5 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 30 Nov 2022 08:02:14 +0000 Subject: [PATCH 62/62] build: bump git workflow to Go 1.19 Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index bbccaa36b..fef212763 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -16,7 +16,7 @@ jobs: - name: Install Go uses: actions/setup-go@v1 with: - go-version: 1.14.x + go-version: 1.19.x - name: Checkout code uses: actions/checkout@v2