From b4368b2a2ca4103b1ff4e37c34a963127342747e Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 30 Jun 2021 09:25:19 +0100 Subject: [PATCH 001/205] 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 002/205] 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 003/205] 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 004/205] 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 005/205] 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 006/205] 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 97403a10190e619c299b8f12973663716f54ded0 Mon Sep 17 00:00:00 2001 From: Pascal Hofmann Date: Fri, 30 Jul 2021 14:48:43 +0200 Subject: [PATCH 007/205] _examples: Remove wrong comment --- _examples/clone/auth/ssh/main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/_examples/clone/auth/ssh/main.go b/_examples/clone/auth/ssh/main.go index 1e06e44b8..5f21d9076 100644 --- a/_examples/clone/auth/ssh/main.go +++ b/_examples/clone/auth/ssh/main.go @@ -32,9 +32,6 @@ func main() { } r, err := git.PlainClone(directory, false, &git.CloneOptions{ - // The intended use of a GitHub personal access token is in replace of your password - // because access tokens can easily be revoked. - // https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ Auth: publicKeys, URL: url, Progress: os.Stdout, From 5e64929185218f53cb45b0f89fb372e06aa172be Mon Sep 17 00:00:00 2001 From: Norwin Date: Wed, 15 Sep 2021 15:34:17 +0200 Subject: [PATCH 008/205] 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 009/205] 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 010/205] 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 011/205] 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 012/205] 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 013/205] 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 014/205] 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 015/205] 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 016/205] 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 017/205] 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 018/205] 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 019/205] 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 020/205] 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 021/205] 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 022/205] 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 023/205] 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 024/205] 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 025/205] 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 026/205] 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 027/205] 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 028/205] 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 029/205] 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 030/205] 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 031/205] 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 032/205] 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 033/205] 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 034/205] 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 035/205] 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 036/205] 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 037/205] 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 5882d60fb7ccd4cfc0fe69286aa96e198c9d1eb0 Mon Sep 17 00:00:00 2001 From: John Cai Date: Mon, 3 Jan 2022 15:40:28 -0500 Subject: [PATCH 038/205] Add Amend option to CommitOptions Adds an Amend option to CommitOptions that behaves like git --amend. This change includes modifications to the Validate function to disallow a Commit call with both Amend and either Parents or All enabled. --- options.go | 11 +++++++++++ worktree_commit.go | 41 +++++++++++++++++++++++++++++------------ worktree_commit_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/options.go b/options.go index d6f0e9f11..a2d62bd3d 100644 --- a/options.go +++ b/options.go @@ -471,10 +471,21 @@ type CommitOptions struct { // commit will not be signed. The private key must be present and already // decrypted. SignKey *openpgp.Entity + // Amend will create a new commit object and replace the commit that HEAD currently + // points to. Cannot be used with All nor Parents. + Amend bool } // Validate validates the fields and sets the default values. func (o *CommitOptions) Validate(r *Repository) error { + if o.All && o.Amend { + return errors.New("all and amend cannot be used together") + } + + if o.Amend && len(o.Parents) > 0 { + return errors.New("parents cannot be used with amend") + } + if o.Author == nil { if err := o.loadConfigAuthorAndCommitter(r); err != nil { return err diff --git a/worktree_commit.go b/worktree_commit.go index dc7956909..86320d848 100644 --- a/worktree_commit.go +++ b/worktree_commit.go @@ -29,22 +29,39 @@ func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error } } - idx, err := w.r.Storer.Index() - if err != nil { - return plumbing.ZeroHash, err - } + var treeHash plumbing.Hash - h := &buildTreeHelper{ - fs: w.Filesystem, - s: w.r.Storer, - } + if opts.Amend { + head, err := w.r.Head() + if err != nil { + return plumbing.ZeroHash, err + } - tree, err := h.BuildTree(idx) - if err != nil { - return plumbing.ZeroHash, err + t, err := w.getTreeFromCommitHash(head.Hash()) + if err != nil { + return plumbing.ZeroHash, err + } + + treeHash = t.Hash + opts.Parents = []plumbing.Hash{head.Hash()} + }else { + idx, err := w.r.Storer.Index() + if err != nil { + return plumbing.ZeroHash, err + } + + h := &buildTreeHelper{ + fs: w.Filesystem, + s: w.r.Storer, + } + + treeHash, err = h.BuildTree(idx) + if err != nil { + return plumbing.ZeroHash, err + } } - commit, err := w.buildCommitObject(msg, opts, tree) + commit, err := w.buildCommitObject(msg, opts, treeHash) if err != nil { return plumbing.ZeroHash, err } diff --git a/worktree_commit_test.go b/worktree_commit_test.go index 097c6e5d3..547c82031 100644 --- a/worktree_commit_test.go +++ b/worktree_commit_test.go @@ -89,6 +89,37 @@ func (s *WorktreeSuite) TestCommitParent(c *C) { assertStorageStatus(c, s.Repository, 13, 11, 10, expected) } +func (s *WorktreeSuite) TestCommitAmend(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{}) + c.Assert(err, IsNil) + + util.WriteFile(fs, "foo", []byte("foo"), 0644) + + _, err = w.Add("foo") + c.Assert(err, IsNil) + + _, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature()}) + c.Assert(err, IsNil) + + + amendedHash, err := w.Commit("bar\n", &CommitOptions{Amend: true}) + c.Assert(err, IsNil) + + headRef, err := w.r.Head() + c.Assert(amendedHash, Equals, headRef.Hash()) + commit, err := w.r.CommitObject(headRef.Hash()) + c.Assert(err, IsNil) + c.Assert(commit.Message, Equals, "bar\n") + + assertStorageStatus(c, s.Repository, 13, 11, 11, amendedHash) +} + func (s *WorktreeSuite) TestCommitAll(c *C) { expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28") From bf57a4c6a53423d92aeeadf1bc02877cc80459d4 Mon Sep 17 00:00:00 2001 From: "paul.t" Date: Wed, 5 Jan 2022 06:54:15 +0000 Subject: [PATCH 039/205] 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 040/205] 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 041/205] 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 042/205] 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 043/205] 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 044/205] 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 045/205] 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 046/205] 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 047/205] 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 048/205] 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 049/205] 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 050/205] 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 051/205] *: 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 052/205] *: 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 053/205] 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 054/205] 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 055/205] 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 056/205] 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 057/205] 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 058/205] 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 059/205] 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 060/205] 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 061/205] .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 062/205] 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 063/205] 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 064/205] 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 From a0b612a537f499b2cc9bddafac625e12c73bd3f5 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 2 Dec 2022 15:38:58 +0000 Subject: [PATCH 065/205] build: Add CI check for CGO_ENABLED=0 Some applications that depend on go-git may not need to build using CGO. The new test ensures no new regressions going forwards. Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index fef212763..ba664a2f4 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -38,3 +38,8 @@ jobs: - name: Test run: make test-coverage + + - name: Build go-git with CGO disabled + run: go build ./... + env: + CGO_ENABLED: 0 From 223e7321561147bb4f56e442ca8f84b7453e131e Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sat, 3 Dec 2022 13:19:01 +0000 Subject: [PATCH 066/205] build: Bump github.com/pjbgf/sha1cd to v0.2.3 Fixes regression in which applications that depend on go-git could no longer build with CGO_ENABLED=0 or when vendoring dependencies. Signed-off-by: Paulo Gomes --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b214c8a8d..9a9fa195b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,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 v1.2.0 - github.com/pjbgf/sha1cd v0.2.0 + github.com/pjbgf/sha1cd v0.2.3 github.com/pkg/errors v0.9.1 // indirect github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.1.0 diff --git a/go.sum b/go.sum index 2c00f0092..35b347b17 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,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/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= +github.com/pjbgf/sha1cd v0.2.3/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= From a513415283c4628259c016587858fe56d7b0fa13 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sat, 3 Dec 2022 16:34:35 +0000 Subject: [PATCH 067/205] Return error instead of creating empty commits BuildTree now returns an ErrEmptyCommit error, when there are no changes to be committed. This can be opted-out via CommitOptions.AllowEmptyCommits. This is a breaking change which enables applications to detect when empty commits are to be created. Some instances in which this can occur is when the fs (e.g. `billy/osfs`) make changes to the underlying files, causing a conflict between what the previous Git worktree state was, and the current state. Changes to the fs implementations are orthogonal to this, and will be dealt with separately. The new behaviour aligns with the Git CLI, in which empty commits returns the error message: 'nothing to commit, working tree clean'. Signed-off-by: Paulo Gomes --- options.go | 4 ++++ worktree_commit.go | 15 +++++++++++++-- worktree_commit_test.go | 26 +++++++++++++++++++++++++- worktree_test.go | 36 +++++++++++++++++++++++++++++++++--- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/options.go b/options.go index 7e5c1b434..747d512bc 100644 --- a/options.go +++ b/options.go @@ -458,6 +458,10 @@ type CommitOptions struct { // All automatically stage files that have been modified and deleted, but // new files you have not told Git about are not affected. All bool + // AllowEmptyCommits enable empty commits to be created. An empty commit + // is when no changes to the tree were made, but a new commit message is + // provided. The default behavior is false, which results in ErrEmptyCommit. + AllowEmptyCommits bool // Author is the author's signature of the commit. If Author is empty the // Name and Email is read from the config, and time.Now it's used as When. Author *object.Signature diff --git a/worktree_commit.go b/worktree_commit.go index dc7956909..e9277217a 100644 --- a/worktree_commit.go +++ b/worktree_commit.go @@ -2,6 +2,7 @@ package git import ( "bytes" + "errors" "path" "sort" "strings" @@ -16,6 +17,12 @@ import ( "github.com/go-git/go-billy/v5" ) +var ( + // ErrEmptyCommit occurs when a commit is attempted using a clean + // working tree, with no changes to be committed. + ErrEmptyCommit = errors.New("cannot create empty commit: clean working tree") +) + // Commit stores the current contents of the index in a new commit along with // a log message from the user describing the changes. func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error) { @@ -39,7 +46,7 @@ func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error s: w.r.Storer, } - tree, err := h.BuildTree(idx) + tree, err := h.BuildTree(idx, opts) if err != nil { return plumbing.ZeroHash, err } @@ -145,7 +152,11 @@ type buildTreeHelper struct { // BuildTree builds the tree objects and push its to the storer, the hash // of the root tree is returned. -func (h *buildTreeHelper) BuildTree(idx *index.Index) (plumbing.Hash, error) { +func (h *buildTreeHelper) BuildTree(idx *index.Index, opts *CommitOptions) (plumbing.Hash, error) { + if len(idx.Entries) == 0 && (opts == nil || !opts.AllowEmptyCommits) { + return plumbing.ZeroHash, ErrEmptyCommit + } + const rootNode = "" h.trees = map[string]*object.Tree{rootNode: {}} h.entries = map[string]*object.TreeEntry{} diff --git a/worktree_commit_test.go b/worktree_commit_test.go index 097c6e5d3..bfeb81dca 100644 --- a/worktree_commit_test.go +++ b/worktree_commit_test.go @@ -26,12 +26,18 @@ import ( ) func (s *WorktreeSuite) TestCommitEmptyOptions(c *C) { - r, err := Init(memory.NewStorage(), memfs.New()) + fs := memfs.New() + r, err := Init(memory.NewStorage(), fs) c.Assert(err, IsNil) w, err := r.Worktree() c.Assert(err, IsNil) + util.WriteFile(fs, "foo", []byte("foo"), 0644) + + _, err = w.Add("foo") + c.Assert(err, IsNil) + hash, err := w.Commit("foo", &CommitOptions{}) c.Assert(err, IsNil) c.Assert(hash.IsZero(), Equals, false) @@ -65,6 +71,24 @@ func (s *WorktreeSuite) TestCommitInitial(c *C) { assertStorageStatus(c, r, 1, 1, 1, expected) } +func (s *WorktreeSuite) TestNothingToCommit(c *C) { + expected := plumbing.NewHash("838ea833ce893e8555907e5ef224aa076f5e274a") + + r, err := Init(memory.NewStorage(), memfs.New()) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + hash, err := w.Commit("failed empty commit\n", &CommitOptions{Author: defaultSignature()}) + c.Assert(hash, Equals, plumbing.ZeroHash) + c.Assert(err, Equals, ErrEmptyCommit) + + hash, err = w.Commit("enable empty commits\n", &CommitOptions{Author: defaultSignature(), AllowEmptyCommits: true}) + c.Assert(hash, Equals, expected) + c.Assert(err, IsNil) +} + func (s *WorktreeSuite) TestCommitParent(c *C) { expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22") diff --git a/worktree_test.go b/worktree_test.go index 4a1412693..4c0633355 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -3,7 +3,6 @@ package git import ( "bytes" "context" - "errors" "io" "io/ioutil" "os" @@ -2167,6 +2166,8 @@ func (s *WorktreeSuite) TestGrep(c *C) { } func (s *WorktreeSuite) TestAddAndCommit(c *C) { + expectedFiles := 2 + dir, clean := s.TemporalDir() defer clean() @@ -2176,17 +2177,23 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) { w, err := repo.Worktree() c.Assert(err, IsNil) + os.WriteFile(filepath.Join(dir, "foo"), []byte("bar"), 0o644) + os.WriteFile(filepath.Join(dir, "bar"), []byte("foo"), 0o644) + _, err = w.Add(".") c.Assert(err, IsNil) - w.Commit("Test Add And Commit", &CommitOptions{Author: &object.Signature{ + _, err = w.Commit("Test Add And Commit", &CommitOptions{Author: &object.Signature{ Name: "foo", Email: "foo@foo.foo", When: time.Now(), }}) + c.Assert(err, IsNil) iter, err := w.r.Log(&LogOptions{}) c.Assert(err, IsNil) + + filesFound := 0 err = iter.ForEach(func(c *object.Commit) error { files, err := c.Files() if err != nil { @@ -2194,11 +2201,34 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) { } err = files.ForEach(func(f *object.File) error { - return errors.New("Expected no files, got at least 1") + filesFound++ + return nil }) return err }) c.Assert(err, IsNil) + c.Assert(filesFound, Equals, expectedFiles) +} + +func (s *WorktreeSuite) TestAddAndCommitEmpty(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + repo, err := PlainInit(dir, false) + c.Assert(err, IsNil) + + w, err := repo.Worktree() + c.Assert(err, IsNil) + + _, err = w.Add(".") + c.Assert(err, IsNil) + + _, err = w.Commit("Test Add And Commit", &CommitOptions{Author: &object.Signature{ + Name: "foo", + Email: "foo@foo.foo", + When: time.Now(), + }}) + c.Assert(err, Equals, ErrEmptyCommit) } func (s *WorktreeSuite) TestLinkedWorktree(c *C) { From 08db65ff4f94240b4cd31e009df7bc6b84c24fcf Mon Sep 17 00:00:00 2001 From: doxsch <28098153+doxsch@users.noreply.github.com> Date: Mon, 5 Dec 2022 12:13:13 +0100 Subject: [PATCH 068/205] fix: Upgrade github.com/xanzy/ssh-agent to v0.3.3 to fix panic --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b214c8a8d..3e271b141 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( 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.2 + github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.3.0 golang.org/x/net v0.2.0 golang.org/x/sys v0.2.0 diff --git a/go.sum b/go.sum index 2c00f0092..e2915e51a 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ 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.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= -github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/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= From 736622fb5d9a058a887667d57c91dc1b58aca6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Sun, 11 Dec 2022 11:52:06 +0100 Subject: [PATCH 069/205] .github: test, remove coveralls --- .github/workflows/test.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cefbbdf6e..bbe531e5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,18 +31,3 @@ jobs: - name: Test run: make test-coverage - - - name: Convert coverage to lcov - 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.19.x' - uses: coverallsapp/github-action@master - continue-on-error: true - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: coverage.lcov From 0162fb17c41753535d1eaabfcaf5af72fd6210e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Thu, 5 Jan 2023 08:21:51 +0100 Subject: [PATCH 070/205] go.mod: update go-billy v5.4.0, removes races --- go.mod | 4 ++-- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2b8239e51..be60e7e83 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( 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-billy/v5 v5.4.0 github.com/go-git/go-git-fixtures/v4 v4.3.1 github.com/google/go-cmp v0.5.9 github.com/imdario/mergo v0.3.13 @@ -21,7 +21,7 @@ require ( github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.3.0 golang.org/x/net v0.2.0 - golang.org/x/sys v0.2.0 + golang.org/x/sys v0.3.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 e7e0c4de5..d26ddf19d 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,9 @@ 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-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQBDE= +github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= 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.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -95,8 +96,9 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.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= From 5dabd83e3712e2554745c736b55df405a0ba4f33 Mon Sep 17 00:00:00 2001 From: Taketoshi Fujiwara Date: Thu, 5 Jan 2023 18:35:52 +0900 Subject: [PATCH 071/205] Worktree: Add, fix add removed files. Fixes #223 (#652) --- worktree_status.go | 41 +++++++++++++++++++++++++---------------- worktree_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/worktree_status.go b/worktree_status.go index c639f1320..f3091cff6 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -270,10 +270,6 @@ func (w *Worktree) Add(path string) (plumbing.Hash, error) { } func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, ignorePattern []gitignore.Pattern) (added bool, err error) { - files, err := w.Filesystem.ReadDir(directory) - if err != nil { - return false, err - } if len(ignorePattern) > 0 { m := gitignore.NewMatcher(ignorePattern) matchPath := strings.Split(directory, string(os.PathSeparator)) @@ -283,20 +279,13 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, } } - for _, file := range files { - name := path.Join(directory, file.Name()) - - var a bool - if file.IsDir() { - if file.Name() == GitDirName { - // ignore special git directory - continue - } - a, err = w.doAddDirectory(idx, s, name, ignorePattern) - } else { - a, _, err = w.doAddFile(idx, s, name, ignorePattern) + for name := range s { + if !isPathInDirectory(name, filepath.ToSlash(filepath.Clean(directory))) { + continue } + var a bool + a, _, err = w.doAddFile(idx, s, name, ignorePattern) if err != nil { return } @@ -309,6 +298,26 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, return } +func isPathInDirectory(path, directory string) bool { + ps := strings.Split(path, "/") + ds := strings.Split(directory, "/") + + if len(ds) == 1 && ds[0] == "." { + return true + } + + if len(ps) < len(ds) { + return false + } + + for i := 0; i < len(ds); i++ { + if ps[i] != ds[i] { + return false + } + } + return true +} + // AddWithOptions file contents to the index, updates the index using the // current content found in the working tree, to prepare the content staged for // the next commit. diff --git a/worktree_test.go b/worktree_test.go index 4c0633355..d545b01eb 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -1398,6 +1398,51 @@ func (s *WorktreeSuite) TestAddRemoved(c *C) { c.Assert(file.Staging, Equals, Deleted) } +func (s *WorktreeSuite) TestAddRemovedInDirectory(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = w.Filesystem.Remove("go/example.go") + c.Assert(err, IsNil) + + err = w.Filesystem.Remove("json/short.json") + c.Assert(err, IsNil) + + hash, err := w.Add("go") + c.Assert(err, IsNil) + c.Assert(hash.IsZero(), Equals, true) + + e, err := idx.Entry("go/example.go") + c.Assert(err, IsNil) + c.Assert(e.Hash, Equals, plumbing.NewHash("880cd14280f4b9b6ed3986d6671f907d7cc2a198")) + c.Assert(e.Mode, Equals, filemode.Regular) + + e, err = idx.Entry("json/short.json") + c.Assert(err, IsNil) + c.Assert(e.Hash, Equals, plumbing.NewHash("c8f1d8c61f9da76f4cb49fd86322b6e685dba956")) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 2) + + file := status.File("go/example.go") + c.Assert(file.Staging, Equals, Deleted) + + file = status.File("json/short.json") + c.Assert(file.Staging, Equals, Unmodified) +} + func (s *WorktreeSuite) TestAddSymlink(c *C) { dir, clean := s.TemporalDir() defer clean() From cfec9e38e6519e80e1e3ac3de224ffd33997bcea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Sat, 14 Jan 2023 12:52:56 +0100 Subject: [PATCH 072/205] dotgit: fix a filesystem race in Refs/walkReferencesTree In walkReferencesTree(), the filesystem is browsed recursively. After a folder is listed, each child element (directory, file) are inspected. What can happen is that by the time we get to operate on the child element, it might have been deleted from the filesystem and we would get a PathError. In the case of directories there was already a case to avoid bubbling up the error (we consider that there is no ref there, which is correct), but that was missing for files. This commit just apply the same logic. --- storage/filesystem/dotgit/dotgit.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go index 6c386f799..2be2bae3e 100644 --- a/storage/filesystem/dotgit/dotgit.go +++ b/storage/filesystem/dotgit/dotgit.go @@ -943,6 +943,7 @@ func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []strin files, err := d.fs.ReadDir(d.fs.Join(relPath...)) if err != nil { if os.IsNotExist(err) { + // a race happened, and our directory is gone now return nil } @@ -960,6 +961,10 @@ func (d *DotGit) walkReferencesTree(refs *[]*plumbing.Reference, relPath []strin } ref, err := d.readReferenceFile(".", strings.Join(newRelPath, "/")) + if os.IsNotExist(err) { + // a race happened, and our file is gone now + continue + } if err != nil { return err } From f848aaf02ab0cb9a41cef3f457c45a93e2265d76 Mon Sep 17 00:00:00 2001 From: mbohy Date: Sat, 28 Jan 2023 19:42:46 +0000 Subject: [PATCH 073/205] git: worktree: check for empty parent dirs during Reset (Fixes #670) (#671) When we delete dir1/dir2/file1, we currently check if dir2 becomes empty with the deletion of file1, and if so, we delete dir2. If dir1 becomes empty with the deletion of dir2, we don't notice that, and dir1 is left behind. This commit adds a loop to check each parent directory in the file path for emptiness, removing empty directories along the way until a non-empty directory is found (or an error occurs). --- worktree.go | 49 ++++++++++++++++++++++++++++++++++++---------- worktree_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/worktree.go b/worktree.go index 02f90a9e6..d28ba3241 100644 --- a/worktree.go +++ b/worktree.go @@ -410,7 +410,7 @@ func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *ind isSubmodule = e.Mode == filemode.Submodule case merkletrie.Delete: - return rmFileAndDirIfEmpty(w.Filesystem, ch.From.String()) + return rmFileAndDirsIfEmpty(w.Filesystem, ch.From.String()) } if isSubmodule { @@ -778,8 +778,10 @@ func (w *Worktree) doClean(status Status, opts *CleanOptions, dir string, files } if opts.Dir && dir != "" { - return doCleanDirectories(w.Filesystem, dir) + _, err := removeDirIfEmpty(w.Filesystem, dir) + return err } + return nil } @@ -920,25 +922,52 @@ func findMatchInFile(file *object.File, treeName string, opts *GrepOptions) ([]G return grepResults, nil } -func rmFileAndDirIfEmpty(fs billy.Filesystem, name string) error { +// will walk up the directory tree removing all encountered empty +// directories, not just the one containing this file +func rmFileAndDirsIfEmpty(fs billy.Filesystem, name string) error { if err := util.RemoveAll(fs, name); err != nil { return err } dir := filepath.Dir(name) - return doCleanDirectories(fs, dir) + for { + removed, err := removeDirIfEmpty(fs, dir) + if err != nil { + return err + } + + if !removed { + // directory was not empty and not removed, + // stop checking parents + break + } + + // move to parent directory + dir = filepath.Dir(dir) + } + + return nil } -// doCleanDirectories removes empty subdirs (without files) -func doCleanDirectories(fs billy.Filesystem, dir string) error { +// removeDirIfEmpty will remove the supplied directory `dir` if +// `dir` is empty +// returns true if the directory was removed +func removeDirIfEmpty(fs billy.Filesystem, dir string) (bool, error) { files, err := fs.ReadDir(dir) if err != nil { - return err + return false, err } - if len(files) == 0 { - return fs.Remove(dir) + + if len(files) > 0 { + return false, nil } - return nil + + err = fs.Remove(dir) + if err != nil { + return false, err + } + + return true, nil } type indexBuilder struct { diff --git a/worktree_test.go b/worktree_test.go index d545b01eb..b57a77dbf 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -3,6 +3,7 @@ package git import ( "bytes" "context" + "errors" "io" "io/ioutil" "os" @@ -2210,6 +2211,56 @@ func (s *WorktreeSuite) TestGrep(c *C) { } } +func (s *WorktreeSuite) TestResetLingeringDirectories(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + commitOpts := &CommitOptions{Author: &object.Signature{ + Name: "foo", + Email: "foo@foo.foo", + When: time.Now(), + }} + + repo, err := PlainInit(dir, false) + c.Assert(err, IsNil) + + w, err := repo.Worktree() + c.Assert(err, IsNil) + + os.WriteFile(filepath.Join(dir, "README"), []byte("placeholder"), 0o644) + + _, err = w.Add(".") + c.Assert(err, IsNil) + + initialHash, err := w.Commit("Initial commit", commitOpts) + c.Assert(err, IsNil) + + os.MkdirAll(filepath.Join(dir, "a", "b"), 0o755) + os.WriteFile(filepath.Join(dir, "a", "b", "1"), []byte("1"), 0o644) + + _, err = w.Add(".") + c.Assert(err, IsNil) + + _, err = w.Commit("Add file in nested sub-directories", commitOpts) + c.Assert(err, IsNil) + + // reset to initial commit, which should remove a/b/1, a/b, and a + err = w.Reset(&ResetOptions{ + Commit: initialHash, + Mode: HardReset, + }) + c.Assert(err, IsNil) + + _, err = os.Stat(filepath.Join(dir, "a", "b", "1")) + c.Assert(errors.Is(err, os.ErrNotExist), Equals, true) + + _, err = os.Stat(filepath.Join(dir, "a", "b")) + c.Assert(errors.Is(err, os.ErrNotExist), Equals, true) + + _, err = os.Stat(filepath.Join(dir, "a")) + c.Assert(errors.Is(err, os.ErrNotExist), Equals, true) +} + func (s *WorktreeSuite) TestAddAndCommit(c *C) { expectedFiles := 2 From 7ab4957732a817bada223e5c361f0c9753d9e40c Mon Sep 17 00:00:00 2001 From: Maximo Cuadros Date: Sun, 5 Feb 2023 12:26:55 +0100 Subject: [PATCH 074/205] ci: update go version --- .github/workflows/git.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index ba664a2f4..fcec675eb 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.19.x + go-version: 1.20.x - name: Checkout code uses: actions/checkout@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bbe531e5a..b576d386e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.18.x, 1.19.x] + go-version: [1.19.x, 1.20.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} From c2958bf730224cc3672e2105faa1879ec5f21cee Mon Sep 17 00:00:00 2001 From: ThinkChaos Date: Fri, 17 Feb 2023 16:15:22 -0500 Subject: [PATCH 075/205] fix: don't use the `firstErrLine` when it is empty Returning `nil` causes `handleAdvRefDecodeError` to fall back to `io.ErrUnexpectedEOF`. --- plumbing/transport/internal/common/common.go | 2 +- plumbing/transport/internal/common/common_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index d0e9a2974..b2c2fee38 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -374,7 +374,7 @@ func (s *session) checkNotFoundError() error { case <-t.C: return ErrTimeoutExceeded case line, ok := <-s.firstErrLine: - if !ok { + if !ok || len(line) == 0 { return nil } diff --git a/plumbing/transport/internal/common/common_test.go b/plumbing/transport/internal/common/common_test.go index c60ef3b05..affa78706 100644 --- a/plumbing/transport/internal/common/common_test.go +++ b/plumbing/transport/internal/common/common_test.go @@ -76,3 +76,17 @@ func (s *CommonSuite) TestIsRepoNotFoundErrorForGogsAccessDenied(c *C) { c.Assert(isRepoNotFound, Equals, true) } + +func (s *CommonSuite) TestCheckNotFoundError(c *C) { + firstErrLine := make(chan string, 1) + + session := session{ + firstErrLine: firstErrLine, + } + + firstErrLine <- "" + + err := session.checkNotFoundError() + + c.Assert(err, IsNil) +} From bd33c95fc3cbe8c04dd084c22defd1aa3c3e43dc Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sat, 25 Feb 2023 13:32:17 +0000 Subject: [PATCH 076/205] Remove need to build with CGO Follow-up from #618, at the time the Pure Go sha1cd implementation was not performant enough to be the default. This has now changed and the cgo and generic implementations yields similar results. Users are able to override the default implementation, however this seems to be a better default as it does not require the use of CGO during build time. Signed-off-by: Paulo Gomes --- go.mod | 2 +- go.sum | 13 +++++++++++-- plumbing/hash/hash.go | 6 ++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index be60e7e83..c46d2446a 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,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 v1.2.0 - github.com/pjbgf/sha1cd v0.2.3 + github.com/pjbgf/sha1cd v0.3.0 github.com/pkg/errors v0.9.1 // indirect github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.1.0 diff --git a/go.sum b/go.sum index d26ddf19d..536173547 100644 --- a/go.sum +++ b/go.sum @@ -45,9 +45,10 @@ 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/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= -github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 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= @@ -65,19 +66,23 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 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/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 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/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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 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= @@ -96,12 +101,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.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.1.0/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= @@ -113,6 +120,7 @@ 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/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 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= @@ -126,3 +134,4 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/plumbing/hash/hash.go b/plumbing/hash/hash.go index fe3bf7655..80e4b5f25 100644 --- a/plumbing/hash/hash.go +++ b/plumbing/hash/hash.go @@ -7,7 +7,7 @@ import ( "fmt" "hash" - "github.com/pjbgf/sha1cd/cgo" + "github.com/pjbgf/sha1cd" ) // algos is a map of hash algorithms. @@ -20,9 +20,7 @@ func init() { // 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 + algos[crypto.SHA1] = sha1cd.New } // RegisterHash allows for the hash algorithm used to be overriden. From f1dc529fac28e6c45882292184270f94b5d30b7f Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 27 Feb 2023 21:42:45 +0100 Subject: [PATCH 077/205] plumbing: support SSH/X509 signed tags This commit enables support for extracting the SSH and X509 signatures from (annotated) Git tags, as an initial step to support the verification of more signatures than just PGP in go-git. The ported logic from Git further ensures that we look for a signature at the tail of an annotation, instead of the first signature we find in the annotation, as this could theoretically result in a faulty signature getting detected if part of a an annotation itself (e.g. by being placed in the middle as part of an inherited message). For commits, no further change is required as the current extraction of any signature (format) from `gpgsig` in the commit header is sufficient for manual verification. In a future iteration, we could add `signature/ssh` and `signature/x509` packages to further enable people to deal with verifying other signatures than PGP. As well as adding additional methods to `Commit` and `Tag` to provide glue between the packages and the most prominent user-facing APIs. Signed-off-by: Hidde Beydals --- plumbing/object/signature.go | 101 +++++++++++++++++ plumbing/object/signature_test.go | 180 ++++++++++++++++++++++++++++++ plumbing/object/tag.go | 37 +----- plumbing/object/tag_test.go | 21 ++++ 4 files changed, 307 insertions(+), 32 deletions(-) create mode 100644 plumbing/object/signature.go create mode 100644 plumbing/object/signature_test.go diff --git a/plumbing/object/signature.go b/plumbing/object/signature.go new file mode 100644 index 000000000..91cf371f0 --- /dev/null +++ b/plumbing/object/signature.go @@ -0,0 +1,101 @@ +package object + +import "bytes" + +const ( + signatureTypeUnknown signatureType = iota + signatureTypeOpenPGP + signatureTypeX509 + signatureTypeSSH +) + +var ( + // openPGPSignatureFormat is the format of an OpenPGP signature. + openPGPSignatureFormat = signatureFormat{ + []byte("-----BEGIN PGP SIGNATURE-----"), + []byte("-----BEGIN PGP MESSAGE-----"), + } + // x509SignatureFormat is the format of an X509 signature, which is + // a PKCS#7 (S/MIME) signature. + x509SignatureFormat = signatureFormat{ + []byte("-----BEGIN CERTIFICATE-----"), + } + + // sshSignatureFormat is the format of an SSH signature. + sshSignatureFormat = signatureFormat{ + []byte("-----BEGIN SSH SIGNATURE-----"), + } +) + +var ( + // knownSignatureFormats is a map of known signature formats, indexed by + // their signatureType. + knownSignatureFormats = map[signatureType]signatureFormat{ + signatureTypeOpenPGP: openPGPSignatureFormat, + signatureTypeX509: x509SignatureFormat, + signatureTypeSSH: sshSignatureFormat, + } +) + +// signatureType represents the type of the signature. +type signatureType int8 + +// signatureFormat represents the beginning of a signature. +type signatureFormat [][]byte + +// typeForSignature returns the type of the signature based on its format. +func typeForSignature(b []byte) signatureType { + for t, i := range knownSignatureFormats { + for _, begin := range i { + if bytes.HasPrefix(b, begin) { + return t + } + } + } + return signatureTypeUnknown +} + +// parseSignedBytes returns the position of the last signature block found in +// the given bytes. If no signature block is found, it returns -1. +// +// When multiple signature blocks are found, the position of the last one is +// returned. Any tailing bytes after this signature block start should be +// considered part of the signature. +// +// Given this, it would be safe to use the returned position to split the bytes +// into two parts: the first part containing the message, the second part +// containing the signature. +// +// Example: +// +// message := []byte(`Message with signature +// +// -----BEGIN SSH SIGNATURE----- +// ...`) +// +// var signature string +// if pos, _ := parseSignedBytes(message); pos != -1 { +// signature = string(message[pos:]) +// message = message[:pos] +// } +// +// This logic is on par with git's gpg-interface.c:parse_signed_buffer(). +// https://github.com/git/git/blob/7c2ef319c52c4997256f5807564523dfd4acdfc7/gpg-interface.c#L668 +func parseSignedBytes(b []byte) (int, signatureType) { + var n, match = 0, -1 + var t signatureType + for n < len(b) { + var i = b[n:] + if st := typeForSignature(i); st != signatureTypeUnknown { + match = n + t = st + } + if eol := bytes.IndexByte(i, '\n'); eol >= 0 { + n += eol + 1 + continue + } + // If we reach this point, we've reached the end. + break + } + return match, t +} diff --git a/plumbing/object/signature_test.go b/plumbing/object/signature_test.go new file mode 100644 index 000000000..1bdb1d1ca --- /dev/null +++ b/plumbing/object/signature_test.go @@ -0,0 +1,180 @@ +package object + +import ( + "bytes" + "testing" +) + +func Test_typeForSignature(t *testing.T) { + tests := []struct { + name string + b []byte + want signatureType + }{ + { + name: "known signature format (PGP)", + b: []byte(`-----BEGIN PGP SIGNATURE----- + +iHUEABYKAB0WIQTMqU0ycQ3f6g3PMoWMmmmF4LuV8QUCYGebVwAKCRCMmmmF4LuV +8VtyAP9LbuXAhtK6FQqOjKybBwlV70rLcXVP24ubDuz88VVwSgD+LuObsasWq6/U +TssDKHUR2taa53bQYjkZQBpvvwOrLgc= +=YQUf +-----END PGP SIGNATURE-----`), + want: signatureTypeOpenPGP, + }, + { + name: "known signature format (SSH)", + b: []byte(`-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE-----`), + want: signatureTypeSSH, + }, + { + name: "known signature format (X509)", + b: []byte(`-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIJALZ9Z3Z9Z3Z9MA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD +VQQGEwJTRTEOMAwGA1UECAwFVGV4YXMxDjAMBgNVBAcMBVRleGFzMQ4wDAYDVQQK +DAVUZXhhczEOMAwGA1UECwwFVGV4YXMxGDAWBgNVBAMMD1RleGFzIENlcnRpZmlj +YXRlMB4XDTE3MDUyNjE3MjY0MloXDTI3MDUyNDE3MjY0MlowgYgxCzAJBgNVBAYT +AlNFMQ4wDAYDVQQIDAVUZXhhczEOMAwGA1UEBwwFVGV4YXMxDjAMBgNVBAoMBVRl +eGFzMQ4wDAYDVQQLDAVUZXhhczEYMBYGA1UEAwwPVGV4YXMgQ2VydGlmaWNhdGUw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQZ9Z3Z9Z3Z9Z3Z9Z3Z9Z3 +-----END CERTIFICATE-----`), + want: signatureTypeX509, + }, + { + name: "unknown signature format", + b: []byte(`-----BEGIN ARBITRARY SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +-----END UNKNOWN SIGNATURE-----`), + want: signatureTypeUnknown, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := typeForSignature(tt.b); got != tt.want { + t.Errorf("typeForSignature() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_parseSignedBytes(t *testing.T) { + tests := []struct { + name string + b []byte + wantSignature []byte + wantType signatureType + }{ + { + name: "detects signature and type", + b: []byte(`signed tag +-----BEGIN PGP SIGNATURE----- + +iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop +TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un +61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI +BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN +hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3 +FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI +gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o +Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV +pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J +sZC//k6m +=VhHy +-----END PGP SIGNATURE-----`), + wantSignature: []byte(`-----BEGIN PGP SIGNATURE----- + +iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop +TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un +61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI +BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN +hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3 +FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI +gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o +Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV +pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J +sZC//k6m +=VhHy +-----END PGP SIGNATURE-----`), + wantType: signatureTypeOpenPGP, + }, + { + name: "last signature for multiple signatures", + b: []byte(`signed tag +-----BEGIN PGP SIGNATURE----- + +iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop +TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un +61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI +BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN +hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3 +FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI +gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o +Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV +pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J +sZC//k6m +=VhHy +-----END PGP SIGNATURE----- +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE-----`), + wantSignature: []byte(`-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE-----`), + wantType: signatureTypeSSH, + }, + { + name: "signature with trailing data", + b: []byte(`An invalid + +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE----- + +signed tag`), + wantSignature: []byte(`-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE----- + +signed tag`), + wantType: signatureTypeSSH, + }, + { + name: "data without signature", + b: []byte(`Some message`), + wantSignature: []byte(``), + wantType: signatureTypeUnknown, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pos, st := parseSignedBytes(tt.b) + var signature []byte + if pos >= 0 { + signature = tt.b[pos:] + } + if !bytes.Equal(signature, tt.wantSignature) { + t.Errorf("parseSignedBytes() got = %s for pos = %v, want %s", signature, pos, tt.wantSignature) + } + if st != tt.wantType { + t.Errorf("parseSignedBytes() got1 = %v, want %v", st, tt.wantType) + } + }) + } +} diff --git a/plumbing/object/tag.go b/plumbing/object/tag.go index 84066f768..cf46c08e1 100644 --- a/plumbing/object/tag.go +++ b/plumbing/object/tag.go @@ -4,11 +4,9 @@ import ( "bytes" "fmt" "io" - stdioutil "io/ioutil" "strings" "github.com/ProtonMail/go-crypto/openpgp" - "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" @@ -128,40 +126,15 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) { } } - data, err := stdioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return err } - - var pgpsig bool - // Check if data contains PGP signature. - if bytes.Contains(data, []byte(beginpgp)) { - // Split the lines at newline. - messageAndSig := bytes.Split(data, []byte("\n")) - - for _, l := range messageAndSig { - if pgpsig { - if bytes.Contains(l, []byte(endpgp)) { - t.PGPSignature += endpgp + "\n" - break - } else { - t.PGPSignature += string(l) + "\n" - } - continue - } - - // Check if it's the beginning of a PGP signature. - if bytes.Contains(l, []byte(beginpgp)) { - t.PGPSignature += beginpgp + "\n" - pgpsig = true - continue - } - - t.Message += string(l) + "\n" - } - } else { - t.Message = string(data) + if sm, _ := parseSignedBytes(data); sm >= 0 { + t.PGPSignature = string(data[sm:]) + data = data[:sm] } + t.Message = string(data) return nil } diff --git a/plumbing/object/tag_test.go b/plumbing/object/tag_test.go index cd1d15d1f..15b943e07 100644 --- a/plumbing/object/tag_test.go +++ b/plumbing/object/tag_test.go @@ -312,6 +312,27 @@ RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk= c.Assert(decoded.PGPSignature, Equals, pgpsignature) } +func (s *TagSuite) TestSSHSignatureSerialization(c *C) { + encoded := &plumbing.MemoryObject{} + decoded := &Tag{} + tag := s.tag(c, plumbing.NewHash("b742a2a9fa0afcfa9a6fad080980fbc26b007c69")) + + signature := `-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE-----` + tag.PGPSignature = signature + + err := tag.Encode(encoded) + c.Assert(err, IsNil) + + err = decoded.Decode(encoded) + c.Assert(err, IsNil) + c.Assert(decoded.PGPSignature, Equals, signature) +} + func (s *TagSuite) TestVerify(c *C) { ts := time.Unix(1617403017, 0) loc, _ := time.LoadLocation("UTC") From 1162350e2dea30b4a60958ab27eb50f749451834 Mon Sep 17 00:00:00 2001 From: pat-nel87 <71235856+pat-nel87@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:18:12 -0500 Subject: [PATCH 078/205] _examples: README.md , Remove broken Config link. This link is broken and doesn't appear to map to anything current. I propose We remove this link. --- _examples/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/_examples/README.md b/_examples/README.md index 3a4c539d0..1f150f99b 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -23,7 +23,6 @@ Here you can find a list of annotated _go-git_ examples: - [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. -- [config](config/main.go) - Explains how to work with config files. - [submodule](submodule/main.go) - Submodule update remote. ### Advanced From e96546570a27f06e46edbe450b6096eeefb8a271 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:42:27 +0000 Subject: [PATCH 079/205] build(deps): bump golang.org/x/net from 0.2.0 to 0.7.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.2.0 to 0.7.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.2.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index c46d2446a..502907e5d 100644 --- a/go.mod +++ b/go.mod @@ -20,9 +20,9 @@ require ( github.com/skeema/knownhosts v1.1.0 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.3.0 - golang.org/x/net v0.2.0 - golang.org/x/sys v0.3.0 - golang.org/x/text v0.4.0 + golang.org/x/net v0.7.0 + golang.org/x/sys v0.5.0 + golang.org/x/text v0.7.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 536173547..da20d7311 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,9 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx 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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 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= @@ -103,20 +104,23 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.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.1.0/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/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 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/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/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.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= From 296eb7692f64f85aea011a790915f15ed0d1d4c6 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 2 Mar 2023 20:30:41 +0000 Subject: [PATCH 080/205] Bump dependencies - github.com/ProtonMail/go-crypto to version 0.0.0-20230217124315-7d5c6f04bbb8. - github.com/acomagu/bufpipe to version 1.0.4. - github.com/go-git/go-billy/v5 to version 5.4.1. - golang.org/x/crypto to version 0.6.0. Signed-off-by: Paulo Gomes --- go.mod | 12 ++++++------ go.sum | 20 +++++++++----------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 502907e5d..85fd7b132 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,15 @@ module github.com/go-git/go-git/v5 +go 1.13 + require ( - github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 - github.com/acomagu/bufpipe v1.0.3 + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 + github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 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.4.0 + github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git-fixtures/v4 v4.3.1 github.com/google/go-cmp v0.5.9 github.com/imdario/mergo v0.3.13 @@ -19,12 +21,10 @@ require ( github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.1.0 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.3.0 + golang.org/x/crypto v0.6.0 golang.org/x/net v0.7.0 golang.org/x/sys v0.5.0 golang.org/x/text v0.7.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/warnings.v0 v0.1.2 // indirect ) - -go 1.13 diff --git a/go.sum b/go.sum index da20d7311..a7548e292 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ 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/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 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= @@ -22,8 +22,8 @@ github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4x 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/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQBDE= -github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= 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.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -73,8 +73,8 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0 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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -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/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -83,7 +83,7 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx 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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -103,7 +103,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc 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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -111,7 +110,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From eea852163223637049cc1b3636daf160169b7bd2 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 2 Mar 2023 20:37:48 +0000 Subject: [PATCH 081/205] build: Reduce git token permissions Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 3 +++ .github/workflows/test.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index fcec675eb..f3dbe479d 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -1,5 +1,8 @@ on: [push, pull_request] name: Git Compatibility +permissions: + contents: read + jobs: test: strategy: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b576d386e..a7b614b32 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,8 @@ on: [push, pull_request] name: Test +permissions: + contents: read + jobs: version-matrix: strategy: From 3486338715d0c1385992c6ca8db6bd04fd0df135 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 2 Mar 2023 20:01:04 +0000 Subject: [PATCH 082/205] *: Fix panic for empty revisions Signed-off-by: Paulo Gomes --- repository.go | 76 +++++++++++++++++++++++++--------------------- repository_test.go | 9 ++++++ 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/repository.go b/repository.go index 7292df627..2a06f8be3 100644 --- a/repository.go +++ b/repository.go @@ -750,21 +750,20 @@ func (r *Repository) buildTagSignature(tag *object.Tag, signKey *openpgp.Entity) // If you want to check to see if the tag is an annotated tag, you can call // TagObject on the hash of the reference in ForEach: // -// ref, err := r.Tag("v0.1.0") -// if err != nil { -// // Handle error -// } -// -// obj, err := r.TagObject(ref.Hash()) -// switch err { -// case nil: -// // Tag object present -// case plumbing.ErrObjectNotFound: -// // Not a tag object -// default: -// // Some other error -// } +// ref, err := r.Tag("v0.1.0") +// if err != nil { +// // Handle error +// } // +// obj, err := r.TagObject(ref.Hash()) +// switch err { +// case nil: +// // Tag object present +// case plumbing.ErrObjectNotFound: +// // Not a tag object +// default: +// // Some other error +// } func (r *Repository) Tag(name string) (*plumbing.Reference, error) { ref, err := r.Reference(plumbing.ReferenceName(path.Join("refs", "tags", name)), false) if err != nil { @@ -1241,26 +1240,25 @@ func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter { // If you want to check to see if the tag is an annotated tag, you can call // TagObject on the hash Reference passed in through ForEach: // -// iter, err := r.Tags() -// if err != nil { -// // Handle error -// } -// -// if err := iter.ForEach(func (ref *plumbing.Reference) error { -// obj, err := r.TagObject(ref.Hash()) -// switch err { -// case nil: -// // Tag object present -// case plumbing.ErrObjectNotFound: -// // Not a tag object -// default: -// // Some other error -// return err -// } -// }); err != nil { -// // Handle outer iterator error -// } +// iter, err := r.Tags() +// if err != nil { +// // Handle error +// } // +// if err := iter.ForEach(func (ref *plumbing.Reference) error { +// obj, err := r.TagObject(ref.Hash()) +// switch err { +// case nil: +// // Tag object present +// case plumbing.ErrObjectNotFound: +// // Not a tag object +// default: +// // Some other error +// return err +// } +// }); err != nil { +// // Handle outer iterator error +// } func (r *Repository) Tags() (storer.ReferenceIter, error) { refIter, err := r.Storer.IterReferences() if err != nil { @@ -1424,9 +1422,13 @@ func (r *Repository) Worktree() (*Worktree, error) { // // Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch, // refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug}), hash (prefix and full) -func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) { - p := revision.NewParserFromString(string(rev)) +func (r *Repository) ResolveRevision(in plumbing.Revision) (*plumbing.Hash, error) { + rev := in.String() + if rev == "" { + return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound + } + p := revision.NewParserFromString(rev) items, err := p.Parse() if err != nil { @@ -1557,6 +1559,10 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err } } + if commit == nil { + return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound + } + return &commit.Hash, nil } diff --git a/repository_test.go b/repository_test.go index 7a9db151d..468ce33b0 100644 --- a/repository_test.go +++ b/repository_test.go @@ -2953,6 +2953,15 @@ func (s *RepositorySuite) TestDotGitToOSFilesystemsInvalidPath(c *C) { c.Assert(err, NotNil) } +func (s *RepositorySuite) TestIssue674(c *C) { + r, _ := Init(memory.NewStorage(), nil) + h, err := r.ResolveRevision(plumbing.Revision("")) + + c.Assert(err, NotNil) + c.Assert(h, NotNil) + c.Check(h.IsZero(), Equals, true) +} + func BenchmarkObjects(b *testing.B) { defer fixtures.Clean() From fba136de01b6fe8c0530626587c7a1f7f6157563 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 5 Mar 2023 16:35:06 +0000 Subject: [PATCH 083/205] tests: Avoid use of user's GPG keys during tests The TestPullAdd test uses git CLI to perform a commit. Contributors with signing enabled globally would have their GPG configuration being used to sign that test commit. This behaviour was transparent for contributors that do not use secure keys which requires physical confirmation. The new behaviour disables GPG signing for that test, which was not required as part of the test. Signed-off-by: Paulo Gomes --- worktree_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worktree_test.go b/worktree_test.go index b57a77dbf..ac56a4688 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -259,7 +259,7 @@ func (s *RepositorySuite) TestPullAdd(c *C) { ExecuteOnPath(c, path, "touch foo", "git add foo", - "git commit -m foo foo", + "git commit --no-gpg-sign -m foo foo", ) w, err := r.Worktree() From 7368bb92af69a10ca786bfdcbbd52008e3d8395c Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 5 Mar 2023 16:37:14 +0000 Subject: [PATCH 084/205] ci: Bump GitHub actions Removes the error messages: Node.js 12 actions are deprecated. Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index f3dbe479d..c945e72ff 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -17,12 +17,12 @@ jobs: steps: - name: Install Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v3 with: go-version: 1.20.x - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install build dependencies run: sudo apt-get install gettext diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a7b614b32..ce5872d03 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,12 +14,12 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Install Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Configure known hosts if: matrix.platform != 'ubuntu-latest' From eddd209ed525a7814ffc80fc2bcd05eb31d0c23a Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 5 Mar 2023 16:41:54 +0000 Subject: [PATCH 085/205] ci: Enable race detection for go tests After the fix of data races in go-billy (go-git/go-billy/pull/28) race detection can be enabled in go-git to ensure no new issues go undetected. Signed-off-by: Paulo Gomes --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d10922fb1..2acb8bc45 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ build-git: test: @echo "running against `git version`"; \ - $(GOTEST) ./... + $(GOTEST) -race ./... test-coverage: @echo "running against `git version`"; \ From 3ba636d6c9e247882798714e3233930441e0a64e Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 9 Jan 2023 17:34:24 +0300 Subject: [PATCH 086/205] fix(ssh): unable to pass a custom HostKeyCallback func Don't overwrite HostKeyCallback if one is provided. Fixes: c35b8082c863 ("plumbing: transport/ssh, auto-populate ClientConfig.HostKeyAlgorithms. Fixes #411") Fixes: https://github.com/go-git/go-git/issues/654 Signed-off-by: Ayman Bagabas --- plumbing/transport/ssh/auth_method.go | 28 ++++---- plumbing/transport/ssh/common.go | 33 ++++----- plumbing/transport/ssh/common_test.go | 79 +++++++++++++++++++--- plumbing/transport/ssh/upload_pack_test.go | 4 ++ 4 files changed, 101 insertions(+), 43 deletions(-) diff --git a/plumbing/transport/ssh/auth_method.go b/plumbing/transport/ssh/auth_method.go index 9d3bcd359..e89ce4ba3 100644 --- a/plumbing/transport/ssh/auth_method.go +++ b/plumbing/transport/ssh/auth_method.go @@ -43,6 +43,7 @@ const ( type KeyboardInteractive struct { User string Challenge ssh.KeyboardInteractiveChallenge + HostKeyCallbackHelper } func (a *KeyboardInteractive) Name() string { @@ -54,18 +55,19 @@ func (a *KeyboardInteractive) String() string { } func (a *KeyboardInteractive) ClientConfig() (*ssh.ClientConfig, error) { - return &ssh.ClientConfig{ + return a.SetHostKeyCallback(&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 { @@ -77,10 +79,10 @@ func (a *Password) String() string { } func (a *Password) ClientConfig() (*ssh.ClientConfig, error) { - return &ssh.ClientConfig{ + return a.SetHostKeyCallback(&ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ssh.Password(a.Password)}, - }, nil + }) } // PasswordCallback implements AuthMethod by using a callback @@ -88,6 +90,7 @@ func (a *Password) ClientConfig() (*ssh.ClientConfig, error) { type PasswordCallback struct { User string Callback func() (pass string, err error) + HostKeyCallbackHelper } func (a *PasswordCallback) Name() string { @@ -99,16 +102,17 @@ func (a *PasswordCallback) String() string { } func (a *PasswordCallback) ClientConfig() (*ssh.ClientConfig, error) { - return &ssh.ClientConfig{ + return a.SetHostKeyCallback(&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 @@ -147,10 +151,10 @@ func (a *PublicKeys) String() string { } func (a *PublicKeys) ClientConfig() (*ssh.ClientConfig, error) { - return &ssh.ClientConfig{ + return a.SetHostKeyCallback(&ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ssh.PublicKeys(a.Signer)}, - }, nil + }) } func username() (string, error) { @@ -173,6 +177,7 @@ 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 @@ -207,10 +212,10 @@ func (a *PublicKeysCallback) String() string { } func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) { - return &ssh.ClientConfig{ + return a.SetHostKeyCallback(&ssh.ClientConfig{ User: a.User, Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(a.Callback)}, - }, nil + }) } // NewKnownHostsCallback returns ssh.HostKeyCallback based on a file based on a @@ -286,9 +291,6 @@ 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 4b9ac0797..e06958a3b 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -10,6 +10,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/internal/common" + "github.com/skeema/knownhosts" "github.com/kevinburke/ssh_config" "golang.org/x/crypto/ssh" @@ -122,9 +123,18 @@ func (c *command) connect() error { return err } hostWithPort := c.getHostWithPort() - config, err = SetConfigHostKeyFields(config, hostWithPort) - if err != nil { - return err + if config.HostKeyCallback == nil { + kh, err := newKnownHosts() + if err != nil { + return err + } + config.HostKeyCallback = kh.HostKeyCallback() + config.HostKeyAlgorithms = kh.HostKeyAlgorithms(hostWithPort) + } else if len(config.HostKeyAlgorithms) == 0 { + // Set the HostKeyAlgorithms based on HostKeyCallback. + // 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. + config.HostKeyAlgorithms = knownhosts.HostKeyAlgorithms(config.HostKeyCallback, hostWithPort) } overrideConfig(c.config, config) @@ -167,23 +177,6 @@ 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 diff --git a/plumbing/transport/ssh/common_test.go b/plumbing/transport/ssh/common_test.go index 6d634d532..496e82d17 100644 --- a/plumbing/transport/ssh/common_test.go +++ b/plumbing/transport/ssh/common_test.go @@ -5,23 +5,25 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/gliderlabs/ssh" "github.com/kevinburke/ssh_config" - "golang.org/x/crypto/ssh" + stdssh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } func (s *SuiteCommon) TestOverrideConfig(c *C) { - config := &ssh.ClientConfig{ + config := &stdssh.ClientConfig{ User: "foo", - Auth: []ssh.AuthMethod{ - ssh.Password("yourpassword"), + Auth: []stdssh.AuthMethod{ + stdssh.Password("yourpassword"), }, - HostKeyCallback: ssh.FixedHostKey(nil), + HostKeyCallback: stdssh.FixedHostKey(nil), } - target := &ssh.ClientConfig{} + target := &stdssh.ClientConfig{} overrideConfig(config, target) c.Assert(target.User, Equals, "foo") @@ -30,11 +32,11 @@ func (s *SuiteCommon) TestOverrideConfig(c *C) { } func (s *SuiteCommon) TestOverrideConfigKeep(c *C) { - config := &ssh.ClientConfig{ + config := &stdssh.ClientConfig{ User: "foo", } - target := &ssh.ClientConfig{ + target := &stdssh.ClientConfig{ User: "bar", } @@ -93,12 +95,69 @@ func (s *SuiteCommon) TestDefaultSSHConfigWildcard(c *C) { c.Assert(cmd.getHostWithPort(), Equals, "github.com:22") } +func (s *SuiteCommon) TestIgnoreHostKeyCallback(c *C) { + uploadPack := &UploadPackSuite{ + opts: []ssh.Option{ + ssh.HostKeyPEM(testdata.PEMBytes["ed25519"]), + }, + } + uploadPack.SetUpSuite(c) + // Use the default client, which does not have a host key callback + uploadPack.Client = DefaultClient + auth, err := NewPublicKeys("foo", testdata.PEMBytes["rsa"], "") + c.Assert(err, IsNil) + c.Assert(auth, NotNil) + auth.HostKeyCallback = stdssh.InsecureIgnoreHostKey() + ep := uploadPack.newEndpoint(c, "bar.git") + ps, err := uploadPack.Client.NewUploadPackSession(ep, auth) + c.Assert(err, IsNil) + c.Assert(ps, NotNil) +} + +func (s *SuiteCommon) TestFixedHostKeyCallback(c *C) { + hostKey, err := stdssh.ParsePrivateKey(testdata.PEMBytes["ed25519"]) + c.Assert(err, IsNil) + uploadPack := &UploadPackSuite{ + opts: []ssh.Option{ + ssh.HostKeyPEM(testdata.PEMBytes["ed25519"]), + }, + } + uploadPack.SetUpSuite(c) + // Use the default client, which does not have a host key callback + uploadPack.Client = DefaultClient + auth, err := NewPublicKeys("foo", testdata.PEMBytes["rsa"], "") + c.Assert(err, IsNil) + c.Assert(auth, NotNil) + auth.HostKeyCallback = stdssh.FixedHostKey(hostKey.PublicKey()) + ep := uploadPack.newEndpoint(c, "bar.git") + ps, err := uploadPack.Client.NewUploadPackSession(ep, auth) + c.Assert(err, IsNil) + c.Assert(ps, NotNil) +} + +func (s *SuiteCommon) TestFailHostKeyCallback(c *C) { + uploadPack := &UploadPackSuite{ + opts: []ssh.Option{ + ssh.HostKeyPEM(testdata.PEMBytes["ed25519"]), + }, + } + uploadPack.SetUpSuite(c) + // Use the default client, which does not have a host key callback + uploadPack.Client = DefaultClient + auth, err := NewPublicKeys("foo", testdata.PEMBytes["rsa"], "") + c.Assert(err, IsNil) + c.Assert(auth, NotNil) + ep := uploadPack.newEndpoint(c, "bar.git") + _, err = uploadPack.Client.NewUploadPackSession(ep, auth) + c.Assert(err, NotNil) +} + func (s *SuiteCommon) TestIssue70(c *C) { uploadPack := &UploadPackSuite{} uploadPack.SetUpSuite(c) - config := &ssh.ClientConfig{ - HostKeyCallback: ssh.InsecureIgnoreHostKey(), + config := &stdssh.ClientConfig{ + HostKeyCallback: stdssh.InsecureIgnoreHostKey(), } r := &runner{ config: config, diff --git a/plumbing/transport/ssh/upload_pack_test.go b/plumbing/transport/ssh/upload_pack_test.go index e65e04a7a..f172feeda 100644 --- a/plumbing/transport/ssh/upload_pack_test.go +++ b/plumbing/transport/ssh/upload_pack_test.go @@ -25,6 +25,7 @@ import ( type UploadPackSuite struct { test.UploadPackSuite fixtures.Suite + opts []ssh.Option port int base string @@ -57,6 +58,9 @@ func (s *UploadPackSuite) SetUpSuite(c *C) { s.UploadPackSuite.NonExistentEndpoint = s.newEndpoint(c, "non-existent.git") server := &ssh.Server{Handler: handlerSSH} + for _, opt := range s.opts { + opt(server) + } go func() { log.Fatal(server.Serve(l)) }() From 16cc29310be7089bbc8d54d79d608834d2753adc Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Sat, 4 Mar 2023 19:12:51 +0100 Subject: [PATCH 087/205] dotgit: test skip deleted references Checks that reading references it correctly skips deleted directories and files. Signed-off-by: Javi Fontan --- storage/filesystem/dotgit/dotgit_test.go | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go index a8f0eb754..098b559ab 100644 --- a/storage/filesystem/dotgit/dotgit_test.go +++ b/storage/filesystem/dotgit/dotgit_test.go @@ -864,3 +864,70 @@ func (s *SuiteDotGit) TestIncBytes(c *C) { c.Assert(overflow, Equals, test.overflow) } } + +// this filesystem wrapper returns os.ErrNotExist if the file matches +// the provided paths list +type notExistsFS struct { + billy.Filesystem + + paths []string +} + +func (f *notExistsFS) matches(filename string) bool { + for _, n := range f.paths { + if filename == n { + return true + } + } + return false +} + +func (f *notExistsFS) Open(filename string) (billy.File, error) { + if f.matches(filename) { + return nil, os.ErrNotExist + } + + return f.Filesystem.Open(filename) +} + +func (f *notExistsFS) ReadDir(path string) ([]os.FileInfo, error) { + if f.matches(path) { + return nil, os.ErrNotExist + } + + return f.Filesystem.ReadDir(path) +} + +func (s *SuiteDotGit) TestDeletedRefs(c *C) { + fs, clean := s.TemporalFilesystem() + defer clean() + + dir := New(¬ExistsFS{ + Filesystem: fs, + paths: []string{ + "refs/heads/bar", + "refs/heads/baz", + }, + }) + + err := dir.SetRef(plumbing.NewReferenceFromStrings( + "refs/heads/foo", + "e8d3ffab552895c19b9fcf7aa264d277cde33881", + ), nil) + c.Assert(err, IsNil) + err = dir.SetRef(plumbing.NewReferenceFromStrings( + "refs/heads/bar", + "a8d3ffab552895c19b9fcf7aa264d277cde33881", + ), nil) + c.Assert(err, IsNil) + err = dir.SetRef(plumbing.NewReferenceFromStrings( + "refs/heads/baz/baz", + "a8d3ffab552895c19b9fcf7aa264d277cde33881", + ), nil) + c.Assert(err, IsNil) + + refs, err := dir.Refs() + c.Assert(err, IsNil) + c.Assert(refs, HasLen, 1) + c.Assert(refs[0].Name(), Equals, plumbing.ReferenceName("refs/heads/foo")) +} From 660071d48cfe9728953e4a84960d40deb7078811 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Sat, 4 Mar 2023 20:12:17 +0100 Subject: [PATCH 088/205] dotgit: fix deleted references test in windows Signed-off-by: Javi Fontan --- storage/filesystem/dotgit/dotgit_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go index 098b559ab..63c9eb015 100644 --- a/storage/filesystem/dotgit/dotgit_test.go +++ b/storage/filesystem/dotgit/dotgit_test.go @@ -873,9 +873,10 @@ type notExistsFS struct { paths []string } -func (f *notExistsFS) matches(filename string) bool { +func (f *notExistsFS) matches(path string) bool { + p := filepath.ToSlash(path) for _, n := range f.paths { - if filename == n { + if p == n { return true } } From 02494219682689ccfae6d4ffed2734509eed02d0 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 7 Mar 2023 22:33:55 +0000 Subject: [PATCH 089/205] plumbing/hash: Add SHA256 as a supported algorithm Relates to #706. Signed-off-by: Paulo Gomes --- plumbing/hash/hash.go | 3 +++ plumbing/hash/hash_sha1.go | 15 +++++++++++++++ plumbing/hash/hash_sha256.go | 15 +++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 plumbing/hash/hash_sha1.go create mode 100644 plumbing/hash/hash_sha256.go diff --git a/plumbing/hash/hash.go b/plumbing/hash/hash.go index 80e4b5f25..82d185616 100644 --- a/plumbing/hash/hash.go +++ b/plumbing/hash/hash.go @@ -21,6 +21,7 @@ func init() { // that registers new algorithms to avoid side effects. func reset() { algos[crypto.SHA1] = sha1cd.New + algos[crypto.SHA256] = crypto.SHA256.New } // RegisterHash allows for the hash algorithm used to be overriden. @@ -34,6 +35,8 @@ func RegisterHash(h crypto.Hash, f func() hash.Hash) error { switch h { case crypto.SHA1: algos[h] = f + case crypto.SHA256: + algos[h] = f default: return fmt.Errorf("unsupported hash function: %v", h) } diff --git a/plumbing/hash/hash_sha1.go b/plumbing/hash/hash_sha1.go new file mode 100644 index 000000000..e3cb60fec --- /dev/null +++ b/plumbing/hash/hash_sha1.go @@ -0,0 +1,15 @@ +//go:build !sha256 +// +build !sha256 + +package hash + +import "crypto" + +const ( + // CryptoType defines what hash algorithm is being used. + CryptoType = crypto.SHA1 + // Size defines the amount of bytes the hash yields. + Size = 20 + // HexSize defines the strings size of the hash when represented in hexadecimal. + HexSize = 40 +) diff --git a/plumbing/hash/hash_sha256.go b/plumbing/hash/hash_sha256.go new file mode 100644 index 000000000..1c52b8975 --- /dev/null +++ b/plumbing/hash/hash_sha256.go @@ -0,0 +1,15 @@ +//go:build sha256 +// +build sha256 + +package hash + +import "crypto" + +const ( + // CryptoType defines what hash algorithm is being used. + CryptoType = crypto.SHA256 + // Size defines the amount of bytes the hash yields. + Size = 32 + // HexSize defines the strings size of the hash when represented in hexadecimal. + HexSize = 64 +) From 99e2f85843878671b028d4d01bd4668676226dd1 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 7 Mar 2023 23:16:17 +0000 Subject: [PATCH 090/205] config: Add Repository Format Extension Relates to the SHA256 implementation, defined in #706. Signed-off-by: Paulo Gomes --- config/config.go | 73 ++++++++++++++++++++++---------- options.go | 8 ++++ plumbing/format/config/format.go | 53 +++++++++++++++++++++++ repository.go | 37 ++++++++++++++++ 4 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 plumbing/format/config/format.go diff --git a/config/config.go b/config/config.go index 8051bc145..83629fcef 100644 --- a/config/config.go +++ b/config/config.go @@ -59,6 +59,8 @@ type Config struct { // CommentChar is the character indicating the start of a // comment for commands like commit and tag CommentChar string + // RepositoryFormatVersion identifies the repository format and layout version. + RepositoryFormatVersion format.RepositoryFormatVersion } User struct { @@ -96,6 +98,17 @@ type Config struct { DefaultBranch string } + Extensions struct { + // ObjectFormat specifies the hash algorithm to use. The + // acceptable values are sha1 and sha256. If not specified, + // sha1 is assumed. It is an error to specify this key unless + // core.repositoryFormatVersion is 1. + // + // This setting must not be changed after repository initialization + // (e.g. clone or init). + ObjectFormat format.ObjectFormat + } + // Remotes list of repository remotes, the key of the map is the name // of the remote, should equal to RemoteConfig.Name. Remotes map[string]*RemoteConfig @@ -226,28 +239,31 @@ func (c *Config) Validate() error { } const ( - remoteSection = "remote" - submoduleSection = "submodule" - branchSection = "branch" - coreSection = "core" - packSection = "pack" - userSection = "user" - authorSection = "author" - committerSection = "committer" - initSection = "init" - urlSection = "url" - fetchKey = "fetch" - urlKey = "url" - bareKey = "bare" - worktreeKey = "worktree" - commentCharKey = "commentChar" - windowKey = "window" - mergeKey = "merge" - rebaseKey = "rebase" - nameKey = "name" - emailKey = "email" - descriptionKey = "description" - defaultBranchKey = "defaultBranch" + remoteSection = "remote" + submoduleSection = "submodule" + branchSection = "branch" + coreSection = "core" + packSection = "pack" + userSection = "user" + authorSection = "author" + committerSection = "committer" + initSection = "init" + urlSection = "url" + extensionsSection = "extensions" + fetchKey = "fetch" + urlKey = "url" + bareKey = "bare" + worktreeKey = "worktree" + commentCharKey = "commentChar" + windowKey = "window" + mergeKey = "merge" + rebaseKey = "rebase" + nameKey = "name" + emailKey = "email" + descriptionKey = "description" + defaultBranchKey = "defaultBranch" + repositoryFormatVersionKey = "repositoryformatversion" + objectFormat = "objectformat" // DefaultPackWindow holds the number of previous objects used to // generate deltas. The value 10 is the same used by git command. @@ -391,6 +407,7 @@ func (c *Config) unmarshalInit() { // Marshal returns Config encoded as a git-config file. func (c *Config) Marshal() ([]byte, error) { c.marshalCore() + c.marshalExtensions() c.marshalUser() c.marshalPack() c.marshalRemotes() @@ -410,12 +427,24 @@ func (c *Config) Marshal() ([]byte, error) { func (c *Config) marshalCore() { s := c.Raw.Section(coreSection) s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare)) + if string(c.Core.RepositoryFormatVersion) != "" { + s.SetOption(repositoryFormatVersionKey, string(c.Core.RepositoryFormatVersion)) + } if c.Core.Worktree != "" { s.SetOption(worktreeKey, c.Core.Worktree) } } +func (c *Config) marshalExtensions() { + // Extensions are only supported on Version 1, therefore + // ignore them otherwise. + if c.Core.RepositoryFormatVersion == format.Version_1 { + s := c.Raw.Section(extensionsSection) + s.SetOption(objectFormat, string(c.Extensions.ObjectFormat)) + } +} + func (c *Config) marshalUser() { s := c.Raw.Section(userSection) if c.User.Name != "" { diff --git a/options.go b/options.go index 747d512bc..738c1ce9c 100644 --- a/options.go +++ b/options.go @@ -10,6 +10,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" + formatcfg "github.com/go-git/go-git/v5/plumbing/format/config" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband" "github.com/go-git/go-git/v5/plumbing/transport" @@ -672,3 +673,10 @@ type PlainOpenOptions struct { // Validate validates the fields and sets the default values. func (o *PlainOpenOptions) Validate() error { return nil } + +type PlainInitOptions struct { + ObjectFormat formatcfg.ObjectFormat +} + +// Validate validates the fields and sets the default values. +func (o *PlainInitOptions) Validate() error { return nil } diff --git a/plumbing/format/config/format.go b/plumbing/format/config/format.go new file mode 100644 index 000000000..4873ea925 --- /dev/null +++ b/plumbing/format/config/format.go @@ -0,0 +1,53 @@ +package config + +// RepositoryFormatVersion represents the repository format version, +// as per defined at: +// +// https://git-scm.com/docs/repository-version +type RepositoryFormatVersion string + +const ( + // Version_0 is the format defined by the initial version of git, + // including but not limited to the format of the repository + // directory, the repository configuration file, and the object + // and ref storage. + // + // Specifying the complete behavior of git is beyond the scope + // of this document. + Version_0 = "0" + + // Version_1 is identical to version 0, with the following exceptions: + // + // 1. When reading the core.repositoryformatversion variable, a git + // implementation which supports version 1 MUST also read any + // configuration keys found in the extensions section of the + // configuration file. + // + // 2. If a version-1 repository specifies any extensions.* keys that + // the running git has not implemented, the operation MUST NOT proceed. + // Similarly, if the value of any known key is not understood by the + // implementation, the operation MUST NOT proceed. + // + // Note that if no extensions are specified in the config file, then + // core.repositoryformatversion SHOULD be set to 0 (setting it to 1 provides + // no benefit, and makes the repository incompatible with older + // implementations of git). + Version_1 = "1" + + // DefaultRepositoryFormatVersion holds the default repository format version. + DefaultRepositoryFormatVersion = Version_0 +) + +// ObjectFormat defines the object format. +type ObjectFormat string + +const ( + // SHA1 represents the object format used for SHA1. + SHA1 ObjectFormat = "sha1" + + // SHA256 represents the object format used for SHA256. + SHA256 ObjectFormat = "sha256" + + // DefaultObjectFormat holds the default object format. + DefaultObjectFormat = SHA1 +) diff --git a/repository.go b/repository.go index 2a06f8be3..56ae9761c 100644 --- a/repository.go +++ b/repository.go @@ -3,6 +3,7 @@ package git import ( "bytes" "context" + "crypto" "encoding/hex" "errors" "fmt" @@ -21,7 +22,9 @@ import ( "github.com/go-git/go-git/v5/internal/revision" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" + formatcfg "github.com/go-git/go-git/v5/plumbing/format/config" "github.com/go-git/go-git/v5/plumbing/format/packfile" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/storage" @@ -57,6 +60,7 @@ var ( ErrIsBareRepository = errors.New("worktree not available in a bare repository") ErrUnableToResolveCommit = errors.New("unable to resolve commit") ErrPackedObjectsNotSupported = errors.New("packed objects not supported") + ErrSHA256NotSupported = errors.New("go-git was not compiled with SHA256 support") ) // Repository represents a git repository @@ -228,6 +232,39 @@ func PlainInit(path string, isBare bool) (*Repository, error) { return Init(s, wt) } +func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, error) { + wt := osfs.New(path) + dot, _ := wt.Chroot(GitDirName) + + s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) + + r, err := Init(s, wt) + if err != nil { + return nil, err + } + + cfg, err := r.Config() + if err != nil { + return nil, err + } + + if opts != nil { + if opts.ObjectFormat == formatcfg.SHA256 && hash.CryptoType != crypto.SHA256 { + return nil, ErrSHA256NotSupported + } + + cfg.Core.RepositoryFormatVersion = formatcfg.Version_1 + cfg.Extensions.ObjectFormat = opts.ObjectFormat + } + + err = r.Storer.SetConfig(cfg) + if err != nil { + return nil, err + } + + return r, err +} + // PlainOpen opens a git repository from the given path. It detects if the // repository is bare or a normal one. If the path doesn't contain a valid // repository ErrRepositoryNotExists is returned From 9822ad8573e374421a79c096d8f1dfa91366fb02 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 7 Mar 2023 23:31:29 +0000 Subject: [PATCH 091/205] *: Support variable length plumbing.Hash The variable length for plumbing.Hash is defined at build time, blocked by tag sha256. This approach was a trade-off between keeping backwards compatibility while making progress towards supporting SHA256 with a small amount of changes. Relates to the SHA256 implementation, defined in #706. Signed-off-by: Paulo Gomes --- plumbing/format/commitgraph/encoder.go | 7 +++---- plumbing/format/idxfile/decoder.go | 3 ++- plumbing/format/idxfile/encoder.go | 7 +++---- plumbing/format/idxfile/idxfile.go | 5 +++-- plumbing/format/index/decoder.go | 3 +-- plumbing/format/index/encoder.go | 3 +-- plumbing/format/packfile/encoder.go | 3 +-- plumbing/format/packfile/encoder_test.go | 9 +++++---- plumbing/format/packfile/scanner_test.go | 3 ++- plumbing/hash.go | 14 +++++++------- plumbing/protocol/packp/ulreq_decode_test.go | 5 +++-- storage/filesystem/dotgit/dotgit.go | 12 +++++++----- storage/filesystem/dotgit/writers.go | 5 +++-- 13 files changed, 41 insertions(+), 38 deletions(-) diff --git a/plumbing/format/commitgraph/encoder.go b/plumbing/format/commitgraph/encoder.go index bcf7d030a..f61025bb4 100644 --- a/plumbing/format/commitgraph/encoder.go +++ b/plumbing/format/commitgraph/encoder.go @@ -1,7 +1,6 @@ package commitgraph import ( - "crypto" "io" "github.com/go-git/go-git/v5/plumbing" @@ -17,7 +16,7 @@ type Encoder struct { // NewEncoder returns a new stream encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { - h := hash.New(crypto.SHA1) + h := hash.New(hash.CryptoType) mw := io.MultiWriter(w, h) return &Encoder{mw, h} } @@ -31,7 +30,7 @@ func (e *Encoder) Encode(idx Index) error { hashToIndex, fanout, extraEdgesCount := e.prepare(idx, hashes) chunkSignatures := [][]byte{oidFanoutSignature, oidLookupSignature, commitDataSignature} - chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * 20, uint64(len(hashes)) * 36} + chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * hash.Size, uint64(len(hashes)) * 36} if extraEdgesCount > 0 { chunkSignatures = append(chunkSignatures, extraEdgeListSignature) chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*4) @@ -183,6 +182,6 @@ func (e *Encoder) encodeExtraEdges(extraEdges []uint32) (err error) { } func (e *Encoder) encodeChecksum() error { - _, err := e.Write(e.hash.Sum(nil)[:20]) + _, err := e.Write(e.hash.Sum(nil)[:hash.Size]) return err } diff --git a/plumbing/format/idxfile/decoder.go b/plumbing/format/idxfile/decoder.go index 51a390489..9afdce301 100644 --- a/plumbing/format/idxfile/decoder.go +++ b/plumbing/format/idxfile/decoder.go @@ -6,6 +6,7 @@ import ( "errors" "io" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/utils/binary" ) @@ -19,7 +20,7 @@ var ( const ( fanout = 256 - objectIDLength = 20 + objectIDLength = hash.Size ) // Decoder reads and decodes idx files from an input stream. diff --git a/plumbing/format/idxfile/encoder.go b/plumbing/format/idxfile/encoder.go index 6ac445ff6..75147376b 100644 --- a/plumbing/format/idxfile/encoder.go +++ b/plumbing/format/idxfile/encoder.go @@ -1,7 +1,6 @@ package idxfile import ( - "crypto" "io" "github.com/go-git/go-git/v5/plumbing/hash" @@ -16,7 +15,7 @@ type Encoder struct { // NewEncoder returns a new stream encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { - h := hash.New(crypto.SHA1) + h := hash.New(hash.CryptoType) mw := io.MultiWriter(w, h) return &Encoder{mw, h} } @@ -133,10 +132,10 @@ func (e *Encoder) encodeChecksums(idx *MemoryIndex) (int, error) { return 0, err } - copy(idx.IdxChecksum[:], e.hash.Sum(nil)[:20]) + copy(idx.IdxChecksum[:], e.hash.Sum(nil)[:hash.Size]) if _, err := e.Write(idx.IdxChecksum[:]); err != nil { return 0, err } - return 40, nil + return hash.HexSize, nil } diff --git a/plumbing/format/idxfile/idxfile.go b/plumbing/format/idxfile/idxfile.go index 64dd8dcef..9237a7434 100644 --- a/plumbing/format/idxfile/idxfile.go +++ b/plumbing/format/idxfile/idxfile.go @@ -8,6 +8,7 @@ import ( encbin "encoding/binary" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" ) const ( @@ -53,8 +54,8 @@ type MemoryIndex struct { Offset32 [][]byte CRC32 [][]byte Offset64 []byte - PackfileChecksum [20]byte - IdxChecksum [20]byte + PackfileChecksum [hash.Size]byte + IdxChecksum [hash.Size]byte offsetHash map[int64]plumbing.Hash offsetHashIsFull bool diff --git a/plumbing/format/index/decoder.go b/plumbing/format/index/decoder.go index c4da20c08..8834e2517 100644 --- a/plumbing/format/index/decoder.go +++ b/plumbing/format/index/decoder.go @@ -3,7 +3,6 @@ package index import ( "bufio" "bytes" - "crypto" "errors" "io" "io/ioutil" @@ -49,7 +48,7 @@ type Decoder struct { // NewDecoder returns a new decoder that reads from r. func NewDecoder(r io.Reader) *Decoder { - h := hash.New(crypto.SHA1) + h := hash.New(hash.CryptoType) return &Decoder{ r: io.TeeReader(r, h), hash: h, diff --git a/plumbing/format/index/encoder.go b/plumbing/format/index/encoder.go index a91537870..fa2d81445 100644 --- a/plumbing/format/index/encoder.go +++ b/plumbing/format/index/encoder.go @@ -2,7 +2,6 @@ package index import ( "bytes" - "crypto" "errors" "io" "sort" @@ -29,7 +28,7 @@ type Encoder struct { // NewEncoder returns a new encoder that writes to w. func NewEncoder(w io.Writer) *Encoder { - h := hash.New(crypto.SHA1) + h := hash.New(hash.CryptoType) mw := io.MultiWriter(w, h) return &Encoder{mw, h} } diff --git a/plumbing/format/packfile/encoder.go b/plumbing/format/packfile/encoder.go index a8a7e967b..c9d19b332 100644 --- a/plumbing/format/packfile/encoder.go +++ b/plumbing/format/packfile/encoder.go @@ -2,7 +2,6 @@ package packfile import ( "compress/zlib" - "crypto" "fmt" "io" @@ -29,7 +28,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: hash.New(crypto.SHA1), + Hash: hash.New(hash.CryptoType), } mw := io.MultiWriter(w, h) ow := newOffsetWriter(mw) diff --git a/plumbing/format/packfile/encoder_test.go b/plumbing/format/packfile/encoder_test.go index c9d49c3b5..902d8ccaa 100644 --- a/plumbing/format/packfile/encoder_test.go +++ b/plumbing/format/packfile/encoder_test.go @@ -7,6 +7,7 @@ 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/hash" "github.com/go-git/go-git/v5/storage/memory" "github.com/go-git/go-billy/v5/memfs" @@ -30,10 +31,10 @@ func (s *EncoderSuite) SetUpTest(c *C) { } func (s *EncoderSuite) TestCorrectPackHeader(c *C) { - hash, err := s.enc.Encode([]plumbing.Hash{}, 10) + h, err := s.enc.Encode([]plumbing.Hash{}, 10) c.Assert(err, IsNil) - hb := [20]byte(hash) + hb := [hash.Size]byte(h) // PACK + VERSION + OBJECTS + HASH expectedResult := []byte{'P', 'A', 'C', 'K', 0, 0, 0, 2, 0, 0, 0, 0} @@ -51,7 +52,7 @@ func (s *EncoderSuite) TestCorrectPackWithOneEmptyObject(c *C) { _, err := s.store.SetEncodedObject(o) c.Assert(err, IsNil) - hash, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, 10) + h, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, 10) c.Assert(err, IsNil) // PACK + VERSION(2) + OBJECT NUMBER(1) @@ -64,7 +65,7 @@ func (s *EncoderSuite) TestCorrectPackWithOneEmptyObject(c *C) { []byte{120, 156, 1, 0, 0, 255, 255, 0, 0, 0, 1}...) // + HASH - hb := [20]byte(hash) + hb := [hash.Size]byte(h) expectedResult = append(expectedResult, hb[:]...) result := s.buf.Bytes() diff --git a/plumbing/format/packfile/scanner_test.go b/plumbing/format/packfile/scanner_test.go index 892a27ca0..9dcc3594d 100644 --- a/plumbing/format/packfile/scanner_test.go +++ b/plumbing/format/packfile/scanner_test.go @@ -6,6 +6,7 @@ import ( fixtures "github.com/go-git/go-git-fixtures/v4" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" . "gopkg.in/check.v1" ) @@ -71,7 +72,7 @@ func (s *ScannerSuite) testNextObjectHeader(c *C, tag string, n, err := p.Checksum() c.Assert(err, IsNil) - c.Assert(n, HasLen, 20) + c.Assert(n, HasLen, hash.Size) } func (s *ScannerSuite) TestNextObjectHeaderWithOutReadObject(c *C) { diff --git a/plumbing/hash.go b/plumbing/hash.go index 2fab7593b..39bb73fbb 100644 --- a/plumbing/hash.go +++ b/plumbing/hash.go @@ -2,7 +2,6 @@ package plumbing import ( "bytes" - "crypto" "encoding/hex" "sort" "strconv" @@ -11,7 +10,7 @@ import ( ) // Hash SHA1 hashed content -type Hash [20]byte +type Hash [hash.Size]byte // ZeroHash is Hash with value zero var ZeroHash Hash @@ -47,7 +46,7 @@ type Hasher struct { } func NewHasher(t ObjectType, size int64) Hasher { - h := Hasher{hash.New(crypto.SHA1)} + h := Hasher{hash.New(hash.CryptoType)} h.Write(t.Bytes()) h.Write([]byte(" ")) h.Write([]byte(strconv.FormatInt(size, 10))) @@ -75,10 +74,11 @@ func (p HashSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // IsHash returns true if the given string is a valid hash. func IsHash(s string) bool { - if len(s) != 40 { + switch len(s) { + case hash.HexSize: + _, err := hex.DecodeString(s) + return err == nil + default: return false } - - _, err := hex.DecodeString(s) - return err == nil } diff --git a/plumbing/protocol/packp/ulreq_decode_test.go b/plumbing/protocol/packp/ulreq_decode_test.go index 9628f0fdd..efcc7b456 100644 --- a/plumbing/protocol/packp/ulreq_decode_test.go +++ b/plumbing/protocol/packp/ulreq_decode_test.go @@ -8,6 +8,7 @@ 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/hash" "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" . "gopkg.in/check.v1" @@ -119,8 +120,8 @@ type byHash []plumbing.Hash func (a byHash) Len() int { return len(a) } func (a byHash) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byHash) Less(i, j int) bool { - ii := [20]byte(a[i]) - jj := [20]byte(a[j]) + ii := [hash.Size]byte(a[i]) + jj := [hash.Size]byte(a[j]) return bytes.Compare(ii[:], jj[:]) < 0 } diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go index 6c386f799..2772b6736 100644 --- a/storage/filesystem/dotgit/dotgit.go +++ b/storage/filesystem/dotgit/dotgit.go @@ -16,6 +16,7 @@ import ( "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/storage" "github.com/go-git/go-git/v5/utils/ioutil" @@ -552,8 +553,8 @@ func (d *DotGit) hasPack(h plumbing.Hash) error { } func (d *DotGit) objectPath(h plumbing.Hash) string { - hash := h.String() - return d.fs.Join(objectsPath, hash[0:2], hash[2:40]) + hex := h.String() + return d.fs.Join(objectsPath, hex[0:2], hex[2:hash.HexSize]) } // incomingObjectPath is intended to add support for a git pre-receive hook @@ -563,15 +564,16 @@ func (d *DotGit) objectPath(h plumbing.Hash) string { // // More on git hooks found here : https://git-scm.com/docs/githooks // More on 'quarantine'/incoming directory here: -// https://git-scm.com/docs/git-receive-pack +// +// https://git-scm.com/docs/git-receive-pack func (d *DotGit) incomingObjectPath(h plumbing.Hash) string { hString := h.String() if d.incomingDirName == "" { - return d.fs.Join(objectsPath, hString[0:2], hString[2:40]) + return d.fs.Join(objectsPath, hString[0:2], hString[2:hash.HexSize]) } - return d.fs.Join(objectsPath, d.incomingDirName, hString[0:2], hString[2:40]) + return d.fs.Join(objectsPath, d.incomingDirName, hString[0:2], hString[2:hash.HexSize]) } // hasIncomingObjects searches for an incoming directory and keeps its name diff --git a/storage/filesystem/dotgit/writers.go b/storage/filesystem/dotgit/writers.go index e2ede938c..849b7a176 100644 --- a/storage/filesystem/dotgit/writers.go +++ b/storage/filesystem/dotgit/writers.go @@ -9,6 +9,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/format/idxfile" "github.com/go-git/go-git/v5/plumbing/format/objfile" "github.com/go-git/go-git/v5/plumbing/format/packfile" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-billy/v5" ) @@ -277,8 +278,8 @@ func (w *ObjectWriter) Close() error { } func (w *ObjectWriter) save() error { - hash := w.Hash().String() - file := w.fs.Join(objectsPath, hash[0:2], hash[2:40]) + hex := w.Hash().String() + file := w.fs.Join(objectsPath, hex[0:2], hex[2:hash.HexSize]) return w.fs.Rename(w.f.Name(), file) } From 8e8281008ee98e9864fa9597be3e16aaecdf9891 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 7 Mar 2023 23:41:07 +0000 Subject: [PATCH 092/205] examples: Add example for SHA256 repositories Add a new example on initializing a new repository with object format SHA256. A new Makefile target was created to run the example to ensure that new changes won't break SHA256 support going forwards. Relates to the SHA256 implementation, defined in #706. Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 3 ++ Makefile | 6 ++++ _examples/sha256/main.go | 66 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 _examples/sha256/main.go diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index c945e72ff..3a40c0c58 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -42,6 +42,9 @@ jobs: - name: Test run: make test-coverage + - name: Test SHA256 + run: make test-sha256 + - name: Build go-git with CGO disabled run: go build ./... env: diff --git a/Makefile b/Makefile index 2acb8bc45..f0c1abb60 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,12 @@ test: @echo "running against `git version`"; \ $(GOTEST) -race ./... +TEMP_REPO := $(shell mktemp) +test-sha256: + $(GOCMD) run -tags sha256 _examples/sha256/main.go $(TEMP_REPO) + cd $(TEMP_REPO) && git fsck + rm -rf $(TEMP_REPO) + test-coverage: @echo "running against `git version`"; \ echo "" > $(COVERAGE_REPORT); \ diff --git a/_examples/sha256/main.go b/_examples/sha256/main.go new file mode 100644 index 000000000..2a257e863 --- /dev/null +++ b/_examples/sha256/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/go-git/go-git/v5" + . "github.com/go-git/go-git/v5/_examples" + "github.com/go-git/go-git/v5/plumbing/format/config" + "github.com/go-git/go-git/v5/plumbing/object" +) + +// This example requires building with the sha256 tag for it to work: +// go run -tags sha256 main.go /tmp/repository + +// Basic example of how to initialise a repository using sha256 as the hashing algorithmn. +func main() { + CheckArgs("") + directory := os.Args[1] + + os.RemoveAll(directory) + + // Init a new repository using the ObjectFormat SHA256. + r, err := git.PlainInitWithOptions(directory, &git.PlainInitOptions{ObjectFormat: config.SHA256}) + CheckIfError(err) + + w, err := r.Worktree() + CheckIfError(err) + + // ... we need a file to commit so let's create a new file inside of the + // worktree of the project using the go standard library. + Info("echo \"hello world!\" > example-git-file") + filename := filepath.Join(directory, "example-git-file") + err = ioutil.WriteFile(filename, []byte("hello world!"), 0644) + CheckIfError(err) + + // Adds the new file to the staging area. + Info("git add example-git-file") + _, err = w.Add("example-git-file") + CheckIfError(err) + + // Commits the current staging area to the repository, with the new file + // just created. We should provide the object.Signature of Author of the + // commit Since version 5.0.1, we can omit the Author signature, being read + // from the git config files. + Info("git commit -m \"example go-git commit\"") + commit, err := w.Commit("example go-git commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "John Doe", + Email: "john@doe.org", + When: time.Now(), + }, + }) + + CheckIfError(err) + + // Prints the current HEAD to verify that all worked well. + Info("git show -s") + obj, err := r.CommitObject(commit) + CheckIfError(err) + + fmt.Println(obj) +} From cdd1e5562d10c6bb19f979ca32f3ae7ef646024f Mon Sep 17 00:00:00 2001 From: ZauberNerd Date: Tue, 15 Mar 2022 17:02:30 +0000 Subject: [PATCH 093/205] plumbing: resolve non-external delta references In a self-contained pack file delta references might point to base objects stored later in the file. In this case we need to replace placeholders for external refs with the actual base object and update the children references. Fixes: #484 Co-authored-by: Markus Wolf --- go.mod | 2 +- go.sum | 4 ++-- plumbing/format/packfile/parser.go | 9 +++++++++ plumbing/format/packfile/parser_test.go | 13 +++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 85fd7b132..95cb02bb9 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.0 github.com/go-git/go-billy/v5 v5.4.1 - github.com/go-git/go-git-fixtures/v4 v4.3.1 + github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f 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 diff --git a/go.sum b/go.sum index a7548e292..bb9bc8506 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4u github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -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/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= 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= diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go index 522c146f2..6012b0458 100644 --- a/plumbing/format/packfile/parser.go +++ b/plumbing/format/packfile/parser.go @@ -237,6 +237,15 @@ func (p *Parser) indexObjects() error { return err } + // Move children of placeholder parent into actual parent, in case this + // was a non-external delta reference. + if placeholder, ok := p.oiByHash[sha1]; ok { + ota.Children = placeholder.Children + for _, c := range ota.Children { + c.Parent = ota + } + } + ota.SHA1 = sha1 p.oiByHash[ota.SHA1] = ota } diff --git a/plumbing/format/packfile/parser_test.go b/plumbing/format/packfile/parser_test.go index 651d05f3d..b8d080f68 100644 --- a/plumbing/format/packfile/parser_test.go +++ b/plumbing/format/packfile/parser_test.go @@ -147,6 +147,19 @@ func (s *ParserSuite) TestResolveExternalRefsInThinPack(c *C) { c.Assert(err, IsNil) } +func (s *ParserSuite) TestResolveExternalRefs(c *C) { + extRefsThinPack := fixtures.ByTag("delta-before-base").One() + + scanner := packfile.NewScanner(extRefsThinPack.Packfile()) + + 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 fc0bf7f9ccee01257879565fe36c13306408f843 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 11 Apr 2023 20:32:17 +0100 Subject: [PATCH 094/205] ci: Fix upstream git build for master branch Changes upstream broke the compatibility test, which now requires libcurl installed in order to successfully build. Additionally, the .git-dist dir was added to the .gitignore file. Fixes: https://github.com/go-git/go-git/actions/runs/4670641376/jobs/8270625669 Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 2 +- .gitignore | 3 ++- Makefile | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index c945e72ff..b95cefd7f 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v3 - name: Install build dependencies - run: sudo apt-get install gettext + run: sudo apt-get install gettext libcurl4-openssl-dev - name: Git Build run: make build-git diff --git a/.gitignore b/.gitignore index e8d25f548..361133d03 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ coverage.out *~ coverage.txt profile.out -.tmp/ \ No newline at end of file +.tmp/ +.git-dist/ diff --git a/Makefile b/Makefile index 2acb8bc45..093a4f47c 100644 --- a/Makefile +++ b/Makefile @@ -35,4 +35,4 @@ test-coverage: $(GOTEST) -coverprofile=$(COVERAGE_REPORT) -coverpkg=./... -covermode=$(COVERAGE_MODE) ./... clean: - rm -rf $(GIT_DIST_PATH) \ No newline at end of file + rm -rf $(GIT_DIST_PATH) From a71389b9d9293a6fefc3c923e765f250235a5262 Mon Sep 17 00:00:00 2001 From: Joseda Rios Date: Thu, 16 Mar 2023 15:20:01 +0100 Subject: [PATCH 095/205] internal: Fix regression in csp-like match Signed-off-by: Joseda Rios --- internal/url/url.go | 6 +- internal/url/url_test.go | 107 ++++++++++++++++++++++-------- plumbing/transport/common_test.go | 20 ++++-- 3 files changed, 98 insertions(+), 35 deletions(-) diff --git a/internal/url/url.go b/internal/url/url.go index 14cf133de..266244869 100644 --- a/internal/url/url.go +++ b/internal/url/url.go @@ -5,8 +5,10 @@ import ( ) var ( - isSchemeRegExp = regexp.MustCompile(`^[^:]+://`) - scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P[^@]+)@)?(?P[^:\s]+):(?:(?P[0-9]{1,5})(?:\/|:))?(?P[^\\].*\/[^\\].*)$`) + isSchemeRegExp = regexp.MustCompile(`^[^:]+://`) + + // Ref: https://github.com/git/git/blob/master/Documentation/urls.txt#L37 + scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P[^@]+)@)?(?P[^:\s]+):(?:(?P[0-9]{1,5}):)?(?P[^\\].*)$`) ) // MatchesScheme returns true if the given string matches a URL-like diff --git a/internal/url/url_test.go b/internal/url/url_test.go index d168db6df..29c3f3e96 100755 --- a/internal/url/url_test.go +++ b/internal/url/url_test.go @@ -13,11 +13,27 @@ type URLSuite struct{} var _ = Suite(&URLSuite{}) func (s *URLSuite) TestMatchesScpLike(c *C) { + // See https://github.com/git/git/blob/master/Documentation/urls.txt#L37 examples := []string{ + // Most-extended case "git@github.com:james/bond", - "git@github.com:007/bond", + // Most-extended case with port "git@github.com:22:james/bond", + // Most-extended case with numeric path + "git@github.com:007/bond", + // Most-extended case with port and numeric "username" "git@github.com:22:007/bond", + // Single repo path + "git@github.com:bond", + // Single repo path with port + "git@github.com:22:bond", + // Single repo path with port and numeric repo + "git@github.com:22:007", + // Repo path ending with .git and starting with _ + "git@github.com:22:_007.git", + "git@github.com:_007.git", + "git@github.com:_james.git", + "git@github.com:_james/bond.git", } for _, url := range examples { @@ -26,35 +42,68 @@ func (s *URLSuite) TestMatchesScpLike(c *C) { } func (s *URLSuite) TestFindScpLikeComponents(c *C) { - url := "git@github.com:james/bond" - user, host, port, path := FindScpLikeComponents(url) - - c.Check(user, Equals, "git") - c.Check(host, Equals, "github.com") - c.Check(port, Equals, "") - c.Check(path, Equals, "james/bond") - - url = "git@github.com:007/bond" - user, host, port, path = FindScpLikeComponents(url) - - c.Check(user, Equals, "git") - c.Check(host, Equals, "github.com") - c.Check(port, Equals, "") - c.Check(path, Equals, "007/bond") - - url = "git@github.com:22:james/bond" - user, host, port, path = FindScpLikeComponents(url) + testCases := []struct { + url, user, host, port, path string + }{ + { + // Most-extended case + url: "git@github.com:james/bond", user: "git", host: "github.com", port: "", path: "james/bond", + }, + { + // Most-extended case with port + url: "git@github.com:22:james/bond", user: "git", host: "github.com", port: "22", path: "james/bond", + }, + { + // Most-extended case with numeric path + url: "git@github.com:007/bond", user: "git", host: "github.com", port: "", path: "007/bond", + }, + { + // Most-extended case with port and numeric path + url: "git@github.com:22:007/bond", user: "git", host: "github.com", port: "22", path: "007/bond", + }, + { + // Single repo path + url: "git@github.com:bond", user: "git", host: "github.com", port: "", path: "bond", + }, + { + // Single repo path with port + url: "git@github.com:22:bond", user: "git", host: "github.com", port: "22", path: "bond", + }, + { + // Single repo path with port and numeric path + url: "git@github.com:22:007", user: "git", host: "github.com", port: "22", path: "007", + }, + { + // Repo path ending with .git and starting with _ + url: "git@github.com:22:_007.git", user: "git", host: "github.com", port: "22", path: "_007.git", + }, + { + // Repo path ending with .git and starting with _ + url: "git@github.com:_007.git", user: "git", host: "github.com", port: "", path: "_007.git", + }, + { + // Repo path ending with .git and starting with _ + url: "git@github.com:_james.git", user: "git", host: "github.com", port: "", path: "_james.git", + }, + { + // Repo path ending with .git and starting with _ + url: "git@github.com:_james/bond.git", user: "git", host: "github.com", port: "", path: "_james/bond.git", + }, + } - c.Check(user, Equals, "git") - c.Check(host, Equals, "github.com") - c.Check(port, Equals, "22") - c.Check(path, Equals, "james/bond") + for _, tc := range testCases { + user, host, port, path := FindScpLikeComponents(tc.url) - url = "git@github.com:22:007/bond" - user, host, port, path = FindScpLikeComponents(url) + logf := func(ok bool) { + if ok { + return + } + c.Logf("%q check failed", tc.url) + } - c.Check(user, Equals, "git") - c.Check(host, Equals, "github.com") - c.Check(port, Equals, "22") - c.Check(path, Equals, "007/bond") + logf(c.Check(user, Equals, tc.user)) + logf(c.Check(host, Equals, tc.host)) + logf(c.Check(port, Equals, tc.port)) + logf(c.Check(path, Equals, tc.path)) + } } diff --git a/plumbing/transport/common_test.go b/plumbing/transport/common_test.go index 0c5a01a9a..db11303a4 100644 --- a/plumbing/transport/common_test.go +++ b/plumbing/transport/common_test.go @@ -95,16 +95,28 @@ func (s *SuiteCommon) TestNewEndpointSCPLike(c *C) { c.Assert(e.String(), Equals, "ssh://git@github.com/user/repository.git") } -func (s *SuiteCommon) TestNewEndpointSCPLikeWithPort(c *C) { +func (s *SuiteCommon) TestNewEndpointSCPLikeWithNumericPath(c *C) { e, err := NewEndpoint("git@github.com:9999/user/repository.git") c.Assert(err, IsNil) c.Assert(e.Protocol, Equals, "ssh") c.Assert(e.User, Equals, "git") c.Assert(e.Password, Equals, "") c.Assert(e.Host, Equals, "github.com") - c.Assert(e.Port, Equals, 9999) - c.Assert(e.Path, Equals, "user/repository.git") - c.Assert(e.String(), Equals, "ssh://git@github.com:9999/user/repository.git") + c.Assert(e.Port, Equals, 22) + c.Assert(e.Path, Equals, "9999/user/repository.git") + c.Assert(e.String(), Equals, "ssh://git@github.com/9999/user/repository.git") +} + +func (s *SuiteCommon) TestNewEndpointSCPLikeWithPort(c *C) { + e, err := NewEndpoint("git@github.com:8080:9999/user/repository.git") + c.Assert(err, IsNil) + c.Assert(e.Protocol, Equals, "ssh") + c.Assert(e.User, Equals, "git") + c.Assert(e.Password, Equals, "") + c.Assert(e.Host, Equals, "github.com") + c.Assert(e.Port, Equals, 8080) + c.Assert(e.Path, Equals, "9999/user/repository.git") + c.Assert(e.String(), Equals, "ssh://git@github.com:8080/9999/user/repository.git") } func (s *SuiteCommon) TestNewEndpointFileAbs(c *C) { From 9a5b08f5c32bad31a35a53c045ebf6c8409f8b2c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 4 Apr 2023 01:13:21 -0400 Subject: [PATCH 096/205] feat(clone): add mirror clone option Clone remote as a mirror. This fetches all remote refs, implies bare repository, and sets the appropriate configs. Fixes: https://github.com/go-git/go-git/issues/293 Update options.go Co-authored-by: Paulo Gomes Signed-off-by: Ayman Bagabas --- config/config.go | 8 ++++++++ options.go | 8 ++++++++ repository.go | 14 ++++++++++---- repository_test.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 83629fcef..60dfca4f0 100644 --- a/config/config.go +++ b/config/config.go @@ -264,6 +264,7 @@ const ( defaultBranchKey = "defaultBranch" repositoryFormatVersionKey = "repositoryformatversion" objectFormat = "objectformat" + mirrorKey = "mirror" // DefaultPackWindow holds the number of previous objects used to // generate deltas. The value 10 is the same used by git command. @@ -578,6 +579,8 @@ type RemoteConfig struct { // URLs the URLs of a remote repository. It must be non-empty. Fetch will // always use the first URL, while push will use all of them. URLs []string + // Mirror indicates that the repository is a mirror of remote. + Mirror bool // insteadOfRulesApplied have urls been modified insteadOfRulesApplied bool @@ -631,6 +634,7 @@ func (c *RemoteConfig) unmarshal(s *format.Subsection) error { c.Name = c.raw.Name c.URLs = append([]string(nil), c.raw.Options.GetAll(urlKey)...) c.Fetch = fetch + c.Mirror = c.raw.Options.Get(mirrorKey) == "true" return nil } @@ -663,6 +667,10 @@ func (c *RemoteConfig) marshal() *format.Subsection { c.raw.SetOption(fetchKey, values...) } + if c.Mirror { + c.raw.SetOption(mirrorKey, strconv.FormatBool(c.Mirror)) + } + return c.raw } diff --git a/options.go b/options.go index 738c1ce9c..6a38ad94f 100644 --- a/options.go +++ b/options.go @@ -46,6 +46,14 @@ type CloneOptions struct { ReferenceName plumbing.ReferenceName // Fetch only ReferenceName if true. SingleBranch bool + // Mirror clones the repository as a mirror. + // + // Compared to a bare clone, mirror not only maps local branches of the + // source to local branches of the target, it maps all refs (including + // remote-tracking branches, notes etc.) and sets up a refspec configuration + // such that all these refs are overwritten by a git remote update in the + // target repository. + Mirror bool // No checkout of HEAD after clone if true. NoCheckout bool // Limit fetching to the specified number of commits. diff --git a/repository.go b/repository.go index 56ae9761c..e009c5d06 100644 --- a/repository.go +++ b/repository.go @@ -444,6 +444,9 @@ func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOp return nil, err } + if o.Mirror { + isBare = true + } r, err := PlainInit(path, isBare) if err != nil { return nil, err @@ -851,9 +854,10 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { } c := &config.RemoteConfig{ - Name: o.RemoteName, - URLs: []string{o.URL}, - Fetch: r.cloneRefSpec(o), + Name: o.RemoteName, + URLs: []string{o.URL}, + Fetch: r.cloneRefSpec(o), + Mirror: o.Mirror, } if _, err := r.CreateRemote(c); err != nil { @@ -906,7 +910,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { return err } - if ref.Name().IsBranch() { + if !o.Mirror && ref.Name().IsBranch() { branchRef := ref.Name() branchName := strings.Split(string(branchRef), "refs/heads/")[1] @@ -937,6 +941,8 @@ const ( func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { switch { + case o.Mirror: + return []config.RefSpec{"+refs/*:refs/*"} case o.ReferenceName.IsTag(): return []config.RefSpec{ config.RefSpec(fmt.Sprintf(refspecTag, o.ReferenceName.Short())), diff --git a/repository_test.go b/repository_test.go index 468ce33b0..ed3e7e69b 100644 --- a/repository_test.go +++ b/repository_test.go @@ -189,6 +189,35 @@ func (s *RepositorySuite) TestCloneContext(c *C) { c.Assert(err, Equals, context.Canceled) } +func (s *RepositorySuite) TestCloneMirror(c *C) { + r, err := Clone(memory.NewStorage(), nil, &CloneOptions{ + URL: fixtures.Basic().One().URL, + Mirror: true, + }) + + c.Assert(err, IsNil) + + refs, err := r.References() + var count int + refs.ForEach(func(r *plumbing.Reference) error { c.Log(r); count++; return nil }) + c.Assert(err, IsNil) + // 6 refs total from github.com/git-fixtures/basic.git: + // - HEAD + // - refs/heads/master + // - refs/heads/branch + // - refs/pull/1/head + // - refs/pull/2/head + // - refs/pull/2/merge + c.Assert(count, Equals, 6) + + cfg, err := r.Config() + c.Assert(err, IsNil) + + c.Assert(cfg.Core.IsBare, Equals, true) + c.Assert(cfg.Remotes[DefaultRemoteName].Validate(), IsNil) + c.Assert(cfg.Remotes[DefaultRemoteName].Mirror, Equals, true) +} + func (s *RepositorySuite) TestCloneWithTags(c *C) { url := s.GetLocalRepositoryURL( fixtures.ByURL("https://github.com/git-fixtures/tags.git").One(), From d4e36fa406a18e78a24fa9cd68d4e34d8434b6b7 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 23 Apr 2023 15:01:36 +0100 Subject: [PATCH 097/205] build: Bump dependencies. Fixes #667 - github.com/ProtonMail/go-crypto to version 0.0.0-20230417170513-8ee5748c52b5. - github.com/imdario/mergo to version 0.3.15. - golang.org/x/crypto to version 0.8.0. - golang.org/x/net to version 0.9.0. - golang.org/x/sys to version 0.7.0. - golang.org/x/text to version 0.9.0. - github.com/go-git/gcfg to version v1.5.1-0.20230307220236-3a3c6141e376 Signed-off-by: Paulo Gomes --- go.mod | 16 +++++++--------- go.sum | 41 ++++++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 95cb02bb9..27b74a36a 100644 --- a/go.mod +++ b/go.mod @@ -3,28 +3,26 @@ module github.com/go-git/go-git/v5 go 1.13 require ( - github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 + github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5 github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 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/gcfg v1.5.1-0.20230307220236-3a3c6141e376 github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f github.com/google/go-cmp v0.5.9 - github.com/imdario/mergo v0.3.13 + github.com/imdario/mergo v0.3.15 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jessevdk/go-flags v1.5.0 github.com/kevinburke/ssh_config v1.2.0 github.com/pjbgf/sha1cd v0.3.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.3 - golang.org/x/crypto v0.6.0 - golang.org/x/net v0.7.0 - golang.org/x/sys v0.5.0 - golang.org/x/text v0.7.0 + golang.org/x/crypto v0.8.0 + golang.org/x/net v0.9.0 + golang.org/x/sys v0.7.0 + golang.org/x/text v0.9.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 bb9bc8506..f4fb14682 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ 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-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5 h1:QXMwHM/lB4ZQhdEF7JUTNgYOJR/gWoFbgQ/2Aj1h3Dk= +github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -19,8 +19,8 @@ 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/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= @@ -28,8 +28,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= 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/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 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= @@ -73,10 +73,12 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0 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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -84,10 +86,12 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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/sync v0.1.0/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= @@ -104,25 +108,32 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 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/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/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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= @@ -134,6 +145,6 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN 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.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= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From ca09e555e4b4b6bdd4c25acafe1b0e42353e2c50 Mon Sep 17 00:00:00 2001 From: Force Charlie Date: Tue, 25 Apr 2023 21:51:46 +0800 Subject: [PATCH 098/205] storage: filesystem/dotgit, Improve load packed-refs --- plumbing/reference_test.go | 2 +- storage/filesystem/dotgit/dotgit.go | 80 ++++++++++++++--------------- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go index e69076ff6..04dfef93d 100644 --- a/plumbing/reference_test.go +++ b/plumbing/reference_test.go @@ -105,7 +105,7 @@ func (s *ReferenceSuite) TestIsTag(c *C) { func benchMarkReferenceString(r *Reference, b *testing.B) { for n := 0; n < b.N; n++ { - r.String() + _ = r.String() } } diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go index 686832430..5bde37f6b 100644 --- a/storage/filesystem/dotgit/dotgit.go +++ b/storage/filesystem/dotgit/dotgit.go @@ -718,48 +718,56 @@ func (d *DotGit) Ref(name plumbing.ReferenceName) (*plumbing.Reference, error) { return d.packedRef(name) } -func (d *DotGit) findPackedRefsInFile(f billy.File) ([]*plumbing.Reference, error) { +func (d *DotGit) findPackedRefsInFile(f billy.File, recv refsRecv) error { s := bufio.NewScanner(f) - var refs []*plumbing.Reference for s.Scan() { ref, err := d.processLine(s.Text()) if err != nil { - return nil, err + return err } - if ref != nil { - refs = append(refs, ref) + if !recv(ref) { + // skip parse + return nil } } - - return refs, s.Err() + if err := s.Err(); err != nil { + return err + } + return nil } -func (d *DotGit) findPackedRefs() (r []*plumbing.Reference, err error) { +// refsRecv: returning true means that the reference continues to be resolved, otherwise it is stopped, which will speed up the lookup of a single reference. +type refsRecv func(*plumbing.Reference) bool + +func (d *DotGit) findPackedRefs(recv refsRecv) error { f, err := d.fs.Open(packedRefsPath) if err != nil { if os.IsNotExist(err) { - return nil, nil + return nil } - return nil, err + return err } defer ioutil.CheckClose(f, &err) - return d.findPackedRefsInFile(f) + return d.findPackedRefsInFile(f, recv) } func (d *DotGit) packedRef(name plumbing.ReferenceName) (*plumbing.Reference, error) { - refs, err := d.findPackedRefs() - if err != nil { + var ref *plumbing.Reference + if err := d.findPackedRefs(func(r *plumbing.Reference) bool { + if r != nil && r.Name() == name { + ref = r + // ref found + return false + } + return true + }); err != nil { return nil, err } - - for _, ref := range refs { - if ref.Name() == name { - return ref, nil - } + if ref != nil { + return ref, nil } - return nil, plumbing.ErrReferenceNotFound } @@ -779,34 +787,22 @@ func (d *DotGit) RemoveRef(name plumbing.ReferenceName) error { return d.rewritePackedRefsWithoutRef(name) } -func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) (err error) { - packedRefs, err := d.findPackedRefs() - if err != nil { - return err - } - - for _, ref := range packedRefs { - if !seen[ref.Name()] { - *refs = append(*refs, ref) - seen[ref.Name()] = true +func refsRecvFunc(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) refsRecv { + return func(r *plumbing.Reference) bool { + if r != nil && !seen[r.Name()] { + *refs = append(*refs, r) + seen[r.Name()] = true } + return true } - return nil } -func (d *DotGit) addRefsFromPackedRefsFile(refs *[]*plumbing.Reference, f billy.File, seen map[plumbing.ReferenceName]bool) (err error) { - packedRefs, err := d.findPackedRefsInFile(f) - if err != nil { - return err - } +func (d *DotGit) addRefsFromPackedRefs(refs *[]*plumbing.Reference, seen map[plumbing.ReferenceName]bool) (err error) { + return d.findPackedRefs(refsRecvFunc(refs, seen)) +} - for _, ref := range packedRefs { - if !seen[ref.Name()] { - *refs = append(*refs, ref) - seen[ref.Name()] = true - } - } - return nil +func (d *DotGit) addRefsFromPackedRefsFile(refs *[]*plumbing.Reference, f billy.File, seen map[plumbing.ReferenceName]bool) (err error) { + return d.findPackedRefsInFile(f, refsRecvFunc(refs, seen)) } func (d *DotGit) openAndLockPackedRefs(doCreate bool) ( From 3aa7575a4d660a250edd06864d6401a302951fab Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 29 Mar 2023 23:26:53 -0400 Subject: [PATCH 099/205] fix: git grep bare repositories Perform grep on `*Repository` instead of `*Worktree`. Fixes: https://github.com/go-git/go-git/issues/68 --- options.go | 8 ++++- worktree.go | 21 ++++++++----- worktree_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 9 deletions(-) diff --git a/options.go b/options.go index 738c1ce9c..112d60004 100644 --- a/options.go +++ b/options.go @@ -642,7 +642,13 @@ var ( ) // Validate validates the fields and sets the default values. +// +// TODO: deprecate in favor of Validate(r *Repository) in v6. func (o *GrepOptions) Validate(w *Worktree) error { + return o.validate(w.r) +} + +func (o *GrepOptions) validate(r *Repository) error { if !o.CommitHash.IsZero() && o.ReferenceName != "" { return ErrHashOrReference } @@ -650,7 +656,7 @@ func (o *GrepOptions) Validate(w *Worktree) error { // If none of CommitHash and ReferenceName are provided, set commit hash of // the repository's head. if o.CommitHash.IsZero() && o.ReferenceName == "" { - ref, err := w.r.Head() + ref, err := r.Head() if err != nil { return err } diff --git a/worktree.go b/worktree.go index d28ba3241..7d2f3b47f 100644 --- a/worktree.go +++ b/worktree.go @@ -290,7 +290,7 @@ func (w *Worktree) ResetSparsely(opts *ResetOptions, dirs []string) error { return nil } - t, err := w.getTreeFromCommitHash(opts.Commit) + t, err := w.r.getTreeFromCommitHash(opts.Commit) if err != nil { return err } @@ -633,8 +633,8 @@ func (w *Worktree) addIndexFromFile(name string, h plumbing.Hash, idx *indexBuil return nil } -func (w *Worktree) getTreeFromCommitHash(commit plumbing.Hash) (*object.Tree, error) { - c, err := w.r.CommitObject(commit) +func (r *Repository) getTreeFromCommitHash(commit plumbing.Hash) (*object.Tree, error) { + c, err := r.CommitObject(commit) if err != nil { return nil, err } @@ -802,9 +802,9 @@ func (gr GrepResult) String() string { return fmt.Sprintf("%s:%s:%d:%s", gr.TreeName, gr.FileName, gr.LineNumber, gr.Content) } -// Grep performs grep on a worktree. -func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) { - if err := opts.Validate(w); err != nil { +// Grep performs grep on a repository. +func (r *Repository) Grep(opts *GrepOptions) ([]GrepResult, error) { + if err := opts.validate(r); err != nil { return nil, err } @@ -814,7 +814,7 @@ func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) { var treeName string if opts.ReferenceName != "" { - ref, err := w.r.Reference(opts.ReferenceName, true) + ref, err := r.Reference(opts.ReferenceName, true) if err != nil { return nil, err } @@ -827,7 +827,7 @@ func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) { // Obtain a tree from the commit hash and get a tracked files iterator from // the tree. - tree, err := w.getTreeFromCommitHash(commitHash) + tree, err := r.getTreeFromCommitHash(commitHash) if err != nil { return nil, err } @@ -836,6 +836,11 @@ func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) { return findMatchInFiles(fileiter, treeName, opts) } +// Grep performs grep on a worktree. +func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) { + return w.r.Grep(opts) +} + // findMatchInFiles takes a FileIter, worktree name and GrepOptions, and // returns a slice of GrepResult containing the result of regex pattern matching // in content of all the files. diff --git a/worktree_test.go b/worktree_test.go index ac56a4688..1d6a05fd2 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -2211,6 +2211,87 @@ func (s *WorktreeSuite) TestGrep(c *C) { } } +func (s *WorktreeSuite) TestGrepBare(c *C) { + cases := []struct { + name string + options GrepOptions + wantResult []GrepResult + dontWantResult []GrepResult + wantError error + }{ + { + name: "basic word match", + options: GrepOptions{ + Patterns: []*regexp.Regexp{regexp.MustCompile("import")}, + CommitHash: plumbing.ZeroHash, + }, + wantResult: []GrepResult{ + { + FileName: "go/example.go", + LineNumber: 3, + Content: "import (", + TreeName: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + }, + { + FileName: "vendor/foo.go", + LineNumber: 3, + Content: "import \"fmt\"", + TreeName: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", + }, + }, + }, + } + + path := fixtures.Basic().ByTag("worktree").One().Worktree().Root() + + dir, clean := s.TemporalDir() + defer clean() + + r, err := PlainClone(dir, true, &CloneOptions{ + URL: path, + }) + c.Assert(err, IsNil) + + for _, tc := range cases { + gr, err := r.Grep(&tc.options) + if tc.wantError != nil { + c.Assert(err, Equals, tc.wantError) + } else { + c.Assert(err, IsNil) + } + + // Iterate through the results and check if the wanted result is present + // in the got result. + for _, wantResult := range tc.wantResult { + found := false + for _, gotResult := range gr { + if wantResult == gotResult { + found = true + break + } + } + if !found { + c.Errorf("unexpected grep results for %q, expected result to contain: %v", tc.name, wantResult) + } + } + + // Iterate through the results and check if the not wanted result is + // present in the got result. + for _, dontWantResult := range tc.dontWantResult { + found := false + for _, gotResult := range gr { + if dontWantResult == gotResult { + found = true + break + } + } + if found { + c.Errorf("unexpected grep results for %q, expected result to NOT contain: %v", tc.name, dontWantResult) + } + } + } +} + func (s *WorktreeSuite) TestResetLingeringDirectories(c *C) { dir, clean := s.TemporalDir() defer clean() From cf51e2febf37332f11ae63feca768d9672e10a36 Mon Sep 17 00:00:00 2001 From: John Pastore Date: Tue, 21 Mar 2023 09:15:32 -0400 Subject: [PATCH 100/205] Worktree: Status, add check to see if file already checked in [Fixes 718] Checks if an ignored file was previously checked in. If it was, then the file is not ignored matching native git behavior. --- worktree_status.go | 4 +++- worktree_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/worktree_status.go b/worktree_status.go index f3091cff6..a26c9e51f 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -169,7 +169,9 @@ func (w *Worktree) excludeIgnoredChanges(changes merkletrie.Changes) merkletrie. if len(path) != 0 { isDir := (len(ch.To) > 0 && ch.To.IsDir()) || (len(ch.From) > 0 && ch.From.IsDir()) if m.Match(path, isDir) { - continue + if len(ch.From) == 0 { + continue + } } } res = append(res, ch) diff --git a/worktree_test.go b/worktree_test.go index ac56a4688..294b7ed23 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -871,6 +871,51 @@ func (s *WorktreeSuite) TestStatusEmpty(c *C) { c.Assert(status, NotNil) } +func (s *WorktreeSuite) TestStatusCheckedInBeforeIgnored(c *C) { + fs := memfs.New() + storage := memory.NewStorage() + + r, err := Init(storage, fs) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + err = util.WriteFile(fs, "fileToIgnore", []byte("Initial data"), 0755) + c.Assert(err, IsNil) + _, err = w.Add("fileToIgnore") + c.Assert(err, IsNil) + _, err = w.Commit("Added file that will be ignored later", &CommitOptions{}) + c.Assert(err, IsNil) + + err = util.WriteFile(fs, ".gitignore", []byte("fileToIgnore\nsecondIgnoredFile"), 0755) + c.Assert(err, IsNil) + _, err = w.Add(".gitignore") + c.Assert(err, IsNil) + _, err = w.Commit("Added .gitignore", &CommitOptions{}) + c.Assert(err, IsNil) + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) + c.Assert(status, NotNil) + + err = util.WriteFile(fs, "secondIgnoredFile", []byte("Should be completly ignored"), 0755) + c.Assert(err, IsNil) + status = nil + status, err = w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) + c.Assert(status, NotNil) + + err = util.WriteFile(fs, "fileToIgnore", []byte("Updated data"), 0755) + c.Assert(err, IsNil) + status = nil + status, err = w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, false) + c.Assert(status, NotNil) +} + func (s *WorktreeSuite) TestStatusEmptyDirty(c *C) { fs := memfs.New() err := util.WriteFile(fs, "foo", []byte("foo"), 0755) From a4b11abc55bf88fbd07a00a5985a34750bee1d72 Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Wed, 3 May 2023 14:01:24 +1000 Subject: [PATCH 101/205] git: fix cloning with branch name Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- repository.go | 16 ++++++++++++++-- repository_test.go | 10 +++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/repository.go b/repository.go index e009c5d06..be5f140d7 100644 --- a/repository.go +++ b/repository.go @@ -1012,9 +1012,21 @@ func (r *Repository) fetchAndUpdateReferences( return nil, err } - resolvedRef, err := storer.ResolveReference(remoteRefs, ref) + var resolvedRef *plumbing.Reference + // return error from checking the raw ref passed in + var rawRefError error + for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) { + resolvedRef, err = storer.ResolveReference(remoteRefs, plumbing.ReferenceName(fmt.Sprintf(rule, ref))) + + if err == nil { + break + } else if rawRefError == nil { + rawRefError = err + } + } + if err != nil { - return nil, err + return nil, rawRefError } refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef) diff --git a/repository_test.go b/repository_test.go index ed3e7e69b..0080a8384 100644 --- a/repository_test.go +++ b/repository_test.go @@ -1020,6 +1020,14 @@ func (s *RepositorySuite) TestCloneConfig(c *C) { } func (s *RepositorySuite) TestCloneSingleBranchAndNonHEAD(c *C) { + s.testCloneSingleBranchAndNonHEADReference(c, "refs/heads/branch") +} + +func (s *RepositorySuite) TestCloneSingleBranchAndNonHEADAndNonFull(c *C) { + s.testCloneSingleBranchAndNonHEADReference(c, "branch") +} + +func (s *RepositorySuite) testCloneSingleBranchAndNonHEADReference(c *C, ref string) { r, _ := Init(memory.NewStorage(), nil) head, err := r.Head() @@ -1028,7 +1036,7 @@ func (s *RepositorySuite) TestCloneSingleBranchAndNonHEAD(c *C) { err = r.clone(context.Background(), &CloneOptions{ URL: s.GetBasicLocalRepositoryURL(), - ReferenceName: plumbing.ReferenceName("refs/heads/branch"), + ReferenceName: plumbing.ReferenceName(ref), SingleBranch: true, }) From 19b39e150071832541f2cdcb669446a8609f57e7 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 3 May 2023 08:39:07 +0100 Subject: [PATCH 102/205] git: Add support to ls-remote with peeled references. Fixes #749 A new PeelingOption field was introduced into ListOptions. The new options include the default (and backwards compatible) IgnorePeeled. Plus another two variations which either only returns peeled references (OnlyPeeled), or append peeled references to the list (AppendPeeled). The ls-remote example was updated to align with upstream, in which peeled references are appended to the results by default. A new ErrEmptyUrls error is now returned when List or ListContext do not receive a URL to work with, to improve overall execution flow. Signed-off-by: Paulo Gomes --- _examples/ls-remote/main.go | 14 ++++++++++++-- options.go | 20 ++++++++++++++++++++ remote.go | 29 +++++++++++++++++++++++------ remote_test.go | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/_examples/ls-remote/main.go b/_examples/ls-remote/main.go index af038d6e2..e49e8c9e4 100644 --- a/_examples/ls-remote/main.go +++ b/_examples/ls-remote/main.go @@ -2,25 +2,35 @@ package main import ( "log" + "os" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/storage/memory" + + . "github.com/go-git/go-git/v5/_examples" ) // Retrieve remote tags without cloning repository func main() { + CheckArgs("") + url := os.Args[1] + + Info("git ls-remote --tags %s", url) // Create the remote with repository URL rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{ Name: "origin", - URLs: []string{"https://github.com/Zenika/MARCEL"}, + URLs: []string{url}, }) log.Print("Fetching tags...") // We can then use every Remote functions to retrieve wanted information - refs, err := rem.List(&git.ListOptions{}) + refs, err := rem.List(&git.ListOptions{ + // Returns all references, including peeled references. + PeelingOption: git.AppendPeeled, + }) if err != nil { log.Fatal(err) } diff --git a/options.go b/options.go index e79cf9dae..04c83de61 100644 --- a/options.go +++ b/options.go @@ -624,8 +624,28 @@ type ListOptions struct { InsecureSkipTLS bool // CABundle specify additional ca bundle with system cert pool CABundle []byte + + // PeelingOption defines how peeled objects are handled during a + // remote list. + PeelingOption PeelingOption } +// PeelingOption represents the different ways to handle peeled references. +// +// Peeled references represent the underlying object of an annotated +// (or signed) tag. Refer to upstream documentation for more info: +// https://github.com/git/git/blob/master/Documentation/technical/reftable.txt +type PeelingOption uint8 + +const ( + // IgnorePeeled ignores all peeled reference names. This is the default behavior. + IgnorePeeled PeelingOption = 0 + // OnlyPeeled returns only peeled reference names. + OnlyPeeled PeelingOption = 1 + // AppendPeeled appends peeled reference names to the reference list. + AppendPeeled PeelingOption = 2 +) + // CleanOptions describes how a clean should be performed. type CleanOptions struct { Dir bool diff --git a/remote.go b/remote.go index db78ae718..ff72bdf36 100644 --- a/remote.go +++ b/remote.go @@ -33,6 +33,7 @@ var ( ErrDeleteRefNotSupported = errors.New("server does not support delete-refs") ErrForceNeeded = errors.New("some refs were not updated") ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec") + ErrEmptyUrls = errors.New("URLs cannot be empty") ) type NoMatchingRefSpecError struct { @@ -54,6 +55,9 @@ const ( // repo containing this remote, when not using the multi-ack // protocol. Setting this to 0 means there is no limit. maxHavesToVisitPerRef = 100 + + // peeledSuffix is the suffix used to build peeled reference names. + peeledSuffix = "^{}" ) // Remote represents a connection to a remote repository. @@ -1259,6 +1263,10 @@ func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) { } func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) { + if r.c == nil || len(r.c.URLs) == 0 { + return nil, ErrEmptyUrls + } + s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle) if err != nil { return nil, err @@ -1282,13 +1290,22 @@ func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Refe } var resultRefs []*plumbing.Reference - err = refs.ForEach(func(ref *plumbing.Reference) error { - resultRefs = append(resultRefs, ref) - return nil - }) - if err != nil { - return nil, err + if o.PeelingOption == AppendPeeled || o.PeelingOption == IgnorePeeled { + err = refs.ForEach(func(ref *plumbing.Reference) error { + resultRefs = append(resultRefs, ref) + return nil + }) + if err != nil { + return nil, err + } } + + if o.PeelingOption == AppendPeeled || o.PeelingOption == OnlyPeeled { + for k, v := range ar.Peeled { + resultRefs = append(resultRefs, plumbing.NewReferenceFromStrings(k+"^{}", v.String())) + } + } + return resultRefs, nil } diff --git a/remote_test.go b/remote_test.go index 751c89a1b..164e4e509 100644 --- a/remote_test.go +++ b/remote_test.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "time" "github.com/go-git/go-git/v5/config" @@ -865,6 +866,7 @@ func (s *RemoteSuite) TestPushForceWithLease_success(c *C) { c.Assert(sto.SetReference(newCommit), IsNil) ref, err := sto.Reference("refs/heads/branch") + c.Assert(err, IsNil) c.Log(ref.String()) url := dstFs.Root() @@ -1210,6 +1212,41 @@ func (s *RemoteSuite) TestList(c *C) { } } +func (s *RemoteSuite) TestListPeeling(c *C) { + remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{ + Name: DefaultRemoteName, + URLs: []string{"https://github.com/git-fixtures/tags.git"}, + }) + + for _, tc := range []struct { + peelingOption PeelingOption + expectPeeled bool + expectNonPeeled bool + }{ + {peelingOption: AppendPeeled, expectPeeled: true, expectNonPeeled: true}, + {peelingOption: IgnorePeeled, expectPeeled: false, expectNonPeeled: true}, + {peelingOption: OnlyPeeled, expectPeeled: true, expectNonPeeled: false}, + } { + refs, err := remote.List(&ListOptions{ + PeelingOption: tc.peelingOption, + }) + c.Assert(err, IsNil) + c.Assert(len(refs) > 0, Equals, true) + + foundPeeled, foundNonPeeled := false, false + for _, ref := range refs { + if strings.HasSuffix(ref.Name().String(), peeledSuffix) { + foundPeeled = true + } else { + foundNonPeeled = true + } + } + + c.Assert(foundPeeled, Equals, tc.expectPeeled) + c.Assert(foundNonPeeled, Equals, tc.expectNonPeeled) + } +} + func (s *RemoteSuite) TestListTimeout(c *C) { remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{ Name: DefaultRemoteName, From 7d183c99d041ecf5f98d6b4c7eb25449e21a5153 Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Thu, 4 May 2023 07:33:19 +1000 Subject: [PATCH 103/205] storage: filesystem, Populate index before use. Fixes #148 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- storage/filesystem/object.go | 10 ++++++++++ storage/filesystem/object_test.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/storage/filesystem/object.go b/storage/filesystem/object.go index 21667fa5a..846a7b860 100644 --- a/storage/filesystem/object.go +++ b/storage/filesystem/object.go @@ -537,14 +537,21 @@ func (s *ObjectStorage) findObjectInPackfile(h plumbing.Hash) (plumbing.Hash, pl return plumbing.ZeroHash, plumbing.ZeroHash, -1 } +// HashesWithPrefix returns all objects with a hash that starts with a prefix by searching for +// them in the packfile and the git object directories. func (s *ObjectStorage) HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error) { hashes, err := s.dir.ObjectsWithPrefix(prefix) if err != nil { return nil, err } + seen := hashListAsMap(hashes) + // TODO: This could be faster with some idxfile changes, // or diving into the packfile. + if err := s.requireIndex(); err != nil { + return nil, err + } for _, index := range s.index { ei, err := index.Entries() if err != nil { @@ -558,6 +565,9 @@ func (s *ObjectStorage) HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error) return nil, err } if bytes.HasPrefix(e.Hash[:], prefix) { + if _, ok := seen[e.Hash]; ok { + continue + } hashes = append(hashes, e.Hash) } } diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go index 19a791464..b5f137f29 100644 --- a/storage/filesystem/object_test.go +++ b/storage/filesystem/object_test.go @@ -406,6 +406,21 @@ func (s *FsSuite) TestHashesWithPrefix(c *C) { c.Assert(hashes[0].String(), Equals, "f3dfe29d268303fc6e1bbce268605fc99573406e") } +func (s *FsSuite) TestHashesWithPrefixFromPackfile(c *C) { + // Same setup as TestGetFromPackfile + fixtures.Basic().ByTag(".git").Test(c, func(f *fixtures.Fixture) { + fs := f.DotGit() + o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault()) + + expected := plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5") + // Only pass the first 8 bytes + hashes, err := o.HashesWithPrefix(expected[:8]) + c.Assert(err, IsNil) + c.Assert(hashes, HasLen, 1) + c.Assert(hashes[0], Equals, expected) + }) +} + func BenchmarkPackfileIter(b *testing.B) { defer fixtures.Clean() From c2a93140c4d2a7df5666c7e436d8f1cb337a579d Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 13 Apr 2023 12:49:53 +0530 Subject: [PATCH 104/205] plumbing/transport: add ProxyOptions to specify proxy details Signed-off-by: Sanskar Jaiswal --- options.go | 11 ++++++++++- plumbing/transport/common.go | 31 +++++++++++++++++++++++++++++++ remote.go | 17 +++++++++-------- repository.go | 1 + 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/options.go b/options.go index 04c83de61..84744c745 100644 --- a/options.go +++ b/options.go @@ -73,6 +73,8 @@ type CloneOptions struct { InsecureSkipTLS bool // CABundle specify additional ca bundle with system cert pool CABundle []byte + // ProxyOptions provides info required for connecting to a proxy. + ProxyOptions transport.ProxyOptions } // Validate validates the fields and sets the default values. @@ -124,6 +126,8 @@ type PullOptions struct { InsecureSkipTLS bool // CABundle specify additional ca bundle with system cert pool CABundle []byte + // ProxyOptions provides info required for connecting to a proxy. + ProxyOptions transport.ProxyOptions } // Validate validates the fields and sets the default values. @@ -180,6 +184,8 @@ type FetchOptions struct { InsecureSkipTLS bool // CABundle specify additional ca bundle with system cert pool CABundle []byte + // ProxyOptions provides info required for connecting to a proxy. + ProxyOptions transport.ProxyOptions } // Validate validates the fields and sets the default values. @@ -243,6 +249,8 @@ type PushOptions struct { Options map[string]string // Atomic sets option to be an atomic push Atomic bool + // ProxyOptions provides info required for connecting to a proxy. + ProxyOptions transport.ProxyOptions } // ForceWithLease sets fields on the lease @@ -624,10 +632,11 @@ type ListOptions struct { InsecureSkipTLS bool // CABundle specify additional ca bundle with system cert pool CABundle []byte - // PeelingOption defines how peeled objects are handled during a // remote list. PeelingOption PeelingOption + // ProxyOptions provides info required for connecting to a proxy. + ProxyOptions transport.ProxyOptions } // PeelingOption represents the different ways to handle peeled references. diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index a2a78f028..89bd3d528 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -116,6 +116,37 @@ type Endpoint struct { InsecureSkipTLS bool // CaBundle specify additional ca bundle with system cert pool CaBundle []byte + // Proxy provides info required for connecting to a proxy. + Proxy ProxyOptions +} + +type ProxyOptions struct { + URL string + Username string + Password string +} + +func (o *ProxyOptions) Validate() error { + if o.URL != "" { + _, err := url.Parse(o.URL) + return err + } + return nil +} + +func (o *ProxyOptions) FullURL() (*url.URL, error) { + proxyURL, err := url.Parse(o.URL) + if err != nil { + return nil, err + } + if o.Username != "" { + if o.Password != "" { + proxyURL.User = url.UserPassword(o.Username, o.Password) + } else { + proxyURL.User = url.User(o.Username) + } + } + return proxyURL, nil } var defaultPorts = map[string]int{ diff --git a/remote.go b/remote.go index ff72bdf36..df2649112 100644 --- a/remote.go +++ b/remote.go @@ -113,7 +113,7 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { o.RemoteURL = r.c.URLs[0] } - s, err := newSendPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle) + s, err := newSendPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions) if err != nil { return err } @@ -414,7 +414,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen o.RemoteURL = r.c.URLs[0] } - s, err := newUploadPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle) + s, err := newUploadPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions) if err != nil { return nil, err } @@ -511,8 +511,8 @@ func depthChanged(before []plumbing.Hash, s storage.Storer) (bool, error) { return false, nil } -func newUploadPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte) (transport.UploadPackSession, error) { - c, ep, err := newClient(url, auth, insecure, cabundle) +func newUploadPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.UploadPackSession, error) { + c, ep, err := newClient(url, insecure, cabundle, proxyOpts) if err != nil { return nil, err } @@ -520,8 +520,8 @@ func newUploadPackSession(url string, auth transport.AuthMethod, insecure bool, return c.NewUploadPackSession(ep, auth) } -func newSendPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte) (transport.ReceivePackSession, error) { - c, ep, err := newClient(url, auth, insecure, cabundle) +func newSendPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.ReceivePackSession, error) { + c, ep, err := newClient(url, insecure, cabundle, proxyOpts) if err != nil { return nil, err } @@ -529,13 +529,14 @@ func newSendPackSession(url string, auth transport.AuthMethod, insecure bool, ca return c.NewReceivePackSession(ep, auth) } -func newClient(url string, auth transport.AuthMethod, insecure bool, cabundle []byte) (transport.Transport, *transport.Endpoint, error) { +func newClient(url string, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.Transport, *transport.Endpoint, error) { ep, err := transport.NewEndpoint(url) if err != nil { return nil, nil, err } ep.InsecureSkipTLS = insecure ep.CaBundle = cabundle + ep.Proxy = proxyOpts c, err := client.NewClient(ep) if err != nil { @@ -1267,7 +1268,7 @@ func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Refe return nil, ErrEmptyUrls } - s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle) + s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions) if err != nil { return nil, err } diff --git a/repository.go b/repository.go index be5f140d7..287b597bb 100644 --- a/repository.go +++ b/repository.go @@ -873,6 +873,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { RemoteName: o.RemoteName, InsecureSkipTLS: o.InsecureSkipTLS, CABundle: o.CABundle, + ProxyOptions: o.ProxyOptions, }, o.ReferenceName) if err != nil { return err From 223727feb195642234a600040b12a2d3597d0989 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 13 Apr 2023 16:29:13 +0530 Subject: [PATCH 105/205] plumbing: transport/ssh, add support for custom proxy URLs Signed-off-by: Sanskar Jaiswal --- plumbing/transport/ssh/common.go | 30 ++++- .../transport/ssh/internal/test/proxy_test.go | 113 ++++++++++++++++++ .../transport/ssh/internal/test/test_utils.go | 83 +++++++++++++ plumbing/transport/ssh/proxy_test.go | 72 +++++++++-- plumbing/transport/ssh/upload_pack_test.go | 3 +- 5 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 plumbing/transport/ssh/internal/test/proxy_test.go create mode 100644 plumbing/transport/ssh/internal/test/test_utils.go diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go index e06958a3b..6617d9b71 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -4,6 +4,7 @@ package ssh import ( "context" "fmt" + "net" "reflect" "strconv" "strings" @@ -139,7 +140,7 @@ func (c *command) connect() error { overrideConfig(c.config, config) - c.client, err = dial("tcp", hostWithPort, config) + c.client, err = dial("tcp", hostWithPort, c.endpoint.Proxy, config) if err != nil { return err } @@ -154,7 +155,7 @@ func (c *command) connect() error { return nil } -func dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { +func dial(network, addr string, proxyOpts transport.ProxyOptions, config *ssh.ClientConfig) (*ssh.Client, error) { var ( ctx = context.Background() cancel context.CancelFunc @@ -166,10 +167,33 @@ func dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { } defer cancel() - conn, err := proxy.Dial(ctx, network, addr) + var conn net.Conn + var err error + + if proxyOpts.URL != "" { + proxyUrl, err := proxyOpts.FullURL() + if err != nil { + return nil, err + } + dialer, err := proxy.FromURL(proxyUrl, proxy.Direct) + if err != nil { + return nil, err + } + + // Try to use a ContextDialer, but fall back to a Dialer if that goes south. + ctxDialer, ok := dialer.(proxy.ContextDialer) + if !ok { + return nil, fmt.Errorf("expected ssh proxy dialer to be of type %s; got %s", + reflect.TypeOf(ctxDialer), reflect.TypeOf(dialer)) + } + conn, err = ctxDialer.DialContext(ctx, "tcp", addr) + } else { + conn, err = proxy.Dial(ctx, network, addr) + } if err != nil { return nil, err } + c, chans, reqs, err := ssh.NewClientConn(conn, addr, config) if err != nil { return nil, err diff --git a/plumbing/transport/ssh/internal/test/proxy_test.go b/plumbing/transport/ssh/internal/test/proxy_test.go new file mode 100644 index 000000000..8baac2b57 --- /dev/null +++ b/plumbing/transport/ssh/internal/test/proxy_test.go @@ -0,0 +1,113 @@ +package test + +import ( + "context" + "fmt" + "io/ioutil" + "log" + "net" + "os" + "path/filepath" + "sync/atomic" + "testing" + + "github.com/armon/go-socks5" + "github.com/gliderlabs/ssh" + "github.com/go-git/go-git/v5/plumbing/transport" + ggssh "github.com/go-git/go-git/v5/plumbing/transport/ssh" + + fixtures "github.com/go-git/go-git-fixtures/v4" + stdssh "golang.org/x/crypto/ssh" + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type ProxyEnvSuite struct { + fixtures.Suite + port int + base string +} + +var _ = Suite(&ProxyEnvSuite{}) + +var socksProxiedRequests int32 + +// This test tests proxy support via an env var, i.e. `ALL_PROXY`. +// Its located in a separate package because golang caches the value +// of proxy env vars leading to misleading/unexpected test results. +func (s *ProxyEnvSuite) TestCommand(c *C) { + socksListener, err := net.Listen("tcp", "localhost:0") + c.Assert(err, IsNil) + + socksServer, err := socks5.New(&socks5.Config{ + Rules: TestProxyRule{}, + }) + c.Assert(err, IsNil) + go func() { + socksServer.Serve(socksListener) + }() + socksProxyAddr := fmt.Sprintf("socks5://localhost:%d", socksListener.Addr().(*net.TCPAddr).Port) + os.Setenv("ALL_PROXY", socksProxyAddr) + defer os.Unsetenv("ALL_PROXY") + + sshListener, err := net.Listen("tcp", "localhost:0") + c.Assert(err, IsNil) + sshServer := &ssh.Server{Handler: HandlerSSH} + go func() { + log.Fatal(sshServer.Serve(sshListener)) + }() + + s.port = sshListener.Addr().(*net.TCPAddr).Port + s.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.port)) + c.Assert(err, IsNil) + + ggssh.DefaultAuthBuilder = func(user string) (ggssh.AuthMethod, error) { + return &ggssh.Password{User: user}, nil + } + + ep := s.prepareRepository(c, fixtures.Basic().One(), "basic.git") + c.Assert(err, IsNil) + + client := ggssh.NewClient(&stdssh.ClientConfig{ + HostKeyCallback: stdssh.InsecureIgnoreHostKey(), + }) + r, err := client.NewUploadPackSession(ep, nil) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + + info, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(info, NotNil) + proxyUsed := atomic.LoadInt32(&socksProxiedRequests) > 0 + c.Assert(proxyUsed, Equals, true) +} + +func (s *ProxyEnvSuite) prepareRepository(c *C, f *fixtures.Fixture, name string) *transport.Endpoint { + fs := f.DotGit() + + err := fixtures.EnsureIsBare(fs) + c.Assert(err, IsNil) + + path := filepath.Join(s.base, name) + err = os.Rename(fs.Root(), path) + c.Assert(err, IsNil) + + return s.newEndpoint(c, name) +} + +func (s *ProxyEnvSuite) newEndpoint(c *C, name string) *transport.Endpoint { + ep, err := transport.NewEndpoint(fmt.Sprintf( + "ssh://git@localhost:%d/%s/%s", s.port, filepath.ToSlash(s.base), name, + )) + + c.Assert(err, IsNil) + return ep +} + +type TestProxyRule struct{} + +func (dr TestProxyRule) Allow(ctx context.Context, req *socks5.Request) (context.Context, bool) { + atomic.AddInt32(&socksProxiedRequests, 1) + return ctx, true +} diff --git a/plumbing/transport/ssh/internal/test/test_utils.go b/plumbing/transport/ssh/internal/test/test_utils.go new file mode 100644 index 000000000..c3797b1d2 --- /dev/null +++ b/plumbing/transport/ssh/internal/test/test_utils.go @@ -0,0 +1,83 @@ +package test + +import ( + "fmt" + "io" + "os/exec" + "runtime" + "strings" + "sync" + + "github.com/gliderlabs/ssh" +) + +func HandlerSSH(s ssh.Session) { + cmd, stdin, stderr, stdout, err := buildCommand(s.Command()) + if err != nil { + fmt.Println(err) + return + } + + if err := cmd.Start(); err != nil { + fmt.Println(err) + return + } + + go func() { + defer stdin.Close() + io.Copy(stdin, s) + }() + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + io.Copy(s.Stderr(), stderr) + }() + + go func() { + defer wg.Done() + io.Copy(s, stdout) + }() + + wg.Wait() + + if err := cmd.Wait(); err != nil { + return + } + +} + +func buildCommand(c []string) (cmd *exec.Cmd, stdin io.WriteCloser, stderr, stdout io.ReadCloser, err error) { + if len(c) != 2 { + err = fmt.Errorf("invalid command") + return + } + + // fix for Windows environments + var path string + if runtime.GOOS == "windows" { + path = strings.Replace(c[1], "/C:/", "C:/", 1) + } else { + path = c[1] + } + + cmd = exec.Command(c[0], path) + stdout, err = cmd.StdoutPipe() + if err != nil { + return + } + + stdin, err = cmd.StdinPipe() + if err != nil { + return + } + + stderr, err = cmd.StderrPipe() + if err != nil { + return + } + + return +} diff --git a/plumbing/transport/ssh/proxy_test.go b/plumbing/transport/ssh/proxy_test.go index 3caf1ff1d..2fab8512b 100644 --- a/plumbing/transport/ssh/proxy_test.go +++ b/plumbing/transport/ssh/proxy_test.go @@ -1,36 +1,88 @@ package ssh import ( + "context" "fmt" + "io/ioutil" "log" "net" "os" + "sync/atomic" "github.com/armon/go-socks5" + "github.com/gliderlabs/ssh" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/ssh/internal/test" + + fixtures "github.com/go-git/go-git-fixtures/v4" + stdssh "golang.org/x/crypto/ssh" . "gopkg.in/check.v1" ) type ProxySuite struct { - UploadPackSuite + u UploadPackSuite + fixtures.Suite } var _ = Suite(&ProxySuite{}) -func (s *ProxySuite) SetUpSuite(c *C) { - s.UploadPackSuite.SetUpSuite(c) +var socksProxiedRequests int32 - l, err := net.Listen("tcp", "localhost:0") +func (s *ProxySuite) TestCommand(c *C) { + socksListener, err := net.Listen("tcp", "localhost:0") c.Assert(err, IsNil) - server, err := socks5.New(&socks5.Config{}) + socksServer, err := socks5.New(&socks5.Config{ + AuthMethods: []socks5.Authenticator{socks5.UserPassAuthenticator{ + Credentials: socks5.StaticCredentials{ + "user": "pass", + }, + }}, + Rules: TestProxyRule{}, + }) c.Assert(err, IsNil) + go func() { + socksServer.Serve(socksListener) + }() + socksProxyAddr := fmt.Sprintf("socks5://localhost:%d", socksListener.Addr().(*net.TCPAddr).Port) - port := l.Addr().(*net.TCPAddr).Port - - err = os.Setenv("ALL_PROXY", fmt.Sprintf("socks5://localhost:%d", port)) + sshListener, err := net.Listen("tcp", "localhost:0") c.Assert(err, IsNil) - + sshServer := &ssh.Server{Handler: test.HandlerSSH} go func() { - log.Fatal(server.Serve(l)) + log.Fatal(sshServer.Serve(sshListener)) }() + + s.u.port = sshListener.Addr().(*net.TCPAddr).Port + s.u.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.u.port)) + c.Assert(err, IsNil) + + DefaultAuthBuilder = func(user string) (AuthMethod, error) { + return &Password{User: user}, nil + } + + ep := s.u.prepareRepository(c, fixtures.Basic().One(), "basic.git") + c.Assert(err, IsNil) + ep.Proxy = transport.ProxyOptions{ + URL: socksProxyAddr, + Username: "user", + Password: "pass", + } + + runner := runner{ + config: &stdssh.ClientConfig{ + HostKeyCallback: stdssh.InsecureIgnoreHostKey(), + }, + } + _, err = runner.Command(transport.UploadPackServiceName, ep, nil) + c.Assert(err, IsNil) + proxyUsed := atomic.LoadInt32(&socksProxiedRequests) > 0 + c.Assert(proxyUsed, Equals, true) +} + +type TestProxyRule struct{} + +func (dr TestProxyRule) Allow(ctx context.Context, req *socks5.Request) (context.Context, bool) { + atomic.AddInt32(&socksProxiedRequests, 1) + return ctx, true } diff --git a/plumbing/transport/ssh/upload_pack_test.go b/plumbing/transport/ssh/upload_pack_test.go index f172feeda..fafff485d 100644 --- a/plumbing/transport/ssh/upload_pack_test.go +++ b/plumbing/transport/ssh/upload_pack_test.go @@ -14,6 +14,7 @@ import ( "sync" "github.com/go-git/go-git/v5/plumbing/transport" + testutils "github.com/go-git/go-git/v5/plumbing/transport/ssh/internal/test" "github.com/go-git/go-git/v5/plumbing/transport/test" "github.com/gliderlabs/ssh" @@ -57,7 +58,7 @@ func (s *UploadPackSuite) SetUpSuite(c *C) { s.UploadPackSuite.EmptyEndpoint = s.prepareRepository(c, fixtures.ByTag("empty").One(), "empty.git") s.UploadPackSuite.NonExistentEndpoint = s.newEndpoint(c, "non-existent.git") - server := &ssh.Server{Handler: handlerSSH} + server := &ssh.Server{Handler: testutils.HandlerSSH} for _, opt := range s.opts { opt(server) } From 399b1ec2d598b7950816727b8d92e8580553372c Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Tue, 18 Apr 2023 16:31:58 +0530 Subject: [PATCH 106/205] plumbing: transport/http, refactor transport to cache underlying transport objects Refactor the in-built http transport to cache the underlying http transport objects mapped to its specific options for each Git transport object. This lets us reuse the transport for a specific set of configurations as recommended. (ref: https://pkg.go.dev/net/http#Transport) If there are no transport specific options provided, the default transport is used. Signed-off-by: Sanskar Jaiswal --- go.mod | 1 + go.sum | 2 + plumbing/transport/client/client.go | 32 ------ plumbing/transport/http/common.go | 147 ++++++++++++++++++++++-- plumbing/transport/http/common_test.go | 54 +++++++++ plumbing/transport/http/receive_pack.go | 2 +- plumbing/transport/http/transport.go | 38 ++++++ plumbing/transport/http/upload_pack.go | 2 +- 8 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 plumbing/transport/http/transport.go diff --git a/go.mod b/go.mod index 27b74a36a..9bcad627a 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/go-cmp v0.5.9 github.com/imdario/mergo v0.3.15 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 diff --git a/go.sum b/go.sum index f4fb14682..ccc6597f0 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8 github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= diff --git a/plumbing/transport/client/client.go b/plumbing/transport/client/client.go index 20c3d0560..1948c2301 100644 --- a/plumbing/transport/client/client.go +++ b/plumbing/transport/client/client.go @@ -3,10 +3,7 @@ package client import ( - "crypto/tls" - "crypto/x509" "fmt" - gohttp "net/http" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/file" @@ -24,14 +21,6 @@ var Protocols = map[string]transport.Transport{ "file": file.DefaultClient, } -var insecureClient = http.NewClient(&gohttp.Client{ - Transport: &gohttp.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, -}) - // InstallProtocol adds or modifies an existing protocol. func InstallProtocol(scheme string, c transport.Transport) { if c == nil { @@ -50,27 +39,6 @@ func NewClient(endpoint *transport.Endpoint) (transport.Transport, error) { } func getTransport(endpoint *transport.Endpoint) (transport.Transport, error) { - if endpoint.Protocol == "https" { - if endpoint.InsecureSkipTLS { - return insecureClient, nil - } - - if len(endpoint.CaBundle) != 0 { - rootCAs, _ := x509.SystemCertPool() - if rootCAs == nil { - rootCAs = x509.NewCertPool() - } - rootCAs.AppendCertsFromPEM(endpoint.CaBundle) - return http.NewClient(&gohttp.Client{ - Transport: &gohttp.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: rootCAs, - }, - }, - }), nil - } - } - f, ok := Protocols[endpoint.Protocol] if !ok { return nil, fmt.Errorf("unsupported scheme %q", endpoint.Protocol) diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go index d57c0feef..530034105 100644 --- a/plumbing/transport/http/common.go +++ b/plumbing/transport/http/common.go @@ -4,16 +4,21 @@ package http import ( "bytes" "context" + "crypto/tls" + "crypto/x509" "fmt" "net" "net/http" + "reflect" "strconv" "strings" + "sync" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/protocol/packp" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/utils/ioutil" + "github.com/golang/groupcache/lru" ) // it requires a bytes.Buffer, because we need to know the length @@ -74,40 +79,83 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) ( } type client struct { - c *http.Client + c *http.Client + transports *lru.Cache + m sync.RWMutex } -// DefaultClient is the default HTTP client, which uses `http.DefaultClient`. -var DefaultClient = NewClient(nil) +// ClientOptions holds user configurable options for the client. +type ClientOptions struct { + // CacheMaxEntries is the max no. of entries that the transport objects + // cache will hold at any given point of time. It must be a positive integer. + // Calling `client.addTransport()` after the cache has reached the specified + // size, will result in the least recently used transport getting deleted + // before the provided transport is added to the cache. + CacheMaxEntries int +} + +var ( + // defaultTransportCacheSize is the default capacity of the transport objects cache. + // Its value is 0 because transport caching is turned off by default and is an + // opt-in feature. + defaultTransportCacheSize = 0 + + // DefaultClient is the default HTTP client, which uses a net/http client configured + // with http.DefaultTransport. + DefaultClient = NewClient(nil) +) // NewClient creates a new client with a custom net/http client. // See `InstallProtocol` to install and override default http client. -// Unless a properly initialized client is given, it will fall back into -// `http.DefaultClient`. +// If the net/http client is nil or empty, it will use a net/http client configured +// with http.DefaultTransport. // // Note that for HTTP client cannot distinguish between private repositories and // unexistent repositories on GitHub. So it returns `ErrAuthorizationRequired` // for both. func NewClient(c *http.Client) transport.Transport { if c == nil { - return &client{http.DefaultClient} + c = &http.Client{ + Transport: http.DefaultTransport, + } } + return NewClientWithOptions(c, &ClientOptions{ + CacheMaxEntries: defaultTransportCacheSize, + }) +} - return &client{ +// NewClientWithOptions returns a new client configured with the provided net/http client +// and other custom options specific to the client. +// If the net/http client is nil or empty, it will use a net/http client configured +// with http.DefaultTransport. +func NewClientWithOptions(c *http.Client, opts *ClientOptions) transport.Transport { + if c == nil { + c = &http.Client{ + Transport: http.DefaultTransport, + } + } + cl := &client{ c: c, } + + if opts != nil { + if opts.CacheMaxEntries > 0 { + cl.transports = lru.New(opts.CacheMaxEntries) + } + } + return cl } func (c *client) NewUploadPackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( transport.UploadPackSession, error) { - return newUploadPackSession(c.c, ep, auth) + return newUploadPackSession(c, ep, auth) } func (c *client) NewReceivePackSession(ep *transport.Endpoint, auth transport.AuthMethod) ( transport.ReceivePackSession, error) { - return newReceivePackSession(c.c, ep, auth) + return newReceivePackSession(c, ep, auth) } type session struct { @@ -117,10 +165,87 @@ type session struct { advRefs *packp.AdvRefs } -func newSession(c *http.Client, ep *transport.Endpoint, auth transport.AuthMethod) (*session, error) { +func transportWithInsecureTLS(transport *http.Transport) { + if transport.TLSClientConfig == nil { + transport.TLSClientConfig = &tls.Config{} + } + transport.TLSClientConfig.InsecureSkipVerify = true +} + +func transportWithCABundle(transport *http.Transport, caBundle []byte) error { + rootCAs, err := x509.SystemCertPool() + if err != nil { + return err + } + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + rootCAs.AppendCertsFromPEM(caBundle) + if transport.TLSClientConfig == nil { + transport.TLSClientConfig = &tls.Config{} + } + transport.TLSClientConfig.RootCAs = rootCAs + return nil +} + +func configureTransport(transport *http.Transport, ep *transport.Endpoint) error { + if len(ep.CaBundle) > 0 { + if err := transportWithCABundle(transport, ep.CaBundle); err != nil { + return err + } + } + if ep.InsecureSkipTLS { + transportWithInsecureTLS(transport) + } + return nil +} + +func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (*session, error) { + var httpClient *http.Client + + // We need to configure the http transport if there are transport specific + // options present in the endpoint. + if len(ep.CaBundle) > 0 || ep.InsecureSkipTLS { + var transport *http.Transport + // if the client wasn't configured to have a cache for transports then just configure + // the transport and use it directly, otherwise try to use the cache. + if c.transports == nil { + tr, ok := c.c.Transport.(*http.Transport) + if !ok { + return nil, fmt.Errorf("expected underlying client transport to be of type: %s; got: %s", + reflect.TypeOf(transport), reflect.TypeOf(c.c.Transport)) + } + + transport = tr.Clone() + configureTransport(transport, ep) + } else { + transportOpts := transportOptions{ + caBundle: string(ep.CaBundle), + insecureSkipTLS: ep.InsecureSkipTLS, + } + var found bool + transport, found = c.fetchTransport(transportOpts) + + if !found { + transport = c.c.Transport.(*http.Transport).Clone() + configureTransport(transport, ep) + c.addTransport(transportOpts, transport) + } + } + + httpClient = &http.Client{ + Transport: transport, + CheckRedirect: c.c.CheckRedirect, + Jar: c.c.Jar, + Timeout: c.c.Timeout, + } + } else { + httpClient = c.c + } + s := &session{ auth: basicAuthFromEndpoint(ep), - client: c, + client: httpClient, endpoint: ep, } if auth != nil { diff --git a/plumbing/transport/http/common_test.go b/plumbing/transport/http/common_test.go index 4122e6279..41188e677 100644 --- a/plumbing/transport/http/common_test.go +++ b/plumbing/transport/http/common_test.go @@ -91,6 +91,60 @@ func (s *ClientSuite) TestNewHTTPError40x(c *C) { "unexpected client error.*") } +func (s *ClientSuite) Test_newSession(c *C) { + cl := NewClientWithOptions(nil, &ClientOptions{ + CacheMaxEntries: 2, + }).(*client) + + insecureEP := s.Endpoint + insecureEP.InsecureSkipTLS = true + session, err := newSession(cl, insecureEP, nil) + c.Assert(err, IsNil) + + sessionTransport := session.client.Transport.(*http.Transport) + c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true) + t, ok := cl.fetchTransport(transportOptions{ + insecureSkipTLS: true, + }) + // transport should be cached. + c.Assert(ok, Equals, true) + // cached transport should be the one that's used. + c.Assert(sessionTransport, Equals, t) + + caEndpoint := insecureEP + caEndpoint.CaBundle = []byte("this is the way") + session, err = newSession(cl, caEndpoint, nil) + c.Assert(err, IsNil) + + sessionTransport = session.client.Transport.(*http.Transport) + c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true) + c.Assert(sessionTransport.TLSClientConfig.RootCAs, NotNil) + t, ok = cl.fetchTransport(transportOptions{ + insecureSkipTLS: true, + caBundle: "this is the way", + }) + // transport should be cached. + c.Assert(ok, Equals, true) + // cached transport should be the one that's used. + c.Assert(sessionTransport, Equals, t) + + session, err = newSession(cl, caEndpoint, nil) + c.Assert(err, IsNil) + sessionTransport = session.client.Transport.(*http.Transport) + // transport that's going to be used should be cached already. + c.Assert(sessionTransport, Equals, t) + // no new transport got cached. + c.Assert(cl.transports.Len(), Equals, 2) + + // if the cache does not exist, the transport should still be correctly configured. + cl.transports = nil + session, err = newSession(cl, insecureEP, nil) + c.Assert(err, IsNil) + + sessionTransport = session.client.Transport.(*http.Transport) + c.Assert(sessionTransport.TLSClientConfig.InsecureSkipVerify, Equals, true) +} + func (s *ClientSuite) testNewHTTPError(c *C, code int, msg string) { req, _ := http.NewRequest("GET", "foo", nil) res := &http.Response{ diff --git a/plumbing/transport/http/receive_pack.go b/plumbing/transport/http/receive_pack.go index 4d14ff21e..4387ecffe 100644 --- a/plumbing/transport/http/receive_pack.go +++ b/plumbing/transport/http/receive_pack.go @@ -19,7 +19,7 @@ type rpSession struct { *session } -func newReceivePackSession(c *http.Client, ep *transport.Endpoint, auth transport.AuthMethod) (transport.ReceivePackSession, error) { +func newReceivePackSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (transport.ReceivePackSession, error) { s, err := newSession(c, ep, auth) return &rpSession{s}, err } diff --git a/plumbing/transport/http/transport.go b/plumbing/transport/http/transport.go new file mode 100644 index 000000000..cd6787ad3 --- /dev/null +++ b/plumbing/transport/http/transport.go @@ -0,0 +1,38 @@ +package http + +import ( + "net/http" +) + +// transportOptions contains transport specific configuration. +type transportOptions struct { + insecureSkipTLS bool + // []byte is not comparable. + caBundle string +} + +func (c *client) addTransport(opts transportOptions, transport *http.Transport) { + c.m.Lock() + c.transports.Add(opts, transport) + c.m.Unlock() +} + +func (c *client) removeTransport(opts transportOptions) { + c.m.Lock() + c.transports.Remove(opts) + c.m.Unlock() +} + +func (c *client) fetchTransport(opts transportOptions) (*http.Transport, bool) { + c.m.RLock() + t, ok := c.transports.Get(opts) + c.m.RUnlock() + if !ok { + return nil, false + } + transport, ok := t.(*http.Transport) + if !ok { + return nil, false + } + return transport, true +} diff --git a/plumbing/transport/http/upload_pack.go b/plumbing/transport/http/upload_pack.go index e735b3d7c..4f851459f 100644 --- a/plumbing/transport/http/upload_pack.go +++ b/plumbing/transport/http/upload_pack.go @@ -19,7 +19,7 @@ type upSession struct { *session } -func newUploadPackSession(c *http.Client, ep *transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) { +func newUploadPackSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) { s, err := newSession(c, ep, auth) return &upSession{s}, err } From a830187d90a6bc36f9466c075ed49076f591efa9 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Fri, 28 Apr 2023 17:17:00 +0530 Subject: [PATCH 107/205] plumbing: transport/http, add support for custom proxy URLs Add support for custom HTTP and HTTPS proxies for each session. The tests require server certificate and a matching private key to be able to run a TLS server and test HTTPS proxy functionality. The cert and the key are stored in `plumbing/transport/http/testdata/certs` and were generated using the following command: `openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt` Note: All details were left empty, except for the FQDN for which example.com was used. Signed-off-by: Sanskar Jaiswal --- go.mod | 1 + go.sum | 5 + plumbing/transport/http/common.go | 22 +++- .../http/internal/test/proxy_test.go | 72 +++++++++++ .../http/internal/test/test_utils.go | 43 +++++++ plumbing/transport/http/proxy_test.go | 119 ++++++++++++++++++ .../transport/http/testdata/certs/server.crt | 22 ++++ .../transport/http/testdata/certs/server.key | 28 +++++ plumbing/transport/http/transport.go | 2 + 9 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 plumbing/transport/http/internal/test/proxy_test.go create mode 100644 plumbing/transport/http/internal/test/test_utils.go create mode 100644 plumbing/transport/http/proxy_test.go create mode 100644 plumbing/transport/http/testdata/certs/server.crt create mode 100644 plumbing/transport/http/testdata/certs/server.key diff --git a/go.mod b/go.mod index 9bcad627a..f062b1a8d 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5 github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 + github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 github.com/emirpasic/gods v1.18.1 github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 diff --git a/go.sum b/go.sum index ccc6597f0..db7d92ba3 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= +github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 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= @@ -55,6 +59,7 @@ 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/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 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= diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go index 530034105..f9b7a0e59 100644 --- a/plumbing/transport/http/common.go +++ b/plumbing/transport/http/common.go @@ -9,6 +9,7 @@ import ( "fmt" "net" "net/http" + "net/url" "reflect" "strconv" "strings" @@ -188,6 +189,10 @@ func transportWithCABundle(transport *http.Transport, caBundle []byte) error { return nil } +func transportWithProxy(transport *http.Transport, proxyURL *url.URL) { + transport.Proxy = http.ProxyURL(proxyURL) +} + func configureTransport(transport *http.Transport, ep *transport.Endpoint) error { if len(ep.CaBundle) > 0 { if err := transportWithCABundle(transport, ep.CaBundle); err != nil { @@ -197,6 +202,14 @@ func configureTransport(transport *http.Transport, ep *transport.Endpoint) error if ep.InsecureSkipTLS { transportWithInsecureTLS(transport) } + + if ep.Proxy.URL != "" { + proxyURL, err := ep.Proxy.FullURL() + if err != nil { + return err + } + transportWithProxy(transport, proxyURL) + } return nil } @@ -205,7 +218,7 @@ func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (* // We need to configure the http transport if there are transport specific // options present in the endpoint. - if len(ep.CaBundle) > 0 || ep.InsecureSkipTLS { + if len(ep.CaBundle) > 0 || ep.InsecureSkipTLS || ep.Proxy.URL != "" { var transport *http.Transport // if the client wasn't configured to have a cache for transports then just configure // the transport and use it directly, otherwise try to use the cache. @@ -223,6 +236,13 @@ func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (* caBundle: string(ep.CaBundle), insecureSkipTLS: ep.InsecureSkipTLS, } + if ep.Proxy.URL != "" { + proxyURL, err := ep.Proxy.FullURL() + if err != nil { + return nil, err + } + transportOpts.proxyURL = *proxyURL + } var found bool transport, found = c.fetchTransport(transportOpts) diff --git a/plumbing/transport/http/internal/test/proxy_test.go b/plumbing/transport/http/internal/test/proxy_test.go new file mode 100644 index 000000000..6ae2943b0 --- /dev/null +++ b/plumbing/transport/http/internal/test/proxy_test.go @@ -0,0 +1,72 @@ +package test + +import ( + "context" + "crypto/tls" + "fmt" + "net" + nethttp "net/http" + "os" + "sync/atomic" + "testing" + + "github.com/elazarl/goproxy" + + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" + + . "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { TestingT(t) } + +type ProxySuite struct{} + +var _ = Suite(&ProxySuite{}) + +var proxiedRequests int32 + +// This test tests proxy support via an env var, i.e. `HTTPS_PROXY`. +// Its located in a separate package because golang caches the value +// of proxy env vars leading to misleading/unexpected test results. +func (s *ProxySuite) TestAdvertisedReferences(c *C) { + proxy := goproxy.NewProxyHttpServer() + proxy.Verbose = true + SetupHTTPSProxy(proxy, &proxiedRequests) + httpsListener, err := net.Listen("tcp", ":0") + c.Assert(err, IsNil) + defer httpsListener.Close() + httpProxyAddr := fmt.Sprintf("localhost:%d", httpsListener.Addr().(*net.TCPAddr).Port) + + proxyServer := nethttp.Server{ + Addr: httpProxyAddr, + Handler: proxy, + // Due to how golang manages http/2 when provided with custom TLS config, + // servers and clients running in the same process leads to issues. + // Ref: https://github.com/golang/go/issues/21336 + TLSConfig: &tls.Config{ + NextProtos: []string{"http/1.1"}, + }, + } + go proxyServer.ServeTLS(httpsListener, "../../testdata/certs/server.crt", "../../testdata/certs/server.key") + defer proxyServer.Close() + os.Setenv("HTTPS_PROXY", fmt.Sprintf("https://user:pass@%s", httpProxyAddr)) + defer os.Unsetenv("HTTPS_PROXY") + + endpoint, err := transport.NewEndpoint("https://github.com/git-fixtures/basic.git") + c.Assert(err, IsNil) + endpoint.InsecureSkipTLS = true + + client := http.DefaultClient + session, err := client.NewUploadPackSession(endpoint, nil) + c.Assert(err, IsNil) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + info, err := session.AdvertisedReferencesContext(ctx) + c.Assert(err, IsNil) + c.Assert(info, NotNil) + proxyUsed := atomic.LoadInt32(&proxiedRequests) > 0 + c.Assert(proxyUsed, Equals, true) +} diff --git a/plumbing/transport/http/internal/test/test_utils.go b/plumbing/transport/http/internal/test/test_utils.go new file mode 100644 index 000000000..6665fb3c6 --- /dev/null +++ b/plumbing/transport/http/internal/test/test_utils.go @@ -0,0 +1,43 @@ +package test + +import ( + "encoding/base64" + "strings" + "sync/atomic" + + "github.com/elazarl/goproxy" +) + +func SetupHTTPSProxy(proxy *goproxy.ProxyHttpServer, proxiedRequests *int32) { + var proxyHandler goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { + if strings.Contains(host, "github.com") { + user, pass, _ := ParseBasicAuth(ctx.Req.Header.Get("Proxy-Authorization")) + if user != "user" || pass != "pass" { + return goproxy.RejectConnect, host + } + atomic.AddInt32(proxiedRequests, 1) + return goproxy.OkConnect, host + } + // Reject if it isn't our request. + return goproxy.RejectConnect, host + } + proxy.OnRequest().HandleConnect(proxyHandler) +} + +// adapted from https://github.com/golang/go/blob/2ef70d9d0f98832c8103a7968b195e560a8bb262/src/net/http/request.go#L959 +func ParseBasicAuth(auth string) (username, password string, ok bool) { + const prefix = "Basic " + if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { + return "", "", false + } + c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + return "", "", false + } + cs := string(c) + username, password, ok = strings.Cut(cs, ":") + if !ok { + return "", "", false + } + return username, password, true +} diff --git a/plumbing/transport/http/proxy_test.go b/plumbing/transport/http/proxy_test.go new file mode 100644 index 000000000..f3024da92 --- /dev/null +++ b/plumbing/transport/http/proxy_test.go @@ -0,0 +1,119 @@ +package http + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "strings" + "sync/atomic" + + "github.com/elazarl/goproxy" + fixtures "github.com/go-git/go-git-fixtures/v4" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http/internal/test" + + . "gopkg.in/check.v1" +) + +type ProxySuite struct { + u UploadPackSuite + fixtures.Suite +} + +var _ = Suite(&ProxySuite{}) + +var proxiedRequests int32 + +func (s *ProxySuite) TestAdvertisedReferences(c *C) { + s.u.SetUpTest(c) + proxy := goproxy.NewProxyHttpServer() + proxy.Verbose = true + setupHTTPProxy(proxy, &proxiedRequests) + httpListener, err := net.Listen("tcp", ":0") + c.Assert(err, IsNil) + defer httpListener.Close() + + httpProxyAddr := fmt.Sprintf("http://localhost:%d", httpListener.Addr().(*net.TCPAddr).Port) + proxyServer := http.Server{ + Addr: httpProxyAddr, + Handler: proxy, + } + go proxyServer.Serve(httpListener) + defer proxyServer.Close() + + endpoint := s.u.prepareRepository(c, fixtures.Basic().One(), "basic.git") + endpoint.Proxy = transport.ProxyOptions{ + URL: httpProxyAddr, + Username: "user", + Password: "pass", + } + + s.u.Client = NewClient(nil) + session, err := s.u.Client.NewUploadPackSession(endpoint, nil) + c.Assert(err, IsNil) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + info, err := session.AdvertisedReferencesContext(ctx) + c.Assert(err, IsNil) + c.Assert(info, NotNil) + proxyUsed := atomic.LoadInt32(&proxiedRequests) > 0 + c.Assert(proxyUsed, Equals, true) + + atomic.StoreInt32(&proxiedRequests, 0) + test.SetupHTTPSProxy(proxy, &proxiedRequests) + httpsListener, err := net.Listen("tcp", ":0") + c.Assert(err, IsNil) + defer httpsListener.Close() + httpsProxyAddr := fmt.Sprintf("https://localhost:%d", httpsListener.Addr().(*net.TCPAddr).Port) + + tlsProxyServer := http.Server{ + Addr: httpsProxyAddr, + Handler: proxy, + // Due to how golang manages http/2 when provided with custom TLS config, + // servers and clients running in the same process leads to issues. + // Ref: https://github.com/golang/go/issues/21336 + TLSConfig: &tls.Config{ + NextProtos: []string{"http/1.1"}, + }, + } + go tlsProxyServer.ServeTLS(httpsListener, "testdata/certs/server.crt", "testdata/certs/server.key") + defer tlsProxyServer.Close() + + endpoint, err = transport.NewEndpoint("https://github.com/git-fixtures/basic.git") + c.Assert(err, IsNil) + endpoint.Proxy = transport.ProxyOptions{ + URL: httpsProxyAddr, + Username: "user", + Password: "pass", + } + endpoint.InsecureSkipTLS = true + + session, err = s.u.Client.NewUploadPackSession(endpoint, nil) + c.Assert(err, IsNil) + + info, err = session.AdvertisedReferencesContext(ctx) + c.Assert(err, IsNil) + c.Assert(info, NotNil) + proxyUsed = atomic.LoadInt32(&proxiedRequests) > 0 + c.Assert(proxyUsed, Equals, true) +} + +func setupHTTPProxy(proxy *goproxy.ProxyHttpServer, proxiedRequests *int32) { + // The request is being forwarded to the local test git server in this handler. + var proxyHandler goproxy.FuncReqHandler = func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { + if strings.Contains(req.Host, "localhost") { + user, pass, _ := test.ParseBasicAuth(req.Header.Get("Proxy-Authorization")) + if user != "user" || pass != "pass" { + return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusUnauthorized, "") + } + atomic.AddInt32(proxiedRequests, 1) + return req, nil + } + // Reject if it isn't our request. + return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "") + } + proxy.OnRequest().Do(proxyHandler) +} diff --git a/plumbing/transport/http/testdata/certs/server.crt b/plumbing/transport/http/testdata/certs/server.crt new file mode 100644 index 000000000..9bdec2ce9 --- /dev/null +++ b/plumbing/transport/http/testdata/certs/server.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIUWcuzUyG3EfGsXVUH0BAmnCJyNHswDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTIzMDMwNzA3MTgwNloXDTI0MDMwNjA3MTgwNlowWTELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAvyKX6vJXt1u+WBfBNJByFDAb7msdsk6SiPFlX5uyilaWmlRxvLo1 +GZMjjuQbs4wU6BAoZcgiELnsC9GSyxgrhk7NIW3ud/QD7s8ZxETxFLb0ur6tJj7/ +ETEcU/AKSl1FpeJbGHqGipYp5+0GU0zPDxRYqC2N3+fcGZPQbBwxb1f+MrBjWutb +3eNYTLdPH3W7RUqbunC1KZRJ8XOcU5XZ4qEaMkZYdz1QItxwPnpPuSZs53ga3TDF +zclpQcT8OH2JNwSI6bwlwFJ0Es06manw7XHmgd8anhix9FdsQYaTOW4kqh1iKQ/P +jPG50bdTUEqlOsaa+9av/qf+90npzt3xqQIDAQABo1MwUTAdBgNVHQ4EFgQUqTqb +q+jiJVgwftQS+YLcQWnvTuAwHwYDVR0jBBgwFoAUqTqbq+jiJVgwftQS+YLcQWnv +TuAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVUaFSikxyCy1 +4P/ZZgeuR7vEJ5vWBxKPw/jFNZUFWy2Ag32w1BhrDwoYoc1Awg76QF2TqBQAhFNm +ek9aE+L83P/R2UhE9+LHnzwdMXt9HYOI1grONk2z3lMI1y4FCJBxHfGyC/XMoNgZ +qP7UdLgLGTIMN3O1Fww416Hn8BHzxN4o5ZEHJZ6QPMuN8OLk9oVu3yQIq/QcmSDH +GT2RiwT5IJWMUKK1UrV+y3/T9FwW2qqu+LX+coxjk7HgDWy3y66V9ahLBt8kONcr +qK0zutoQ5WPSmvnD2Nr0LVLGXEd7hbQNO7bgjO2YOBtnagUQJt72i/OmvZv8Mfnp +Bu6Qgl5hDw== +-----END CERTIFICATE----- diff --git a/plumbing/transport/http/testdata/certs/server.key b/plumbing/transport/http/testdata/certs/server.key new file mode 100644 index 000000000..9a0cd8f92 --- /dev/null +++ b/plumbing/transport/http/testdata/certs/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/Ipfq8le3W75Y +F8E0kHIUMBvuax2yTpKI8WVfm7KKVpaaVHG8ujUZkyOO5BuzjBToEChlyCIQuewL +0ZLLGCuGTs0hbe539APuzxnERPEUtvS6vq0mPv8RMRxT8ApKXUWl4lsYeoaKlinn +7QZTTM8PFFioLY3f59wZk9BsHDFvV/4ysGNa61vd41hMt08fdbtFSpu6cLUplEnx +c5xTldnioRoyRlh3PVAi3HA+ek+5JmzneBrdMMXNyWlBxPw4fYk3BIjpvCXAUnQS +zTqZqfDtceaB3xqeGLH0V2xBhpM5biSqHWIpD8+M8bnRt1NQSqU6xpr71q/+p/73 +SenO3fGpAgMBAAECggEAQUjenQhzv5Rdmpdajcq8vHqGP9Rki0/dK1tQpex3elsD +C+nGA5GSq46feaIeeCBjz7QdKE7Im+/1WUAXJLm3vCNUW5PB/UTixwIEKg7mTY4E +X3jbiZHA661boKv/x9C+BmAff2fyZonN/ILwQymcG+l2MtOEfzMh8baUXSjwFbhg +B08u4iXjee0y9I0CGMYWfasHLOIuhACCFKtqnvdQp8B82g8eSPhme5IjfPP8KZVr +00n6z8m00HVk6/yYJ8pVZ82j3T+wH6IqvlvaC320sbto8YXV6i8GWHaJumzU4/9s +IRm4459E+NmNcLNY/TCu89zsfrgNirN+qFfvJIOTxQKBgQDtME8s4UP0MhGuJ2lD +1HD64fAxMC6Xp/QSzY91Yn79UNssUUV+IwjuUnLIz3U8DBs/QETLm7CkNtI7h5m5 +dBdeBBzCRGxhe8WqRfvceu4s0zr08ZkDaKLjFsBSnBsXZhKAAuRqBjnGAoAiKgVa +WpEAug00ThhQjipSY9tO9NSBawKBgQDOSz+8m2HJFktEdSctKIB9DesqlAg7YCyy +dHzywP0/r7wEvsCN7xPgCT5g8JBkRaFvLLKgw7gMKAUx8V2iwizEoDCAs/pbTWji +uZwPC8lWtbkpBMQIaP4Wap+GyFQJKv1/qZduwpkwkj+ok+m3WwIW55VFGyLn3XGG +VcLZm83aOwKBgQDXXI/nXjqHVZb8HEjWD+Ttx4yB/Q+xIAzbrc3edap8c5guKzUA +DOulCTOz5bq65PsweTh970V6NVS6PKt12lUFRpKeSeZmtS2LJ7RCQ1RTWxAjK+MV +V0LfEt9ZouhuXH3bwcSICFMY2VhirOjjW2xhzo0Cuw4UxqDi4kxU6rSxNQKBgQCI +sn5KmV/jot0/QK40E0mJFEcHkM4foiwcGGqPZWiq4eUh89CefJTb+OQX0nCrsSQ3 +ChRXyTlU/NPsczcL2cVWiZt6PUihZZsh2cJaigHhbkuCrcDEneX4rrCE3IwrAwy1 +oohRAawG7nI2X8UYFbs9uDlGcKPhpvBKBtw13DM87wKBgE8fOiFoTph//6piU7dV +pN33UfhPcAFwsIzxAH6Ljo6BYx2hfPRCxI2g0wchk6ydbDecLgMwVgugdJZ6+tRf +P+YV3wEwPcWOvWby3+EmJh0cXUTl6ZMA+eR4pvCi6kf2xJf9dRmEeNNhOuzn9Y0J +cT9yhBFG4iejKP0iTwET1JKY +-----END PRIVATE KEY----- diff --git a/plumbing/transport/http/transport.go b/plumbing/transport/http/transport.go index cd6787ad3..052f3c8e2 100644 --- a/plumbing/transport/http/transport.go +++ b/plumbing/transport/http/transport.go @@ -2,6 +2,7 @@ package http import ( "net/http" + "net/url" ) // transportOptions contains transport specific configuration. @@ -9,6 +10,7 @@ type transportOptions struct { insecureSkipTLS bool // []byte is not comparable. caBundle string + proxyURL url.URL } func (c *client) addTransport(opts transportOptions, transport *http.Transport) { From ecc9fe45b6261492c0cfdcdb1ff4e642252d6694 Mon Sep 17 00:00:00 2001 From: "matej.risek" Date: Thu, 4 May 2023 09:04:23 +0200 Subject: [PATCH 108/205] git: Add Depth to SubmoduleUpdateOptions --- options.go | 3 +++ submodule.go | 3 ++- submodule_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/options.go b/options.go index 738c1ce9c..d1200d8a0 100644 --- a/options.go +++ b/options.go @@ -284,6 +284,9 @@ type SubmoduleUpdateOptions struct { RecurseSubmodules SubmoduleRescursivity // Auth credentials, if required, to use with the remote repository. Auth transport.AuthMethod + // Depth limit fetching to the specified number of commits from the tip of + // each remote branch history. + Depth int } var ( diff --git a/submodule.go b/submodule.go index a202a9b60..b0c416961 100644 --- a/submodule.go +++ b/submodule.go @@ -243,7 +243,7 @@ func (s *Submodule) fetchAndCheckout( ctx context.Context, r *Repository, o *SubmoduleUpdateOptions, hash plumbing.Hash, ) error { if !o.NoFetch { - err := r.FetchContext(ctx, &FetchOptions{Auth: o.Auth}) + err := r.FetchContext(ctx, &FetchOptions{Auth: o.Auth, Depth: o.Depth}) if err != nil && err != NoErrAlreadyUpToDate { return err } @@ -265,6 +265,7 @@ func (s *Submodule) fetchAndCheckout( err := r.FetchContext(ctx, &FetchOptions{ Auth: o.Auth, RefSpecs: []config.RefSpec{refSpec}, + Depth: o.Depth, }) if err != nil && err != NoErrAlreadyUpToDate && err != ErrExactSHA1NotSupported { return err diff --git a/submodule_test.go b/submodule_test.go index 4bae544d1..a8a0681a1 100644 --- a/submodule_test.go +++ b/submodule_test.go @@ -233,3 +233,32 @@ func (s *SubmoduleSuite) TestSubmodulesUpdateContext(c *C) { err = sm.UpdateContext(ctx, &SubmoduleUpdateOptions{Init: true}) c.Assert(err, NotNil) } + +func (s *SubmoduleSuite) TestSubmodulesFetchDepth(c *C) { + if testing.Short() { + c.Skip("skipping test in short mode.") + } + + sm, err := s.Worktree.Submodule("basic") + c.Assert(err, IsNil) + + err = sm.Update(&SubmoduleUpdateOptions{ + Init: true, + Depth: 1, + }) + c.Assert(err, IsNil) + + r, err := sm.Repository() + c.Assert(err, IsNil) + + lr, err := r.Log(&LogOptions{}) + c.Assert(err, IsNil) + + commitCount := 0 + for _, err := lr.Next(); err == nil; _, err = lr.Next() { + commitCount++ + } + c.Assert(err, IsNil) + + c.Assert(commitCount, Equals, 1) +} From b8dd39abcc9dd35808ec625a90a68c0ace4973e4 Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Thu, 11 May 2023 11:17:04 +1000 Subject: [PATCH 109/205] git: Testing, Fix tests not cleaning temp folders Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- common_test.go | 24 ++++++++++++++++-------- repository_test.go | 18 +++++++++--------- submodule_test.go | 16 ++++++---------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/common_test.go b/common_test.go index c0c4009e2..3311a1793 100644 --- a/common_test.go +++ b/common_test.go @@ -37,7 +37,7 @@ func (s *BaseSuite) TearDownSuite(c *C) { s.Suite.TearDownSuite(c) } -func (s *BaseSuite) buildBasicRepository(c *C) { +func (s *BaseSuite) buildBasicRepository(_ *C) { f := fixtures.Basic().One() s.Repository = s.NewRepository(f) } @@ -105,13 +105,16 @@ func (s *BaseSuite) NewRepositoryFromPackfile(f *fixtures.Fixture) *Repository { storer := memory.NewStorage() p := f.Packfile() - defer p.Close() + defer func() { _ = p.Close() }() if err := packfile.UpdateObjectStorage(storer, p); err != nil { panic(err) } - storer.SetReference(plumbing.NewHashReference(plumbing.HEAD, plumbing.NewHash(f.Head))) + err := storer.SetReference(plumbing.NewHashReference(plumbing.HEAD, plumbing.NewHash(f.Head))) + if err != nil { + panic(err) + } r, err := Open(storer, memfs.New()) if err != nil { @@ -133,14 +136,17 @@ func (s *BaseSuite) GetLocalRepositoryURL(f *fixtures.Fixture) string { func (s *BaseSuite) TemporalDir() (path string, clean func()) { fs := osfs.New(os.TempDir()) - path, err := util.TempDir(fs, "", "") + relPath, err := util.TempDir(fs, "", "") if err != nil { panic(err) } - return fs.Join(fs.Root(), path), func() { - util.RemoveAll(fs, path) + path = fs.Join(fs.Root(), relPath) + clean = func() { + _ = util.RemoveAll(fs, relPath) } + + return } func (s *BaseSuite) TemporalFilesystem() (fs billy.Filesystem, clean func()) { @@ -155,9 +161,11 @@ func (s *BaseSuite) TemporalFilesystem() (fs billy.Filesystem, clean func()) { panic(err) } - return fs, func() { - util.RemoveAll(fs, path) + clean = func() { + _ = util.RemoveAll(fs, path) } + + return } type SuiteCommon struct{} diff --git a/repository_test.go b/repository_test.go index 0080a8384..45af396be 100644 --- a/repository_test.go +++ b/repository_test.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -60,17 +59,18 @@ func (s *RepositorySuite) TestInitNonStandardDotGit(c *C) { fs := osfs.New(dir) dot, _ := fs.Chroot("storage") - storage := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) + st := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) wt, _ := fs.Chroot("worktree") - r, err := Init(storage, wt) + r, err := Init(st, wt) c.Assert(err, IsNil) c.Assert(r, NotNil) f, err := fs.Open(fs.Join("worktree", ".git")) c.Assert(err, IsNil) + defer func() { _ = f.Close() }() - all, err := ioutil.ReadAll(f) + all, err := io.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, fmt.Sprintf("gitdir: %s\n", filepath.Join("..", "storage"))) @@ -85,9 +85,9 @@ func (s *RepositorySuite) TestInitStandardDotGit(c *C) { fs := osfs.New(dir) dot, _ := fs.Chroot(".git") - storage := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) + st := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) - r, err := Init(storage, fs) + r, err := Init(st, fs) c.Assert(err, IsNil) c.Assert(r, NotNil) @@ -3009,14 +3009,14 @@ func BenchmarkObjects(b *testing.B) { b.Run(f.URL, func(b *testing.B) { fs := f.DotGit() - storer := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) + st := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) worktree, err := fs.Chroot(filepath.Dir(fs.Root())) if err != nil { b.Fatal(err) } - repo, err := Open(storer, worktree) + repo, err := Open(st, worktree) if err != nil { b.Fatal(err) } @@ -3046,7 +3046,7 @@ func BenchmarkObjects(b *testing.B) { func BenchmarkPlainClone(b *testing.B) { for i := 0; i < b.N; i++ { - t, err := ioutil.TempDir("", "") + t, err := os.MkdirTemp("", "") if err != nil { b.Fatal(err) } diff --git a/submodule_test.go b/submodule_test.go index a8a0681a1..92943e11d 100644 --- a/submodule_test.go +++ b/submodule_test.go @@ -2,7 +2,6 @@ package git import ( "context" - "os" "path/filepath" "testing" @@ -15,7 +14,7 @@ import ( type SubmoduleSuite struct { BaseSuite Worktree *Worktree - path string + clean func() } var _ = Suite(&SubmoduleSuite{}) @@ -23,8 +22,8 @@ var _ = Suite(&SubmoduleSuite{}) func (s *SubmoduleSuite) SetUpTest(c *C) { path := fixtures.ByTag("submodule").One().Worktree().Root() - dir, clean := s.TemporalDir() - defer clean() + var dir string + dir, s.clean = s.TemporalDir() r, err := PlainClone(filepath.Join(dir, "worktree"), false, &CloneOptions{ URL: path, @@ -35,13 +34,10 @@ func (s *SubmoduleSuite) SetUpTest(c *C) { s.Repository = r s.Worktree, err = r.Worktree() c.Assert(err, IsNil) - - s.path = dir } -func (s *SubmoduleSuite) TearDownTest(c *C) { - err := os.RemoveAll(s.path) - c.Assert(err, IsNil) +func (s *SubmoduleSuite) TearDownTest(_ *C) { + s.clean() } func (s *SubmoduleSuite) TestInit(c *C) { @@ -198,7 +194,7 @@ func (s *SubmoduleSuite) TestSubmodulesInit(c *C) { func (s *SubmoduleSuite) TestGitSubmodulesSymlink(c *C) { f, err := s.Worktree.Filesystem.Create("badfile") c.Assert(err, IsNil) - defer f.Close() + defer func() { _ = f.Close() }() err = s.Worktree.Filesystem.Remove(gitmodulesFile) c.Assert(err, IsNil) From 9dfaf6acd8e3aa1c85bcce7d45f8e8c6b908319c Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 11 May 2023 21:34:07 +0100 Subject: [PATCH 110/205] *: Remove redudant err nil checks Signed-off-by: Paulo Gomes --- plumbing/format/idxfile/writer.go | 5 +---- plumbing/format/packfile/encoder.go | 6 +----- plumbing/object/rename.go | 5 +---- remote.go | 6 +----- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/plumbing/format/idxfile/writer.go b/plumbing/format/idxfile/writer.go index daa160502..e5560130f 100644 --- a/plumbing/format/idxfile/writer.go +++ b/plumbing/format/idxfile/writer.go @@ -84,11 +84,8 @@ func (w *Writer) OnFooter(h plumbing.Hash) error { w.checksum = h w.finished = true _, err := w.createIndex() - if err != nil { - return err - } - return nil + return err } // creatIndex returns a filled MemoryIndex with the information filled by diff --git a/plumbing/format/packfile/encoder.go b/plumbing/format/packfile/encoder.go index c9d19b332..804f5a876 100644 --- a/plumbing/format/packfile/encoder.go +++ b/plumbing/format/packfile/encoder.go @@ -131,11 +131,7 @@ func (e *Encoder) entry(o *ObjectToPack) (err error) { defer ioutil.CheckClose(or, &err) _, err = io.Copy(e.zw, or) - if err != nil { - return err - } - - return nil + return err } func (e *Encoder) writeBaseIfDelta(o *ObjectToPack) error { diff --git a/plumbing/object/rename.go b/plumbing/object/rename.go index 0394613ff..ad2b902c2 100644 --- a/plumbing/object/rename.go +++ b/plumbing/object/rename.go @@ -741,10 +741,7 @@ func (i *similarityIndex) add(key int, cnt uint64) error { // It's the same key, so increment the counter. var err error i.hashes[j], err = newKeyCountPair(key, v.count()+cnt) - if err != nil { - return err - } - return nil + return err } else if j+1 >= len(i.hashes) { j = 0 } else { diff --git a/remote.go b/remote.go index df2649112..dd12f8ab6 100644 --- a/remote.go +++ b/remote.go @@ -1250,11 +1250,7 @@ func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) (updated bool, e // operation is complete, an error is returned. The context only affects to the // transport operations. func (r *Remote) ListContext(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) { - refs, err := r.list(ctx, o) - if err != nil { - return refs, err - } - return refs, nil + return r.list(ctx, o) } func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) { From 096b3cc16b547f3c0d6e4f92046945bcfac0fa14 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 11 May 2023 21:59:37 +0100 Subject: [PATCH 111/205] *: Remove use of deprecated io/util Signed-off-by: Paulo Gomes --- _examples/commit/main.go | 3 +-- _examples/common_test.go | 3 +-- _examples/sha256/main.go | 3 +-- _examples/tag-create-push/main.go | 3 +-- config/config.go | 3 +-- example_test.go | 7 +++---- plumbing/format/gitattributes/attributes.go | 3 +-- plumbing/format/gitignore/dir.go | 4 ++-- plumbing/format/idxfile/decoder_test.go | 3 +-- plumbing/format/idxfile/encoder_test.go | 4 ++-- plumbing/format/idxfile/writer_test.go | 6 +++--- plumbing/format/index/decoder.go | 4 ++-- plumbing/format/objfile/reader_test.go | 3 +-- plumbing/format/packfile/delta_test.go | 10 +++++----- plumbing/format/packfile/encoder_test.go | 5 ++--- plumbing/format/packfile/parser.go | 3 +-- plumbing/format/packfile/scanner.go | 5 ++--- plumbing/memory_test.go | 5 ++--- plumbing/object/blob_test.go | 7 +++---- plumbing/object/commit_test.go | 5 ++--- plumbing/object/object_test.go | 3 +-- plumbing/object/tag_test.go | 3 +-- .../protocol/packp/sideband/demux_test.go | 3 +-- plumbing/protocol/packp/updreq_decode.go | 3 +-- plumbing/protocol/packp/updreq_decode_test.go | 15 +++++++------- plumbing/protocol/packp/updreq_encode_test.go | 5 ++--- plumbing/protocol/packp/uppackresp_test.go | 20 +++++++++---------- plumbing/transport/file/common_test.go | 3 +-- plumbing/transport/git/common_test.go | 3 +-- plumbing/transport/http/common_test.go | 3 +-- plumbing/transport/http/upload_pack_test.go | 4 ++-- plumbing/transport/internal/common/common.go | 3 +-- plumbing/transport/ssh/auth_method.go | 3 +-- .../transport/ssh/internal/test/proxy_test.go | 3 +-- plumbing/transport/ssh/proxy_test.go | 3 +-- plumbing/transport/ssh/upload_pack_test.go | 3 +-- plumbing/transport/test/receive_pack.go | 6 ++---- plumbing/transport/test/upload_pack.go | 6 ++---- remote_test.go | 3 +-- repository.go | 6 +++--- storage/filesystem/dotgit/dotgit.go | 3 +-- storage/filesystem/dotgit/dotgit_test.go | 7 +++---- storage/filesystem/object_test.go | 3 +-- storage/test/storage_suite.go | 5 ++--- utils/ioutil/common_test.go | 8 ++++---- worktree.go | 5 ++--- worktree_test.go | 11 +++++----- 47 files changed, 96 insertions(+), 136 deletions(-) diff --git a/_examples/commit/main.go b/_examples/commit/main.go index 4529c845a..3f3c88048 100644 --- a/_examples/commit/main.go +++ b/_examples/commit/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io/ioutil" "os" "path/filepath" "time" @@ -29,7 +28,7 @@ func main() { // worktree of the project using the go standard library. Info("echo \"hello world!\" > example-git-file") filename := filepath.Join(directory, "example-git-file") - err = ioutil.WriteFile(filename, []byte("hello world!"), 0644) + err = os.WriteFile(filename, []byte("hello world!"), 0644) CheckIfError(err) // Adds the new file to the staging area. diff --git a/_examples/common_test.go b/_examples/common_test.go index 9945c875a..6630f15ee 100644 --- a/_examples/common_test.go +++ b/_examples/common_test.go @@ -3,7 +3,6 @@ package examples import ( "flag" "go/build" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -65,7 +64,7 @@ func TestExamples(t *testing.T) { } func tempFolder() string { - path, err := ioutil.TempDir("", "") + path, err := os.MkdirTemp("", "") CheckIfError(err) tempFolders = append(tempFolders, path) diff --git a/_examples/sha256/main.go b/_examples/sha256/main.go index 2a257e863..e1772d274 100644 --- a/_examples/sha256/main.go +++ b/_examples/sha256/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io/ioutil" "os" "path/filepath" "time" @@ -34,7 +33,7 @@ func main() { // worktree of the project using the go standard library. Info("echo \"hello world!\" > example-git-file") filename := filepath.Join(directory, "example-git-file") - err = ioutil.WriteFile(filename, []byte("hello world!"), 0644) + err = os.WriteFile(filename, []byte("hello world!"), 0644) CheckIfError(err) // Adds the new file to the staging area. diff --git a/_examples/tag-create-push/main.go b/_examples/tag-create-push/main.go index c443641e2..b820c76b7 100644 --- a/_examples/tag-create-push/main.go +++ b/_examples/tag-create-push/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io/ioutil" "log" "os" @@ -67,7 +66,7 @@ func cloneRepo(url, dir, publicKeyPath string) (*git.Repository, error) { func publicKey(filePath string) (*ssh.PublicKeys, error) { var publicKey *ssh.PublicKeys - sshKey, _ := ioutil.ReadFile(filePath) + sshKey, _ := os.ReadFile(filePath) publicKey, err := ssh.NewPublicKeys("git", []byte(sshKey), "") if err != nil { return nil, err diff --git a/config/config.go b/config/config.go index 60dfca4f0..82af12d28 100644 --- a/config/config.go +++ b/config/config.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "path/filepath" "sort" @@ -144,7 +143,7 @@ func NewConfig() *Config { // ReadConfig reads a config file from a io.Reader. func ReadConfig(r io.Reader) (*Config, error) { - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/example_test.go b/example_test.go index bba2961c0..27ea4a2d5 100644 --- a/example_test.go +++ b/example_test.go @@ -3,7 +3,6 @@ package git_test import ( "fmt" "io" - "io/ioutil" "log" "os" "path/filepath" @@ -44,7 +43,7 @@ func ExampleClone() { func ExamplePlainClone() { // Tempdir to clone the repository - dir, err := ioutil.TempDir("", "clone-example") + dir, err := os.MkdirTemp("", "clone-example") if err != nil { log.Fatal(err) } @@ -72,7 +71,7 @@ func ExamplePlainClone() { func ExamplePlainClone_usernamePassword() { // Tempdir to clone the repository - dir, err := ioutil.TempDir("", "clone-example") + dir, err := os.MkdirTemp("", "clone-example") if err != nil { log.Fatal(err) } @@ -95,7 +94,7 @@ func ExamplePlainClone_usernamePassword() { func ExamplePlainClone_accessToken() { // Tempdir to clone the repository - dir, err := ioutil.TempDir("", "clone-example") + dir, err := os.MkdirTemp("", "clone-example") if err != nil { log.Fatal(err) } diff --git a/plumbing/format/gitattributes/attributes.go b/plumbing/format/gitattributes/attributes.go index 329e66731..d36ec1b53 100644 --- a/plumbing/format/gitattributes/attributes.go +++ b/plumbing/format/gitattributes/attributes.go @@ -3,7 +3,6 @@ package gitattributes import ( "errors" "io" - "io/ioutil" "strings" ) @@ -89,7 +88,7 @@ func (a attribute) String() string { // ReadAttributes reads patterns and attributes from the gitattributes format. func ReadAttributes(r io.Reader, domain []string, allowMacro bool) (attributes []MatchAttribute, err error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go index 15bc9c779..bb786557c 100644 --- a/plumbing/format/gitignore/dir.go +++ b/plumbing/format/gitignore/dir.go @@ -3,7 +3,7 @@ package gitignore import ( "bufio" "bytes" - "io/ioutil" + "io" "os" "strings" @@ -86,7 +86,7 @@ func loadPatterns(fs billy.Filesystem, path string) (ps []Pattern, err error) { defer gioutil.CheckClose(f, &err) - b, err := ioutil.ReadAll(f) + b, err := io.ReadAll(f) if err != nil { return } diff --git a/plumbing/format/idxfile/decoder_test.go b/plumbing/format/idxfile/decoder_test.go index 94059ccb3..2c4a801a7 100644 --- a/plumbing/format/idxfile/decoder_test.go +++ b/plumbing/format/idxfile/decoder_test.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "fmt" "io" - "io/ioutil" "testing" "github.com/go-git/go-git/v5/plumbing" @@ -119,7 +118,7 @@ ch2xUA== func BenchmarkDecode(b *testing.B) { f := fixtures.Basic().One() - fixture, err := ioutil.ReadAll(f.Idx()) + fixture, err := io.ReadAll(f.Idx()) if err != nil { b.Errorf("unexpected error reading idx file: %s", err) } diff --git a/plumbing/format/idxfile/encoder_test.go b/plumbing/format/idxfile/encoder_test.go index 32b60f9b2..b8ece8398 100644 --- a/plumbing/format/idxfile/encoder_test.go +++ b/plumbing/format/idxfile/encoder_test.go @@ -2,7 +2,7 @@ package idxfile_test import ( "bytes" - "io/ioutil" + "io" . "github.com/go-git/go-git/v5/plumbing/format/idxfile" @@ -12,7 +12,7 @@ import ( func (s *IdxfileSuite) TestDecodeEncode(c *C) { fixtures.ByTag("packfile").Test(c, func(f *fixtures.Fixture) { - expected, err := ioutil.ReadAll(f.Idx()) + expected, err := io.ReadAll(f.Idx()) c.Assert(err, IsNil) idx := new(MemoryIndex) diff --git a/plumbing/format/idxfile/writer_test.go b/plumbing/format/idxfile/writer_test.go index fba3e4272..eaa8605f7 100644 --- a/plumbing/format/idxfile/writer_test.go +++ b/plumbing/format/idxfile/writer_test.go @@ -3,7 +3,7 @@ package idxfile_test import ( "bytes" "encoding/base64" - "io/ioutil" + "io" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/idxfile" @@ -34,7 +34,7 @@ func (s *WriterSuite) TestWriter(c *C) { c.Assert(err, IsNil) idxFile := f.Idx() - expected, err := ioutil.ReadAll(idxFile) + expected, err := io.ReadAll(idxFile) c.Assert(err, IsNil) idxFile.Close() @@ -65,7 +65,7 @@ func (s *WriterSuite) TestWriterLarge(c *C) { // load fixture index f := bytes.NewBufferString(fixtureLarge4GB) - expected, err := ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, f)) + expected, err := io.ReadAll(base64.NewDecoder(base64.StdEncoding, f)) c.Assert(err, IsNil) buf := new(bytes.Buffer) diff --git a/plumbing/format/index/decoder.go b/plumbing/format/index/decoder.go index 8834e2517..6778cf74e 100644 --- a/plumbing/format/index/decoder.go +++ b/plumbing/format/index/decoder.go @@ -5,7 +5,7 @@ import ( "bytes" "errors" "io" - "io/ioutil" + "strconv" "time" @@ -201,7 +201,7 @@ func (d *Decoder) padEntry(idx *Index, e *Entry, read int) error { entrySize := read + len(e.Name) padLen := 8 - entrySize%8 - _, err := io.CopyN(ioutil.Discard, d.r, int64(padLen)) + _, err := io.CopyN(io.Discard, d.r, int64(padLen)) return err } diff --git a/plumbing/format/objfile/reader_test.go b/plumbing/format/objfile/reader_test.go index d697d5464..5526f7f4e 100644 --- a/plumbing/format/objfile/reader_test.go +++ b/plumbing/format/objfile/reader_test.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "fmt" "io" - "io/ioutil" "github.com/go-git/go-git/v5/plumbing" @@ -36,7 +35,7 @@ func testReader(c *C, source io.Reader, hash plumbing.Hash, t plumbing.ObjectTyp c.Assert(typ, Equals, t) c.Assert(content, HasLen, int(size)) - rc, err := ioutil.ReadAll(r) + rc, err := io.ReadAll(r) c.Assert(err, IsNil) c.Assert(rc, DeepEquals, content, Commentf("%scontent=%s, expected=%s", base64.StdEncoding.EncodeToString(rc), base64.StdEncoding.EncodeToString(content))) diff --git a/plumbing/format/packfile/delta_test.go b/plumbing/format/packfile/delta_test.go index 137e4859b..e8f5ea68f 100644 --- a/plumbing/format/packfile/delta_test.go +++ b/plumbing/format/packfile/delta_test.go @@ -2,7 +2,7 @@ package packfile import ( "bytes" - "io/ioutil" + "io" "math/rand" "github.com/go-git/go-git/v5/plumbing" @@ -109,14 +109,14 @@ func (s *DeltaSuite) TestAddDeltaReader(c *C) { targetBuf := genBytes(t.target) delta := DiffDelta(baseBuf, targetBuf) - deltaRC := ioutil.NopCloser(bytes.NewReader(delta)) + deltaRC := io.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) + result, err := io.ReadAll(resultRC) c.Assert(err, IsNil) err = resultRC.Close() @@ -164,12 +164,12 @@ func (s *DeltaSuite) TestMaxCopySizeDeltaReader(c *C) { targetBuf = append(targetBuf, byte(1)) delta := DiffDelta(baseBuf, targetBuf) - deltaRC := ioutil.NopCloser(bytes.NewReader(delta)) + deltaRC := io.NopCloser(bytes.NewReader(delta)) resultRC, err := ReaderFromDelta(baseObj, deltaRC) c.Assert(err, IsNil) - result, err := ioutil.ReadAll(resultRC) + result, err := io.ReadAll(resultRC) c.Assert(err, IsNil) err = resultRC.Close() diff --git a/plumbing/format/packfile/encoder_test.go b/plumbing/format/packfile/encoder_test.go index 902d8ccaa..6719f376a 100644 --- a/plumbing/format/packfile/encoder_test.go +++ b/plumbing/format/packfile/encoder_test.go @@ -3,7 +3,6 @@ package packfile import ( "bytes" "io" - stdioutil "io/ioutil" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/idxfile" @@ -278,13 +277,13 @@ func objectsEqual(c *C, o1, o2 plumbing.EncodedObject) { r1, err := o1.Reader() c.Assert(err, IsNil) - b1, err := stdioutil.ReadAll(r1) + b1, err := io.ReadAll(r1) c.Assert(err, IsNil) r2, err := o2.Reader() c.Assert(err, IsNil) - b2, err := stdioutil.ReadAll(r2) + b2, err := io.ReadAll(r2) c.Assert(err, IsNil) c.Assert(bytes.Compare(b1, b2), Equals, 0) diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go index 6012b0458..edbc0e796 100644 --- a/plumbing/format/packfile/parser.go +++ b/plumbing/format/packfile/parser.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "io" - stdioutil "io/ioutil" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" @@ -297,7 +296,7 @@ func (p *Parser) resolveDeltas() error { if !obj.IsDelta() && len(obj.Children) > 0 { for _, child := range obj.Children { - if err := p.resolveObject(stdioutil.Discard, child, content); err != nil { + if err := p.resolveObject(io.Discard, child, content); err != nil { return err } p.resolveExternalRef(child) diff --git a/plumbing/format/packfile/scanner.go b/plumbing/format/packfile/scanner.go index 9ebb84a24..730343ee3 100644 --- a/plumbing/format/packfile/scanner.go +++ b/plumbing/format/packfile/scanner.go @@ -7,7 +7,6 @@ import ( "hash" "hash/crc32" "io" - stdioutil "io/ioutil" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/utils/binary" @@ -242,7 +241,7 @@ func (s *Scanner) discardObjectIfNeeded() error { } h := s.pendingObject - n, _, err := s.NextObject(stdioutil.Discard) + n, _, err := s.NextObject(io.Discard) if err != nil { return err } @@ -381,7 +380,7 @@ func (s *Scanner) Checksum() (plumbing.Hash, error) { // Close reads the reader until io.EOF func (s *Scanner) Close() error { buf := sync.GetByteSlice() - _, err := io.CopyBuffer(stdioutil.Discard, s.r, *buf) + _, err := io.CopyBuffer(io.Discard, s.r, *buf) sync.PutByteSlice(buf) return err diff --git a/plumbing/memory_test.go b/plumbing/memory_test.go index 2a141f491..f76b4f40f 100644 --- a/plumbing/memory_test.go +++ b/plumbing/memory_test.go @@ -2,7 +2,6 @@ package plumbing import ( "io" - "io/ioutil" . "gopkg.in/check.v1" ) @@ -52,7 +51,7 @@ func (s *MemoryObjectSuite) TestReader(c *C) { c.Assert(err, IsNil) defer func() { c.Assert(reader.Close(), IsNil) }() - b, err := ioutil.ReadAll(reader) + b, err := io.ReadAll(reader) c.Assert(err, IsNil) c.Assert(b, DeepEquals, []byte("foo")) } @@ -75,7 +74,7 @@ func (s *MemoryObjectSuite) TestSeekableReader(c *C) { _, err = rs.Seek(pageSize, io.SeekStart) c.Assert(err, IsNil) - b, err := ioutil.ReadAll(rs) + b, err := io.ReadAll(rs) c.Assert(err, IsNil) c.Assert(b, DeepEquals, []byte(payload)) diff --git a/plumbing/object/blob_test.go b/plumbing/object/blob_test.go index 44613433a..9481dbe44 100644 --- a/plumbing/object/blob_test.go +++ b/plumbing/object/blob_test.go @@ -3,7 +3,6 @@ package object import ( "bytes" "io" - "io/ioutil" "github.com/go-git/go-git/v5/plumbing" @@ -37,7 +36,7 @@ func (s *BlobsSuite) TestBlobHash(c *C) { c.Assert(err, IsNil) defer func() { c.Assert(reader.Close(), IsNil) }() - data, err := ioutil.ReadAll(reader) + data, err := io.ReadAll(reader) c.Assert(err, IsNil) c.Assert(string(data), Equals, "FOO") } @@ -96,14 +95,14 @@ func (s *BlobsSuite) TestBlobIter(c *C) { r1, err := b.Reader() c.Assert(err, IsNil) - b1, err := ioutil.ReadAll(r1) + b1, err := io.ReadAll(r1) c.Assert(err, IsNil) c.Assert(r1.Close(), IsNil) r2, err := blobs[i].Reader() c.Assert(err, IsNil) - b2, err := ioutil.ReadAll(r2) + b2, err := io.ReadAll(r2) c.Assert(err, IsNil) c.Assert(r2.Close(), IsNil) diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index 468a751fa..a939bc6a4 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "io" - "io/ioutil" "strings" "time" @@ -449,7 +448,7 @@ YIefGtzXfldDxg4= ` e, err := commit.Verify(armoredKeyRing) - c.Assert(err, IsNil) + c.Assert(err, IsNil) _, ok := e.Identities["go-git test key"] c.Assert(ok, Equals, true) @@ -492,7 +491,7 @@ func (s *SuiteCommit) TestEncodeWithoutSignature(c *C) { c.Assert(err, IsNil) er, err := encoded.Reader() c.Assert(err, IsNil) - payload, err := ioutil.ReadAll(er) + payload, err := io.ReadAll(er) c.Assert(err, IsNil) c.Assert(string(payload), Equals, ""+ diff --git a/plumbing/object/object_test.go b/plumbing/object/object_test.go index 6c95eef9c..c4fdb4c7a 100644 --- a/plumbing/object/object_test.go +++ b/plumbing/object/object_test.go @@ -2,7 +2,6 @@ package object import ( "io" - "io/ioutil" "testing" "time" @@ -103,7 +102,7 @@ func (s *ObjectsSuite) TestParseTree(c *C) { reader, err := f.Reader() c.Assert(err, IsNil) defer func() { c.Assert(reader.Close(), IsNil) }() - content, _ := ioutil.ReadAll(reader) + content, _ := io.ReadAll(reader) c.Assert(content, HasLen, 2780) } } diff --git a/plumbing/object/tag_test.go b/plumbing/object/tag_test.go index 15b943e07..d374c6c54 100644 --- a/plumbing/object/tag_test.go +++ b/plumbing/object/tag_test.go @@ -3,7 +3,6 @@ package object import ( "fmt" "io" - "io/ioutil" "strings" "time" @@ -466,7 +465,7 @@ func (s *TagSuite) TestEncodeWithoutSignature(c *C) { c.Assert(err, IsNil) er, err := encoded.Reader() c.Assert(err, IsNil) - payload, err := ioutil.ReadAll(er) + payload, err := io.ReadAll(er) c.Assert(err, IsNil) c.Assert(string(payload), Equals, ""+ diff --git a/plumbing/protocol/packp/sideband/demux_test.go b/plumbing/protocol/packp/sideband/demux_test.go index 6cda70381..8f233538c 100644 --- a/plumbing/protocol/packp/sideband/demux_test.go +++ b/plumbing/protocol/packp/sideband/demux_test.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "io" - "io/ioutil" "testing" "github.com/go-git/go-git/v5/plumbing/format/pktline" @@ -101,7 +100,7 @@ func (s *SidebandSuite) TestDecodeWithProgress(c *C) { c.Assert(n, Equals, 26) c.Assert(content, DeepEquals, expected) - progress, err := ioutil.ReadAll(output) + progress, err := io.ReadAll(output) c.Assert(err, IsNil) c.Assert(progress, DeepEquals, []byte{'F', 'O', 'O', '\n'}) } diff --git a/plumbing/protocol/packp/updreq_decode.go b/plumbing/protocol/packp/updreq_decode.go index 2c9843a56..076de545f 100644 --- a/plumbing/protocol/packp/updreq_decode.go +++ b/plumbing/protocol/packp/updreq_decode.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/pktline" @@ -81,7 +80,7 @@ func (req *ReferenceUpdateRequest) Decode(r io.Reader) error { var ok bool rc, ok = r.(io.ReadCloser) if !ok { - rc = ioutil.NopCloser(r) + rc = io.NopCloser(r) } d := &updReqDecoder{r: rc, s: pktline.NewScanner(r)} diff --git a/plumbing/protocol/packp/updreq_decode_test.go b/plumbing/protocol/packp/updreq_decode_test.go index 26301123b..bdcbdf503 100644 --- a/plumbing/protocol/packp/updreq_decode_test.go +++ b/plumbing/protocol/packp/updreq_decode_test.go @@ -3,7 +3,6 @@ package packp import ( "bytes" "io" - "io/ioutil" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/pktline" @@ -157,7 +156,7 @@ func (s *UpdReqDecodeSuite) TestOneUpdateCommand(c *C) { expected.Commands = []*Command{ {Name: name, Old: hash1, New: hash2}, } - expected.Packfile = ioutil.NopCloser(bytes.NewReader([]byte{})) + expected.Packfile = io.NopCloser(bytes.NewReader([]byte{})) payloads := []string{ "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", @@ -177,7 +176,7 @@ func (s *UpdReqDecodeSuite) TestMultipleCommands(c *C) { {Name: plumbing.ReferenceName("myref2"), Old: plumbing.ZeroHash, New: hash2}, {Name: plumbing.ReferenceName("myref3"), Old: hash1, New: plumbing.ZeroHash}, } - expected.Packfile = ioutil.NopCloser(bytes.NewReader([]byte{})) + expected.Packfile = io.NopCloser(bytes.NewReader([]byte{})) payloads := []string{ "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00", @@ -200,7 +199,7 @@ func (s *UpdReqDecodeSuite) TestMultipleCommandsAndCapabilities(c *C) { {Name: plumbing.ReferenceName("myref3"), Old: hash1, New: plumbing.ZeroHash}, } expected.Capabilities.Add("shallow") - expected.Packfile = ioutil.NopCloser(bytes.NewReader([]byte{})) + expected.Packfile = io.NopCloser(bytes.NewReader([]byte{})) payloads := []string{ "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00shallow", @@ -224,7 +223,7 @@ func (s *UpdReqDecodeSuite) TestMultipleCommandsAndCapabilitiesShallow(c *C) { } expected.Capabilities.Add("shallow") expected.Shallow = &hash1 - expected.Packfile = ioutil.NopCloser(bytes.NewReader([]byte{})) + expected.Packfile = io.NopCloser(bytes.NewReader([]byte{})) payloads := []string{ "shallow 1ecf0ef2c2dffb796033e5a02219af86ec6584e5", @@ -247,7 +246,7 @@ func (s *UpdReqDecodeSuite) TestWithPackfile(c *C) { {Name: name, Old: hash1, New: hash2}, } packfileContent := []byte("PACKabc") - expected.Packfile = ioutil.NopCloser(bytes.NewReader(packfileContent)) + expected.Packfile = io.NopCloser(bytes.NewReader(packfileContent)) payloads := []string{ "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", @@ -298,10 +297,10 @@ func (s *UpdReqDecodeSuite) testDecodeOkExpected(c *C, expected *ReferenceUpdate } func (s *UpdReqDecodeSuite) compareReaders(c *C, a io.ReadCloser, b io.ReadCloser) { - pba, err := ioutil.ReadAll(a) + pba, err := io.ReadAll(a) c.Assert(err, IsNil) c.Assert(a.Close(), IsNil) - pbb, err := ioutil.ReadAll(b) + pbb, err := io.ReadAll(b) c.Assert(err, IsNil) c.Assert(b.Close(), IsNil) c.Assert(pba, DeepEquals, pbb) diff --git a/plumbing/protocol/packp/updreq_encode_test.go b/plumbing/protocol/packp/updreq_encode_test.go index 4370b794f..97868bd64 100644 --- a/plumbing/protocol/packp/updreq_encode_test.go +++ b/plumbing/protocol/packp/updreq_encode_test.go @@ -2,13 +2,12 @@ package packp import ( "bytes" + "io" "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" - "io/ioutil" - . "gopkg.in/check.v1" ) @@ -128,7 +127,7 @@ func (s *UpdReqEncodeSuite) TestWithPackfile(c *C) { packfileContent := []byte("PACKabc") packfileReader := bytes.NewReader(packfileContent) - packfileReadCloser := ioutil.NopCloser(packfileReader) + packfileReadCloser := io.NopCloser(packfileReader) r := NewReferenceUpdateRequest() r.Commands = []*Command{ diff --git a/plumbing/protocol/packp/uppackresp_test.go b/plumbing/protocol/packp/uppackresp_test.go index 3f87804a5..8fbf92467 100644 --- a/plumbing/protocol/packp/uppackresp_test.go +++ b/plumbing/protocol/packp/uppackresp_test.go @@ -2,7 +2,7 @@ package packp import ( "bytes" - "io/ioutil" + "io" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" @@ -21,10 +21,10 @@ func (s *UploadPackResponseSuite) TestDecodeNAK(c *C) { res := NewUploadPackResponse(req) defer res.Close() - err := res.Decode(ioutil.NopCloser(bytes.NewBufferString(raw))) + err := res.Decode(io.NopCloser(bytes.NewBufferString(raw))) c.Assert(err, IsNil) - pack, err := ioutil.ReadAll(res) + pack, err := io.ReadAll(res) c.Assert(err, IsNil) c.Assert(pack, DeepEquals, []byte("PACK")) } @@ -38,10 +38,10 @@ func (s *UploadPackResponseSuite) TestDecodeDepth(c *C) { res := NewUploadPackResponse(req) defer res.Close() - err := res.Decode(ioutil.NopCloser(bytes.NewBufferString(raw))) + err := res.Decode(io.NopCloser(bytes.NewBufferString(raw))) c.Assert(err, IsNil) - pack, err := ioutil.ReadAll(res) + pack, err := io.ReadAll(res) c.Assert(err, IsNil) c.Assert(pack, DeepEquals, []byte("PACK")) } @@ -55,7 +55,7 @@ func (s *UploadPackResponseSuite) TestDecodeMalformed(c *C) { res := NewUploadPackResponse(req) defer res.Close() - err := res.Decode(ioutil.NopCloser(bytes.NewBufferString(raw))) + err := res.Decode(io.NopCloser(bytes.NewBufferString(raw))) c.Assert(err, NotNil) } @@ -70,7 +70,7 @@ func (s *UploadPackResponseSuite) TestDecodeMultiACK(c *C) { res := NewUploadPackResponse(req) defer res.Close() - err := res.Decode(ioutil.NopCloser(bytes.NewBuffer(nil))) + err := res.Decode(io.NopCloser(bytes.NewBuffer(nil))) c.Assert(err, IsNil) } @@ -87,7 +87,7 @@ func (s *UploadPackResponseSuite) TestReadNoDecode(c *C) { } func (s *UploadPackResponseSuite) TestEncodeNAK(c *C) { - pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + pf := io.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) req := NewUploadPackRequest() res := NewUploadPackResponseWithPackfile(req, pf) defer func() { c.Assert(res.Close(), IsNil) }() @@ -100,7 +100,7 @@ func (s *UploadPackResponseSuite) TestEncodeNAK(c *C) { } func (s *UploadPackResponseSuite) TestEncodeDepth(c *C) { - pf := ioutil.NopCloser(bytes.NewBuffer([]byte("PACK"))) + pf := io.NopCloser(bytes.NewBuffer([]byte("PACK"))) req := NewUploadPackRequest() req.Depth = DepthCommits(1) @@ -115,7 +115,7 @@ func (s *UploadPackResponseSuite) TestEncodeDepth(c *C) { } func (s *UploadPackResponseSuite) TestEncodeMultiACK(c *C) { - pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + pf := io.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) req := NewUploadPackRequest() res := NewUploadPackResponseWithPackfile(req, pf) diff --git a/plumbing/transport/file/common_test.go b/plumbing/transport/file/common_test.go index 4d6612b70..7e033a80b 100644 --- a/plumbing/transport/file/common_test.go +++ b/plumbing/transport/file/common_test.go @@ -1,7 +1,6 @@ package file import ( - "io/ioutil" "os" "os/exec" "path/filepath" @@ -25,7 +24,7 @@ func (s *CommonSuite) SetUpSuite(c *C) { } var err error - s.tmpDir, err = ioutil.TempDir("", "") + s.tmpDir, err = os.MkdirTemp("", "") c.Assert(err, IsNil) s.ReceivePackBin = filepath.Join(s.tmpDir, "git-receive-pack") s.UploadPackBin = filepath.Join(s.tmpDir, "git-upload-pack") diff --git a/plumbing/transport/git/common_test.go b/plumbing/transport/git/common_test.go index 3391aafd6..73899198f 100644 --- a/plumbing/transport/git/common_test.go +++ b/plumbing/transport/git/common_test.go @@ -2,7 +2,6 @@ package git import ( "fmt" - "io/ioutil" "net" "os" "os/exec" @@ -37,7 +36,7 @@ func (s *BaseSuite) SetUpTest(c *C) { s.port, err = freePort() c.Assert(err, IsNil) - s.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-protocol-%d", s.port)) + s.base, err = os.MkdirTemp(os.TempDir(), fmt.Sprintf("go-git-protocol-%d", s.port)) c.Assert(err, IsNil) } diff --git a/plumbing/transport/http/common_test.go b/plumbing/transport/http/common_test.go index 41188e677..151722876 100644 --- a/plumbing/transport/http/common_test.go +++ b/plumbing/transport/http/common_test.go @@ -3,7 +3,6 @@ package http import ( "crypto/tls" "fmt" - "io/ioutil" "log" "net" "net/http" @@ -222,7 +221,7 @@ func (s *BaseSuite) SetUpTest(c *C) { l, err := net.Listen("tcp", "localhost:0") c.Assert(err, IsNil) - base, err := ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-http-%d", s.port)) + base, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("go-git-http-%d", s.port)) c.Assert(err, IsNil) s.port = l.Addr().(*net.TCPAddr).Port diff --git a/plumbing/transport/http/upload_pack_test.go b/plumbing/transport/http/upload_pack_test.go index c088ecccd..abb7adf37 100644 --- a/plumbing/transport/http/upload_pack_test.go +++ b/plumbing/transport/http/upload_pack_test.go @@ -3,7 +3,7 @@ package http import ( "context" "fmt" - "io/ioutil" + "io" "net/url" "os" "path/filepath" @@ -49,7 +49,7 @@ func (s *UploadPackSuite) TestuploadPackRequestToReader(c *C) { sr, err := uploadPackRequestToReader(r) c.Assert(err, IsNil) - b, _ := ioutil.ReadAll(sr) + b, _ := io.ReadAll(sr) c.Assert(string(b), Equals, "0032want 2b41ef280fdb67a9b250678686a0c3e03b0a9989\n"+ "0032want d82f291cde9987322c8a0c81a325e1ba6159684c\n0000"+ diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index b2c2fee38..99e0850f9 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "io" - stdioutil "io/ioutil" "strings" "time" @@ -156,7 +155,7 @@ func (c *client) listenFirstError(r io.Reader) chan string { close(errLine) } - _, _ = io.Copy(stdioutil.Discard, r) + _, _ = io.Copy(io.Discard, r) }() return errLine diff --git a/plumbing/transport/ssh/auth_method.go b/plumbing/transport/ssh/auth_method.go index e89ce4ba3..ac4e3583c 100644 --- a/plumbing/transport/ssh/auth_method.go +++ b/plumbing/transport/ssh/auth_method.go @@ -3,7 +3,6 @@ package ssh import ( "errors" "fmt" - "io/ioutil" "os" "os/user" "path/filepath" @@ -134,7 +133,7 @@ func NewPublicKeys(user string, pemBytes []byte, password string) (*PublicKeys, // encoded private key. An encryption password should be given if the pemBytes // contains a password encrypted PEM block otherwise password should be empty. func NewPublicKeysFromFile(user, pemFile, password string) (*PublicKeys, error) { - bytes, err := ioutil.ReadFile(pemFile) + bytes, err := os.ReadFile(pemFile) if err != nil { return nil, err } diff --git a/plumbing/transport/ssh/internal/test/proxy_test.go b/plumbing/transport/ssh/internal/test/proxy_test.go index 8baac2b57..8e775f89a 100644 --- a/plumbing/transport/ssh/internal/test/proxy_test.go +++ b/plumbing/transport/ssh/internal/test/proxy_test.go @@ -3,7 +3,6 @@ package test import ( "context" "fmt" - "io/ioutil" "log" "net" "os" @@ -59,7 +58,7 @@ func (s *ProxyEnvSuite) TestCommand(c *C) { }() s.port = sshListener.Addr().(*net.TCPAddr).Port - s.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.port)) + s.base, err = os.MkdirTemp(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.port)) c.Assert(err, IsNil) ggssh.DefaultAuthBuilder = func(user string) (ggssh.AuthMethod, error) { diff --git a/plumbing/transport/ssh/proxy_test.go b/plumbing/transport/ssh/proxy_test.go index 2fab8512b..2ba98e823 100644 --- a/plumbing/transport/ssh/proxy_test.go +++ b/plumbing/transport/ssh/proxy_test.go @@ -3,7 +3,6 @@ package ssh import ( "context" "fmt" - "io/ioutil" "log" "net" "os" @@ -54,7 +53,7 @@ func (s *ProxySuite) TestCommand(c *C) { }() s.u.port = sshListener.Addr().(*net.TCPAddr).Port - s.u.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.u.port)) + s.u.base, err = os.MkdirTemp(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.u.port)) c.Assert(err, IsNil) DefaultAuthBuilder = func(user string) (AuthMethod, error) { diff --git a/plumbing/transport/ssh/upload_pack_test.go b/plumbing/transport/ssh/upload_pack_test.go index fafff485d..67af566e6 100644 --- a/plumbing/transport/ssh/upload_pack_test.go +++ b/plumbing/transport/ssh/upload_pack_test.go @@ -3,7 +3,6 @@ package ssh import ( "fmt" "io" - "io/ioutil" "log" "net" "os" @@ -43,7 +42,7 @@ func (s *UploadPackSuite) SetUpSuite(c *C) { c.Assert(err, IsNil) s.port = l.Addr().(*net.TCPAddr).Port - s.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.port)) + s.base, err = os.MkdirTemp(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.port)) c.Assert(err, IsNil) DefaultAuthBuilder = func(user string) (AuthMethod, error) { diff --git a/plumbing/transport/test/receive_pack.go b/plumbing/transport/test/receive_pack.go index 018d38e30..9414fbaff 100644 --- a/plumbing/transport/test/receive_pack.go +++ b/plumbing/transport/test/receive_pack.go @@ -1,13 +1,11 @@ // Package test implements common test suite for different transport // implementations. -// package test import ( "bytes" "context" "io" - "io/ioutil" "os" "path/filepath" @@ -235,7 +233,7 @@ func (s *ReceivePackSuite) receivePackNoCheck(c *C, ep *transport.Endpoint, if rootPath != "" && err == nil && stat.IsDir() { objectPath := filepath.Join(rootPath, "objects/pack") - files, err := ioutil.ReadDir(objectPath) + files, err := os.ReadDir(objectPath) c.Assert(err, IsNil) for _, file := range files { @@ -371,5 +369,5 @@ func (s *ReceivePackSuite) emptyPackfile() io.ReadCloser { panic(err) } - return ioutil.NopCloser(&buf) + return io.NopCloser(&buf) } diff --git a/plumbing/transport/test/upload_pack.go b/plumbing/transport/test/upload_pack.go index 3ee029d40..f7842ebb7 100644 --- a/plumbing/transport/test/upload_pack.go +++ b/plumbing/transport/test/upload_pack.go @@ -1,13 +1,11 @@ // Package test implements common test suite for different transport // implementations. -// package test import ( "bytes" "context" "io" - "io/ioutil" "time" "github.com/go-git/go-git/v5/plumbing" @@ -154,7 +152,7 @@ func (s *UploadPackSuite) TestUploadPackWithContextOnRead(c *C) { cancel() - _, err = io.Copy(ioutil.Discard, reader) + _, err = io.Copy(io.Discard, reader) c.Assert(err, NotNil) err = reader.Close() @@ -255,7 +253,7 @@ func (s *UploadPackSuite) TestFetchError(c *C) { } func (s *UploadPackSuite) checkObjectNumber(c *C, r io.Reader, n int) { - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) c.Assert(err, IsNil) buf := bytes.NewBuffer(b) storage := memory.NewStorage() diff --git a/remote_test.go b/remote_test.go index 164e4e509..1bcdb0f75 100644 --- a/remote_test.go +++ b/remote_test.go @@ -5,7 +5,6 @@ import ( "context" "errors" "io" - "io/ioutil" "os" "path/filepath" "runtime" @@ -1387,7 +1386,7 @@ func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) { } func (s *RemoteSuite) TestCanPushShasToReference(c *C) { - d, err := ioutil.TempDir("", "TestCanPushShasToReference") + d, err := os.MkdirTemp("", "TestCanPushShasToReference") c.Assert(err, IsNil) if err != nil { return diff --git a/repository.go b/repository.go index 287b597bb..89c48db82 100644 --- a/repository.go +++ b/repository.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "errors" "fmt" - stdioutil "io/ioutil" + "io" "os" "path" "path/filepath" @@ -367,7 +367,7 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files } defer ioutil.CheckClose(f, &err) - b, err := stdioutil.ReadAll(f) + b, err := io.ReadAll(f) if err != nil { return nil, err } @@ -396,7 +396,7 @@ func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err return nil, err } - b, err := stdioutil.ReadAll(f) + b, err := io.ReadAll(f) if err != nil { return nil, err } diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go index 5bde37f6b..19d702632 100644 --- a/storage/filesystem/dotgit/dotgit.go +++ b/storage/filesystem/dotgit/dotgit.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - stdioutil "io/ioutil" "os" "path/filepath" "sort" @@ -647,7 +646,7 @@ func (d *DotGit) ObjectDelete(h plumbing.Hash) error { } func (d *DotGit) readReferenceFrom(rd io.Reader, name string) (ref *plumbing.Reference, err error) { - b, err := stdioutil.ReadAll(rd) + b, err := io.ReadAll(rd) if err != nil { return nil, err } diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go index 63c9eb015..6a339d1e5 100644 --- a/storage/filesystem/dotgit/dotgit_test.go +++ b/storage/filesystem/dotgit/dotgit_test.go @@ -4,7 +4,6 @@ import ( "bufio" "encoding/hex" "io" - "io/ioutil" "os" "path/filepath" "runtime" @@ -374,7 +373,7 @@ func (s *SuiteDotGit) TestConfigWriteAndConfig(c *C) { f, err = dir.Config() c.Assert(err, IsNil) - cnt, err := ioutil.ReadAll(f) + cnt, err := io.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(cnt), Equals, "foo") @@ -404,7 +403,7 @@ func (s *SuiteDotGit) TestIndexWriteAndIndex(c *C) { f, err = dir.Index() c.Assert(err, IsNil) - cnt, err := ioutil.ReadAll(f) + cnt, err := io.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(cnt), Equals, "foo") @@ -434,7 +433,7 @@ func (s *SuiteDotGit) TestShallowWriteAndShallow(c *C) { f, err = dir.Shallow() c.Assert(err, IsNil) - cnt, err := ioutil.ReadAll(f) + cnt, err := io.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(cnt), Equals, "foo") diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go index b5f137f29..251077a61 100644 --- a/storage/filesystem/object_test.go +++ b/storage/filesystem/object_test.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "os" "path/filepath" "testing" @@ -510,7 +509,7 @@ func BenchmarkPackfileIterReadContent(b *testing.B) { b.Fatal(err) } - if _, err := ioutil.ReadAll(r); err != nil { + if _, err := io.ReadAll(r); err != nil { b.Fatal(err) } diff --git a/storage/test/storage_suite.go b/storage/test/storage_suite.go index 2c00e75fe..ee67fc791 100644 --- a/storage/test/storage_suite.go +++ b/storage/test/storage_suite.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" @@ -502,12 +501,12 @@ func objectEquals(a plumbing.EncodedObject, b plumbing.EncodedObject) error { return fmt.Errorf("can't get reader on b: %q", err) } - ca, err := ioutil.ReadAll(ra) + ca, err := io.ReadAll(ra) if err != nil { return fmt.Errorf("error reading a: %q", err) } - cb, err := ioutil.ReadAll(rb) + cb, err := io.ReadAll(rb) if err != nil { return fmt.Errorf("error reading b: %q", err) } diff --git a/utils/ioutil/common_test.go b/utils/ioutil/common_test.go index 27bfa62ed..e3c9d69fa 100644 --- a/utils/ioutil/common_test.go +++ b/utils/ioutil/common_test.go @@ -3,7 +3,7 @@ package ioutil import ( "bytes" "context" - "io/ioutil" + "io" "strings" "testing" @@ -38,7 +38,7 @@ func (s *CommonSuite) TestNonEmptyReader_NonEmpty(c *C) { c.Assert(err, IsNil) c.Assert(r, NotNil) - read, err := ioutil.ReadAll(r) + read, err := io.ReadAll(r) c.Assert(err, IsNil) c.Assert(string(read), Equals, "1") } @@ -48,7 +48,7 @@ func (s *CommonSuite) TestNewReadCloser(c *C) { closer := &closer{} r := NewReadCloser(buf, closer) - read, err := ioutil.ReadAll(r) + read, err := io.ReadAll(r) c.Assert(err, IsNil) c.Assert(string(read), Equals, "1") @@ -160,7 +160,7 @@ func ExampleCheckClose() { // CheckClose is commonly used with named return values f := func() (err error) { // Get a io.ReadCloser - r := ioutil.NopCloser(strings.NewReader("foo")) + r := io.NopCloser(strings.NewReader("foo")) // defer CheckClose call with an io.Closer and pointer to error defer CheckClose(r, &err) diff --git a/worktree.go b/worktree.go index 7d2f3b47f..595dceaae 100644 --- a/worktree.go +++ b/worktree.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - stdioutil "io/ioutil" "os" "path/filepath" "strings" @@ -569,7 +568,7 @@ func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) { defer ioutil.CheckClose(from, &err) - bytes, err := stdioutil.ReadAll(from) + bytes, err := io.ReadAll(from) if err != nil { return } @@ -718,7 +717,7 @@ func (w *Worktree) readGitmodulesFile() (*config.Modules, error) { } defer f.Close() - input, err := stdioutil.ReadAll(f) + input, err := io.ReadAll(f) if err != nil { return nil, err } diff --git a/worktree_test.go b/worktree_test.go index 4f55844a9..ea55ac6f3 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -5,7 +5,6 @@ import ( "context" "errors" "io" - "io/ioutil" "os" "path/filepath" "regexp" @@ -81,7 +80,7 @@ func (s *WorktreeSuite) TestPullFastForward(c *C) { w, err := server.Worktree() c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755) + err = os.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755) c.Assert(err, IsNil) hash, err := w.Commit("foo", &CommitOptions{Author: defaultSignature()}) c.Assert(err, IsNil) @@ -118,14 +117,14 @@ func (s *WorktreeSuite) TestPullNonFastForward(c *C) { w, err := server.Worktree() c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755) + err = os.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755) c.Assert(err, IsNil) _, err = w.Commit("foo", &CommitOptions{Author: defaultSignature()}) c.Assert(err, IsNil) w, err = r.Worktree() c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755) + err = os.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755) c.Assert(err, IsNil) _, err = w.Commit("bar", &CommitOptions{Author: defaultSignature()}) c.Assert(err, IsNil) @@ -287,7 +286,7 @@ func (s *WorktreeSuite) TestPullAlreadyUptodate(c *C) { w, err := r.Worktree() c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755) + err = os.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755) c.Assert(err, IsNil) _, err = w.Commit("bar", &CommitOptions{Author: defaultSignature()}) c.Assert(err, IsNil) @@ -315,7 +314,7 @@ func (s *WorktreeSuite) TestCheckout(c *C) { ch, err := fs.Open("CHANGELOG") c.Assert(err, IsNil) - content, err := ioutil.ReadAll(ch) + content, err := io.ReadAll(ch) c.Assert(err, IsNil) c.Assert(string(content), Equals, "Initial changelog\n") From cb72b58b7576e28627e58338c92e61d3d41a72d7 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 11 May 2023 22:01:31 +0100 Subject: [PATCH 112/205] *: Replace fmt.Sprintf with net.JoinHostPort Signed-off-by: Paulo Gomes --- plumbing/transport/git/common.go | 5 +++-- plumbing/transport/ssh/common.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plumbing/transport/git/common.go b/plumbing/transport/git/common.go index c18d600c2..92fc0becc 100644 --- a/plumbing/transport/git/common.go +++ b/plumbing/transport/git/common.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "strconv" "github.com/go-git/go-git/v5/plumbing/format/pktline" "github.com/go-git/go-git/v5/plumbing/transport" @@ -69,7 +70,7 @@ func (c *command) getHostWithPort() string { port = DefaultPort } - return fmt.Sprintf("%s:%d", host, port) + return net.JoinHostPort(host, strconv.Itoa(port)) } // StderrPipe git protocol doesn't have any dedicated error channel @@ -92,7 +93,7 @@ func (c *command) StdoutPipe() (io.Reader, error) { func endpointToCommand(cmd string, ep *transport.Endpoint) string { host := ep.Host if ep.Port != DefaultPort { - host = fmt.Sprintf("%s:%d", ep.Host, ep.Port) + host = net.JoinHostPort(ep.Host, strconv.Itoa(ep.Port)) } return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path, 0, host, 0) diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go index 6617d9b71..15316038b 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -212,7 +212,7 @@ func (c *command) getHostWithPort() string { port = DefaultPort } - return fmt.Sprintf("%s:%d", host, port) + return net.JoinHostPort(host, strconv.Itoa(port)) } func (c *command) doGetHostWithPortFromSSHConfig() (addr string, found bool) { @@ -240,7 +240,7 @@ func (c *command) doGetHostWithPortFromSSHConfig() (addr string, found bool) { } } - addr = fmt.Sprintf("%s:%d", host, port) + addr = net.JoinHostPort(host, strconv.Itoa(port)) return } From bddd89e697ebdaad2fe341b3e8a3233691f5544a Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 11 May 2023 22:14:26 +0100 Subject: [PATCH 113/205] *: Add missing error checks Some areas of the code base were missing error checks, without them it may be harder to troubleshoot unexpected behaviours. Signed-off-by: Paulo Gomes --- plumbing/format/idxfile/writer.go | 24 +++++++++++++++++------- plumbing/protocol/packp/advrefs.go | 14 +++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/plumbing/format/idxfile/writer.go b/plumbing/format/idxfile/writer.go index e5560130f..c4c21e167 100644 --- a/plumbing/format/idxfile/writer.go +++ b/plumbing/format/idxfile/writer.go @@ -136,15 +136,23 @@ func (w *Writer) createIndex() (*MemoryIndex, error) { offset := o.Offset if offset > math.MaxInt32 { - offset = w.addOffset64(offset) + var err error + offset, err = w.addOffset64(offset) + if err != nil { + return nil, err + } } buf.Truncate(0) - binary.WriteUint32(buf, uint32(offset)) + if err := binary.WriteUint32(buf, uint32(offset)); err != nil { + return nil, err + } idx.Offset32[bucket] = append(idx.Offset32[bucket], buf.Bytes()...) buf.Truncate(0) - binary.WriteUint32(buf, o.CRC32) + if err := binary.WriteUint32(buf, o.CRC32); err != nil { + return nil, err + } idx.CRC32[bucket] = append(idx.CRC32[bucket], buf.Bytes()...) } @@ -158,15 +166,17 @@ func (w *Writer) createIndex() (*MemoryIndex, error) { return idx, nil } -func (w *Writer) addOffset64(pos uint64) uint64 { +func (w *Writer) addOffset64(pos uint64) (uint64, error) { buf := new(bytes.Buffer) - binary.WriteUint64(buf, pos) - w.index.Offset64 = append(w.index.Offset64, buf.Bytes()...) + if err := binary.WriteUint64(buf, pos); err != nil { + return 0, err + } + w.index.Offset64 = append(w.index.Offset64, buf.Bytes()...) index := uint64(w.offset64 | (1 << 31)) w.offset64++ - return index + return index, nil } func (o objects) Len() int { diff --git a/plumbing/protocol/packp/advrefs.go b/plumbing/protocol/packp/advrefs.go index 1bd724cad..f93ad3047 100644 --- a/plumbing/protocol/packp/advrefs.go +++ b/plumbing/protocol/packp/advrefs.go @@ -57,7 +57,7 @@ func (a *AdvRefs) AddReference(r *plumbing.Reference) error { switch r.Type() { case plumbing.SymbolicReference: v := fmt.Sprintf("%s:%s", r.Name().String(), r.Target().String()) - a.Capabilities.Add(capability.SymRef, v) + return a.Capabilities.Add(capability.SymRef, v) case plumbing.HashReference: a.References[r.Name().String()] = r.Hash() default: @@ -96,12 +96,12 @@ func (a *AdvRefs) addRefs(s storer.ReferenceStorer) error { // // Git versions prior to 1.8.4.3 has an special procedure to get // the reference where is pointing to HEAD: -// - Check if a reference called master exists. If exists and it -// has the same hash as HEAD hash, we can say that HEAD is pointing to master -// - If master does not exists or does not have the same hash as HEAD, -// order references and check in that order if that reference has the same -// hash than HEAD. If yes, set HEAD pointing to that branch hash -// - If no reference is found, throw an error +// - Check if a reference called master exists. If exists and it +// has the same hash as HEAD hash, we can say that HEAD is pointing to master +// - If master does not exists or does not have the same hash as HEAD, +// order references and check in that order if that reference has the same +// hash than HEAD. If yes, set HEAD pointing to that branch hash +// - If no reference is found, throw an error func (a *AdvRefs) resolveHead(s storer.ReferenceStorer) error { if a.Head == nil { return nil From f47bb2d633347913d5575a42e25872c38569a4a3 Mon Sep 17 00:00:00 2001 From: Andrew Pollock Date: Tue, 2 May 2023 21:05:24 +1000 Subject: [PATCH 114/205] git: remote, add support for a configurable timeout. The previous hard-coded 10 second value is too short for listing large repositories like https://gitlab.com/gitlab-org/gitlab Return an error on nonsensical subzero timeout values --- options.go | 2 ++ remote.go | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/options.go b/options.go index 3d75e0335..d607b3078 100644 --- a/options.go +++ b/options.go @@ -640,6 +640,8 @@ type ListOptions struct { PeelingOption PeelingOption // ProxyOptions provides info required for connecting to a proxy. ProxyOptions transport.ProxyOptions + // Timeout specifies the timeout in seconds for list operations + Timeout int } // PeelingOption represents the different ways to handle peeled references. diff --git a/remote.go b/remote.go index df2649112..7d44c5b91 100644 --- a/remote.go +++ b/remote.go @@ -1258,7 +1258,15 @@ func (r *Remote) ListContext(ctx context.Context, o *ListOptions) (rfs []*plumbi } func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + timeout := o.Timeout + // Default to the old hardcoded 10s value if a timeout is not explicitly set. + if timeout == 0 { + timeout = 10 + } + if timeout < 0 { + return nil, fmt.Errorf("invalid timeout: %d", timeout) + } + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) defer cancel() return r.ListContext(ctx, o) } From 1aa8e8940336aa80eccdd8dd9b46b0e6547e7127 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Sun, 21 May 2023 02:24:18 -0400 Subject: [PATCH 115/205] git: Allow Initial Branch to be configurable --- plumbing/reference.go | 1 + repository.go | 18 +++++++++++++++- repository_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/plumbing/reference.go b/plumbing/reference.go index eef11e827..aeb4227bf 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -126,6 +126,7 @@ func (r ReferenceName) Short() string { const ( HEAD ReferenceName = "HEAD" Master ReferenceName = "refs/heads/master" + Main ReferenceName = "refs/heads/main" ) // Reference is a representation of git reference diff --git a/repository.go b/repository.go index 287b597bb..ffd4f0116 100644 --- a/repository.go +++ b/repository.go @@ -71,14 +71,30 @@ type Repository struct { wt billy.Filesystem } +type InitOptions struct { + // The default branch (e.g. "refs/heads/master") + DefaultBranch plumbing.ReferenceName +} + // Init creates an empty git repository, based on the given Storer and worktree. // The worktree Filesystem is optional, if nil a bare repository is created. If // the given storer is not empty ErrRepositoryAlreadyExists is returned func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { + options := InitOptions{ + DefaultBranch: plumbing.Master, + } + return InitWithOptions(s, worktree, options) +} + +func InitWithOptions(s storage.Storer, worktree billy.Filesystem, options InitOptions) (*Repository, error) { if err := initStorer(s); err != nil { return nil, err } + if options.DefaultBranch == "" { + options.DefaultBranch = plumbing.Master + } + r := newRepository(s, worktree) _, err := r.Reference(plumbing.HEAD, false) switch err { @@ -89,7 +105,7 @@ func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { return nil, err } - h := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.Master) + h := plumbing.NewSymbolicReference(plumbing.HEAD, options.DefaultBranch) if err := s.SetReference(h); err != nil { return nil, err } diff --git a/repository_test.go b/repository_test.go index 0080a8384..f9926727a 100644 --- a/repository_test.go +++ b/repository_test.go @@ -52,6 +52,54 @@ func (s *RepositorySuite) TestInit(c *C) { cfg, err := r.Config() c.Assert(err, IsNil) c.Assert(cfg.Core.IsBare, Equals, false) + + // check the HEAD to see what the default branch is + createCommit(c, r) + ref, err := r.Head() + c.Assert(err, IsNil) + c.Assert(ref.Name().String(), Equals, plumbing.Master.String()) +} + +func (s *RepositorySuite) TestInitWithOptions(c *C) { + r, err := InitWithOptions(memory.NewStorage(), memfs.New(), InitOptions{ + DefaultBranch: "refs/heads/foo", + }) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + createCommit(c, r) + + ref, err := r.Head() + c.Assert(err, IsNil) + c.Assert(ref.Name().String(), Equals, "refs/heads/foo") + +} + +func createCommit(c *C, r *Repository) { + // Create a commit so there is a HEAD to check + wt, err := r.Worktree() + c.Assert(err, IsNil) + + rm, err := wt.Filesystem.Create("foo.txt") + c.Assert(err, IsNil) + + _, err = rm.Write([]byte("foo text")) + c.Assert(err, IsNil) + + _, err = wt.Add("foo.txt") + c.Assert(err, IsNil) + + author := object.Signature{ + Name: "go-git", + Email: "go-git@fake.local", + When: time.Now(), + } + _, err = wt.Commit("test commit message", &CommitOptions{ + All: true, + Author: &author, + Committer: &author, + }) + c.Assert(err, IsNil) + } func (s *RepositorySuite) TestInitNonStandardDotGit(c *C) { From a8bb14673e8d56ce012c790a88b9a372267186c8 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 21 May 2023 08:51:51 +0100 Subject: [PATCH 116/205] *: Bump Go version to 1.18 on go.mod Signed-off-by: Paulo Gomes --- go.mod | 12 +++++++++++- go.sum | 12 ------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index f062b1a8d..604a3309e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/go-git/go-git/v5 -go 1.13 +// go-git supports the last 3 stable Go versions. +go 1.18 require ( github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5 @@ -28,3 +29,12 @@ require ( golang.org/x/text v0.9.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) + +require ( + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/cloudflare/circl v1.1.0 // indirect + github.com/kr/pretty v0.2.1 // indirect + github.com/kr/text v0.2.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) diff --git a/go.sum b/go.sum index db7d92ba3..9c02e8eee 100644 --- a/go.sum +++ b/go.sum @@ -51,7 +51,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/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= @@ -73,25 +72,20 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= @@ -113,7 +107,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -122,16 +115,13 @@ golang.org/x/sys v0.7.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 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/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/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= @@ -139,7 +129,6 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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= @@ -154,4 +143,3 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From b2576e56b9ad4f6e5bb3400e74d1f74c501f7d40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 May 2023 15:13:02 +0000 Subject: [PATCH 117/205] build(deps): bump github.com/cloudflare/circl from 1.1.0 to 1.3.3 Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.1.0 to 1.3.3. - [Release notes](https://github.com/cloudflare/circl/releases) - [Commits](https://github.com/cloudflare/circl/compare/v1.1.0...v1.3.3) --- updated-dependencies: - dependency-name: github.com/cloudflare/circl dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 604a3309e..8989f3691 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( require ( github.com/Microsoft/go-winio v0.5.2 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/cloudflare/circl v1.1.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 9c02e8eee..2401aa38d 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,9 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW 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/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 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= From ad19238d2cabadf84f4cec64d3f1e7d05966c3cc Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 21 May 2023 08:57:01 +0100 Subject: [PATCH 118/205] *: Add CodeQL workflow Signed-off-by: Paulo Gomes --- .github/workflows/codeql.yml | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..fbb867cc7 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,44 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '00 5 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + + steps: + - name: Checkout code + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3 + with: + languages: ${{ matrix.language }} + # xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # xref: https://codeql.github.com/codeql-query-help/go/ + queries: security-and-quality + + - name: Manual Build + run: go build ./... + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3 + with: + category: "/language:${{matrix.language}}" From 7cd1ae26f8f2108570753e947100c1f38f89fd8d Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 23 May 2023 09:10:10 +0100 Subject: [PATCH 119/205] *: Bump dependencies - github.com/ProtonMail/go-crypto to 0.0.0-20230518184743-7afd39499903. - github.com/skeema/knownhosts to 1.1.1. - golang.org/x/crypto to 0.9.0. - golang.org/x/net to 0.10.0. - golang.org/x/sys to 0.8.0. Signed-off-by: Paulo Gomes --- go.mod | 10 +++++----- go.sum | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 8989f3691..ee58848c8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ module github.com/go-git/go-git/v5 go 1.18 require ( - github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5 + github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 @@ -21,11 +21,11 @@ require ( github.com/kevinburke/ssh_config v1.2.0 github.com/pjbgf/sha1cd v0.3.0 github.com/sergi/go-diff v1.1.0 - github.com/skeema/knownhosts v1.1.0 + github.com/skeema/knownhosts v1.1.1 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.8.0 - golang.org/x/net v0.9.0 - golang.org/x/sys v0.7.0 + golang.org/x/crypto v0.9.0 + golang.org/x/net v0.10.0 + golang.org/x/sys v0.8.0 golang.org/x/text v0.9.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) diff --git a/go.sum b/go.sum index 2401aa38d..fcfc9f5b0 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ 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-20230417170513-8ee5748c52b5 h1:QXMwHM/lB4ZQhdEF7JUTNgYOJR/gWoFbgQ/2Aj1h3Dk= -github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -63,8 +63,8 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe 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/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= +github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= 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= @@ -78,8 +78,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y 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.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -89,8 +89,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -111,14 +111,14 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From cdba5330cd205312443e8553f434829c4c5e66ee Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Wed, 24 May 2023 15:17:02 +1000 Subject: [PATCH 120/205] git: Fix fetching after shallow clone. Fixes #305 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- common_test.go | 35 ++++++++++++++++ remote.go | 16 ++++++-- remote_test.go | 109 ++++++++++++++++++++++++++++++------------------- 3 files changed, 116 insertions(+), 44 deletions(-) diff --git a/common_test.go b/common_test.go index 3311a1793..7f9b84b40 100644 --- a/common_test.go +++ b/common_test.go @@ -3,10 +3,12 @@ package git import ( "os" "testing" + "time" "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/object" "github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/memory" @@ -212,3 +214,36 @@ func AssertReferencesMissing(c *C, r *Repository, expected []string) { c.Assert(err, Equals, plumbing.ErrReferenceNotFound) } } + +func CommitNewFile(c *C, repo *Repository, fileName string) plumbing.Hash { + wt, err := repo.Worktree() + c.Assert(err, IsNil) + + fd, err := wt.Filesystem.Create(fileName) + c.Assert(err, IsNil) + + _, err = fd.Write([]byte("# test file")) + c.Assert(err, IsNil) + + err = fd.Close() + c.Assert(err, IsNil) + + _, err = wt.Add(fileName) + c.Assert(err, IsNil) + + 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) + + return sha +} diff --git a/remote.go b/remote.go index 7b2741a43..0e72aadee 100644 --- a/remote.go +++ b/remote.go @@ -459,7 +459,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen req.Wants, err = getWants(r.s, refs) if len(req.Wants) > 0 { - req.Haves, err = getHaves(localRefs, remoteRefs, r.s) + req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth) if err != nil { return nil, err } @@ -837,6 +837,7 @@ func getHavesFromRef( remoteRefs map[plumbing.Hash]bool, s storage.Storer, haves map[plumbing.Hash]bool, + depth int, ) error { h := ref.Hash() if haves[h] { @@ -862,7 +863,13 @@ func getHavesFromRef( // commits from the history of each ref. walker := object.NewCommitPreorderIter(commit, haves, nil) toVisit := maxHavesToVisitPerRef - return walker.ForEach(func(c *object.Commit) error { + // But only need up to the requested depth + if depth > 0 && depth < maxHavesToVisitPerRef { + toVisit = depth + } + // It is safe to ignore any error here as we are just trying to find the references that we already have + // An example of a legitimate failure is we have a shallow clone and don't have the previous commit(s) + _ = walker.ForEach(func(c *object.Commit) error { haves[c.Hash] = true toVisit-- // If toVisit starts out at 0 (indicating there is no @@ -873,12 +880,15 @@ func getHavesFromRef( } return nil }) + + return nil } func getHaves( localRefs []*plumbing.Reference, remoteRefStorer storer.ReferenceStorer, s storage.Storer, + depth int, ) ([]plumbing.Hash, error) { haves := map[plumbing.Hash]bool{} @@ -899,7 +909,7 @@ func getHaves( continue } - err = getHavesFromRef(ref, remoteRefs, s, haves) + err = getHavesFromRef(ref, remoteRefs, s, haves, depth) if err != nil { return nil, err } diff --git a/remote_test.go b/remote_test.go index 1bcdb0f75..f8a0bdbfd 100644 --- a/remote_test.go +++ b/remote_test.go @@ -14,7 +14,6 @@ import ( "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" @@ -1175,7 +1174,7 @@ func (s *RemoteSuite) TestGetHaves(c *C) { ), } - l, err := getHaves(localRefs, memory.NewStorage(), sto) + l, err := getHaves(localRefs, memory.NewStorage(), sto, 0) c.Assert(err, IsNil) c.Assert(l, HasLen, 2) } @@ -1404,45 +1403,7 @@ func (s *RemoteSuite) TestCanPushShasToReference(c *C) { 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 - } + sha := CommitNewFile(c, repo, "README.md") gitremote, err := repo.CreateRemote(&config.RemoteConfig{ Name: "local", @@ -1472,3 +1433,69 @@ func (s *RemoteSuite) TestCanPushShasToReference(c *C) { } c.Assert(ref.Hash().String(), Equals, sha.String()) } + +func (s *RemoteSuite) TestFetchAfterShallowClone(c *C) { + tempDir, clean := s.TemporalDir() + defer clean() + remoteUrl := filepath.Join(tempDir, "remote") + repoDir := filepath.Join(tempDir, "repo") + + // Create a new repo and add more than 1 commit (so we can have a shallow commit) + remote, err := PlainInit(remoteUrl, false) + c.Assert(err, IsNil) + c.Assert(remote, NotNil) + + _ = CommitNewFile(c, remote, "File1") + _ = CommitNewFile(c, remote, "File2") + + // Clone the repo with a depth of 1 + repo, err := PlainClone(repoDir, false, &CloneOptions{ + URL: remoteUrl, + Depth: 1, + Tags: NoTags, + SingleBranch: true, + ReferenceName: "master", + }) + c.Assert(err, IsNil) + + // Add new commits to the origin (more than 1 so that our next test hits a missing commit) + _ = CommitNewFile(c, remote, "File3") + sha4 := CommitNewFile(c, remote, "File4") + + // Try fetch with depth of 1 again (note, we need to ensure no remote branch remains pointing at the old commit) + r, err := repo.Remote(DefaultRemoteName) + c.Assert(err, IsNil) + s.testFetch(c, r, &FetchOptions{ + Depth: 2, + Tags: NoTags, + + RefSpecs: []config.RefSpec{ + "+refs/heads/master:refs/heads/master", + "+refs/heads/master:refs/remotes/origin/master", + }, + }, []*plumbing.Reference{ + plumbing.NewReferenceFromStrings("refs/heads/master", sha4.String()), + plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha4.String()), + plumbing.NewSymbolicReference("HEAD", "refs/heads/master"), + }) + + // Add another commit to the origin + sha5 := CommitNewFile(c, remote, "File5") + + // Try fetch with depth of 2 this time (to reach a commit that we don't have locally) + r, err = repo.Remote(DefaultRemoteName) + c.Assert(err, IsNil) + s.testFetch(c, r, &FetchOptions{ + Depth: 1, + Tags: NoTags, + + RefSpecs: []config.RefSpec{ + "+refs/heads/master:refs/heads/master", + "+refs/heads/master:refs/remotes/origin/master", + }, + }, []*plumbing.Reference{ + plumbing.NewReferenceFromStrings("refs/heads/master", sha5.String()), + plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha5.String()), + plumbing.NewSymbolicReference("HEAD", "refs/heads/master"), + }) +} From 65a5c716353f81b20c70f4a2c6560590d9472b6e Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Thu, 25 May 2023 08:56:17 +1000 Subject: [PATCH 121/205] git: enable fetch with unqualified references Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- plumbing/reference.go | 7 ++-- remote.go | 81 +++++++++++++++++++++++++------------------ remote_test.go | 56 ++++++++++++++++++++++++++++++ repository.go | 43 ++++++++++++----------- 4 files changed, 130 insertions(+), 57 deletions(-) diff --git a/plumbing/reference.go b/plumbing/reference.go index aeb4227bf..5a67f69e7 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -15,10 +15,11 @@ const ( symrefPrefix = "ref: " ) -// RefRevParseRules are a set of rules to parse references into short names. -// These are the same rules as used by git in shorten_unambiguous_ref. +// RefRevParseRules are a set of rules to parse references into short names, or expand into a full reference. +// These are the same rules as used by git in shorten_unambiguous_ref and expand_ref. // See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 var RefRevParseRules = []string{ + "%s", "refs/%s", "refs/tags/%s", "refs/heads/%s", @@ -113,7 +114,7 @@ func (r ReferenceName) String() string { func (r ReferenceName) Short() string { s := string(r) res := s - for _, format := range RefRevParseRules { + for _, format := range RefRevParseRules[1:] { _, err := fmt.Sscanf(s, format, &res) if err == nil { continue diff --git a/remote.go b/remote.go index 0e72aadee..6ea275c1c 100644 --- a/remote.go +++ b/remote.go @@ -445,7 +445,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen return nil, err } - refs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags) + refs, specToRefs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags) if err != nil { return nil, err } @@ -469,7 +469,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen } } - updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, o.Tags, o.Force) + updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, specToRefs, o.Tags, o.Force) if err != nil { return nil, err } @@ -929,42 +929,41 @@ func calculateRefs( spec []config.RefSpec, remoteRefs storer.ReferenceStorer, tagMode TagMode, -) (memory.ReferenceStorage, error) { +) (memory.ReferenceStorage, [][]*plumbing.Reference, error) { if tagMode == AllTags { spec = append(spec, refspecAllTags) } refs := make(memory.ReferenceStorage) - for _, s := range spec { - if err := doCalculateRefs(s, remoteRefs, refs); err != nil { - return nil, err + // list of references matched for each spec + specToRefs := make([][]*plumbing.Reference, len(spec)) + for i := range spec { + var err error + specToRefs[i], err = doCalculateRefs(spec[i], remoteRefs, refs) + if err != nil { + return nil, nil, err } } - return refs, nil + return refs, specToRefs, nil } func doCalculateRefs( s config.RefSpec, remoteRefs storer.ReferenceStorer, refs memory.ReferenceStorage, -) error { - iter, err := remoteRefs.IterReferences() - if err != nil { - return err - } +) ([]*plumbing.Reference, error) { + var refList []*plumbing.Reference if s.IsExactSHA1() { ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src())) - return refs.SetReference(ref) + + refList = append(refList, ref) + return refList, refs.SetReference(ref) } var matched bool - err = iter.ForEach(func(ref *plumbing.Reference) error { - if !s.Match(ref.Name()) { - return nil - } - + onMatched := func(ref *plumbing.Reference) error { if ref.Type() == plumbing.SymbolicReference { target, err := storer.ResolveReference(remoteRefs, ref.Name()) if err != nil { @@ -979,22 +978,37 @@ func doCalculateRefs( } matched = true - if err := refs.SetReference(ref); err != nil { - return err - } + refList = append(refList, ref) + return refs.SetReference(ref) + } - if !s.IsWildcard() { - return storer.ErrStop + var ret error + if s.IsWildcard() { + iter, err := remoteRefs.IterReferences() + if err != nil { + return nil, err } + ret = iter.ForEach(func(ref *plumbing.Reference) error { + if !s.Match(ref.Name()) { + return nil + } - return nil - }) + return onMatched(ref) + }) + } else { + var resolvedRef *plumbing.Reference + src := s.Src() + resolvedRef, ret = expand_ref(remoteRefs, plumbing.ReferenceName(src)) + if ret == nil { + ret = onMatched(resolvedRef) + } + } if !matched && !s.IsWildcard() { - return NoMatchingRefSpecError{refSpec: s} + return nil, NoMatchingRefSpecError{refSpec: s} } - return err + return refList, ret } func getWants(localStorer storage.Storer, refs memory.ReferenceStorage) ([]plumbing.Hash, error) { @@ -1154,27 +1168,28 @@ func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.P func (r *Remote) updateLocalReferenceStorage( specs []config.RefSpec, fetchedRefs, remoteRefs memory.ReferenceStorage, + specToRefs [][]*plumbing.Reference, tagMode TagMode, force bool, ) (updated bool, err error) { isWildcard := true forceNeeded := false - for _, spec := range specs { + for i, spec := range specs { if !spec.IsWildcard() { isWildcard = false } - for _, ref := range fetchedRefs { - if !spec.Match(ref.Name()) && !spec.IsExactSHA1() { - continue - } - + for _, ref := range specToRefs[i] { if ref.Type() != plumbing.HashReference { continue } localName := spec.Dst(ref.Name()) + // If localName doesn't start with "refs/" then treat as a branch. + if !strings.HasPrefix(localName.String(), "refs/") { + localName = plumbing.NewBranchReferenceName(localName.String()) + } old, _ := storer.ResolveReference(r.s, localName) new := plumbing.NewHashReference(localName, ref.Hash()) diff --git a/remote_test.go b/remote_test.go index f8a0bdbfd..ca5f261c7 100644 --- a/remote_test.go +++ b/remote_test.go @@ -140,6 +140,62 @@ func (s *RemoteSuite) TestFetch(c *C) { }) } +func (s *RemoteSuite) TestFetchToNewBranch(c *C) { + r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ + URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, + }) + + s.testFetch(c, r, &FetchOptions{ + RefSpecs: []config.RefSpec{ + // qualified branch to unqualified branch + "refs/heads/master:foo", + // unqualified branch to unqualified branch + "+master:bar", + // unqualified tag to unqualified branch + config.RefSpec("tree-tag:tree-tag"), + // unqualified tag to qualified tag + config.RefSpec("+commit-tag:refs/tags/renamed-tag"), + }, + }, []*plumbing.Reference{ + plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), + plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), + plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), + plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), + plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), + plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), + }) +} + +func (s *RemoteSuite) TestFetchToNewBranchWithAllTags(c *C) { + r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ + URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, + }) + + s.testFetch(c, r, &FetchOptions{ + Tags: AllTags, + RefSpecs: []config.RefSpec{ + // qualified branch to unqualified branch + "+refs/heads/master:foo", + // unqualified branch to unqualified branch + "master:bar", + // unqualified tag to unqualified branch + config.RefSpec("+tree-tag:tree-tag"), + // unqualified tag to qualified tag + config.RefSpec("commit-tag:refs/tags/renamed-tag"), + }, + }, []*plumbing.Reference{ + plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), + plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), + plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), + plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), + plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), + plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), + plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"), + plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"), + plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), + }) +} + func (s *RemoteSuite) TestFetchNonExistantReference(c *C) { r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, diff --git a/repository.go b/repository.go index f3540c63b..17fa5ddda 100644 --- a/repository.go +++ b/repository.go @@ -1029,21 +1029,9 @@ func (r *Repository) fetchAndUpdateReferences( return nil, err } - var resolvedRef *plumbing.Reference - // return error from checking the raw ref passed in - var rawRefError error - for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) { - resolvedRef, err = storer.ResolveReference(remoteRefs, plumbing.ReferenceName(fmt.Sprintf(rule, ref))) - - if err == nil { - break - } else if rawRefError == nil { - rawRefError = err - } - } - + resolvedRef, err := expand_ref(remoteRefs, ref) if err != nil { - return nil, rawRefError + return nil, err } refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef) @@ -1489,6 +1477,23 @@ func (r *Repository) Worktree() (*Worktree, error) { return &Worktree{r: r, Filesystem: r.wt}, nil } +func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) { + // For improving troubleshooting, this preserves the error for the provided `ref`, + // and returns the error for that specific ref in case all parse rules fails. + var ret error + for _, rule := range plumbing.RefRevParseRules { + resolvedRef, err := storer.ResolveReference(s, plumbing.ReferenceName(fmt.Sprintf(rule, ref))) + + if err == nil { + return resolvedRef, nil + } else if ret == nil { + ret = err + } + } + + return nil, ret +} + // ResolveRevision resolves revision to corresponding hash. It will always // resolve to a commit hash, not a tree or annotated tag. // @@ -1518,13 +1523,9 @@ func (r *Repository) ResolveRevision(in plumbing.Revision) (*plumbing.Hash, erro tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...) - for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) { - ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef))) - - if err == nil { - tryHashes = append(tryHashes, ref.Hash()) - break - } + ref, err := expand_ref(r.Storer, plumbing.ReferenceName(revisionRef)) + if err == nil { + tryHashes = append(tryHashes, ref.Hash()) } // in ambiguous cases, `git rev-parse` will emit a warning, but From 2dfdcb9985bf55f523c01fb2dac087a4391e915a Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Thu, 25 May 2023 09:18:07 +1000 Subject: [PATCH 122/205] git: don't add to wants if exists, shallow and depth 1 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- remote.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/remote.go b/remote.go index 0e72aadee..70d4df85b 100644 --- a/remote.go +++ b/remote.go @@ -457,7 +457,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen } } - req.Wants, err = getWants(r.s, refs) + req.Wants, err = getWants(r.s, refs, o.Depth) if len(req.Wants) > 0 { req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth) if err != nil { @@ -997,10 +997,14 @@ func doCalculateRefs( return err } -func getWants(localStorer storage.Storer, refs memory.ReferenceStorage) ([]plumbing.Hash, error) { +func getWants(localStorer storage.Storer, refs memory.ReferenceStorage, depth int) ([]plumbing.Hash, error) { + // If depth is anything other than 1 and the repo has shallow commits then just because we have the commit + // at the reference doesn't mean that we don't still need to fetch the parents shallow := false - if s, _ := localStorer.Shallow(); len(s) > 0 { - shallow = true + if depth != 1 { + if s, _ := localStorer.Shallow(); len(s) > 0 { + shallow = true + } } wants := map[plumbing.Hash]bool{} From b7cc5d9bccb94fa06e3c9a231376dd981f343a9b Mon Sep 17 00:00:00 2001 From: Jleagle Date: Thu, 25 May 2023 12:52:22 +0100 Subject: [PATCH 123/205] plumbing: gitignore, Allow gitconfig to contain a gitignore relative to user home --- plumbing/format/gitignore/dir.go | 8 ++++++ plumbing/format/gitignore/dir_test.go | 39 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go index bb786557c..bf6a1c121 100644 --- a/plumbing/format/gitignore/dir.go +++ b/plumbing/format/gitignore/dir.go @@ -25,6 +25,14 @@ const ( // readIgnoreFile reads a specific git ignore file. func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) { + + if strings.HasPrefix(ignoreFile, "~") { + home, err := os.UserHomeDir() + if err == nil { + ignoreFile = strings.Replace(ignoreFile, "~", home, 1) + } + } + f, err := fs.Open(fs.Join(append(path, ignoreFile)...)) if err == nil { defer f.Close() diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go index facc36d8e..180d6f2e3 100644 --- a/plumbing/format/gitignore/dir_test.go +++ b/plumbing/format/gitignore/dir_test.go @@ -12,6 +12,7 @@ import ( type MatcherSuite struct { GFS billy.Filesystem // git repository root RFS billy.Filesystem // root that contains user home + RFSR billy.Filesystem // root that contains user home, but with with relative ~/.gitignore_global MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry MIFS billy.Filesystem // root that contains user home, but missing .gitignore @@ -95,6 +96,33 @@ func (s *MatcherSuite) SetUpTest(c *C) { s.RFS = fs + // root that contains user home, but with with relative ~/.gitignore_global + fs = memfs.New() + err = fs.MkdirAll(home, os.ModePerm) + c.Assert(err, IsNil) + + f, err = fs.Create(fs.Join(home, gitconfigFile)) + c.Assert(err, IsNil) + _, err = f.Write([]byte("[core]\n")) + c.Assert(err, IsNil) + _, err = f.Write([]byte(" excludesfile = ~/.gitignore_global" + "\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + + f, err = fs.Create(fs.Join(home, ".gitignore_global")) + c.Assert(err, IsNil) + _, err = f.Write([]byte("# IntelliJ\n")) + c.Assert(err, IsNil) + _, err = f.Write([]byte(".idea/\n")) + c.Assert(err, IsNil) + _, err = f.Write([]byte("*.iml\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + + s.RFSR = fs + // root that contains user home, but missing ~/.gitconfig fs = memfs.New() err = fs.MkdirAll(home, os.ModePerm) @@ -194,6 +222,17 @@ func (s *MatcherSuite) TestDir_ReadPatterns(c *C) { c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false) } +func (s *MatcherSuite) TestDir_ReadRelativeGlobalGitIgnore(c *C) { + ps, err := LoadGlobalPatterns(s.RFSR) + c.Assert(err, IsNil) + c.Assert(ps, HasLen, 2) + + m := NewMatcher(ps) + c.Assert(m.Match([]string{".idea/"}, true), Equals, false) + c.Assert(m.Match([]string{"*.iml"}, true), Equals, true) + c.Assert(m.Match([]string{"IntelliJ"}, true), Equals, false) +} + func (s *MatcherSuite) TestDir_LoadGlobalPatterns(c *C) { ps, err := LoadGlobalPatterns(s.RFS) c.Assert(err, IsNil) From 209eda52fb004745ce36c95576bf24204999bb42 Mon Sep 17 00:00:00 2001 From: Jleagle Date: Thu, 25 May 2023 13:26:46 +0100 Subject: [PATCH 124/205] plumbing: gitignore, Typo Co-authored-by: Paulo Gomes --- plumbing/format/gitignore/dir_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go index 180d6f2e3..7cd37ddb2 100644 --- a/plumbing/format/gitignore/dir_test.go +++ b/plumbing/format/gitignore/dir_test.go @@ -96,7 +96,7 @@ func (s *MatcherSuite) SetUpTest(c *C) { s.RFS = fs - // root that contains user home, but with with relative ~/.gitignore_global + // root that contains user home, but with relative ~/.gitignore_global fs = memfs.New() err = fs.MkdirAll(home, os.ModePerm) c.Assert(err, IsNil) From 80d5f724674280d81f9163d9a33de9ef0c72c61c Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Mon, 29 May 2023 12:47:53 +1000 Subject: [PATCH 125/205] plumbing: gitignore, fix incorrect parsing. Fixes #500 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- plumbing/format/gitignore/dir_test.go | 44 +++++++++++++++++++++++---- plumbing/format/gitignore/pattern.go | 2 ++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go index 7cd37ddb2..4bbba68b8 100644 --- a/plumbing/format/gitignore/dir_test.go +++ b/plumbing/format/gitignore/dir_test.go @@ -64,6 +64,27 @@ func (s *MatcherSuite) SetUpTest(c *C) { err = fs.MkdirAll("vendor/gopkg.in", os.ModePerm) c.Assert(err, IsNil) + err = fs.MkdirAll("multiple/sub/ignores/first", os.ModePerm) + c.Assert(err, IsNil) + err = fs.MkdirAll("multiple/sub/ignores/second", os.ModePerm) + c.Assert(err, IsNil) + f, err = fs.Create("multiple/sub/ignores/first/.gitignore") + c.Assert(err, IsNil) + _, err = f.Write([]byte("ignore_dir\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + f, err = fs.Create("multiple/sub/ignores/second/.gitignore") + c.Assert(err, IsNil) + _, err = f.Write([]byte("ignore_dir\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + err = fs.MkdirAll("multiple/sub/ignores/first/ignore_dir", os.ModePerm) + c.Assert(err, IsNil) + err = fs.MkdirAll("multiple/sub/ignores/second/ignore_dir", os.ModePerm) + c.Assert(err, IsNil) + s.GFS = fs // setup root that contains user home @@ -211,15 +232,26 @@ func (s *MatcherSuite) SetUpTest(c *C) { } func (s *MatcherSuite) TestDir_ReadPatterns(c *C) { + checkPatterns := func(ps []Pattern) { + c.Assert(ps, HasLen, 6) + 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) + c.Assert(m.Match([]string{"multiple", "sub", "ignores", "first", "ignore_dir"}, true), Equals, true) + c.Assert(m.Match([]string{"multiple", "sub", "ignores", "second", "ignore_dir"}, true), Equals, true) + } + ps, err := ReadPatterns(s.GFS, nil) c.Assert(err, IsNil) - c.Assert(ps, HasLen, 4) + checkPatterns(ps) - 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) + // passing an empty slice with capacity to check we don't hit a bug where the extra capacity is reused incorrectly + ps, err = ReadPatterns(s.GFS, make([]string, 0, 6)) + c.Assert(err, IsNil) + checkPatterns(ps) } func (s *MatcherSuite) TestDir_ReadRelativeGlobalGitIgnore(c *C) { diff --git a/plumbing/format/gitignore/pattern.go b/plumbing/format/gitignore/pattern.go index 098cb5021..450b3cdf7 100644 --- a/plumbing/format/gitignore/pattern.go +++ b/plumbing/format/gitignore/pattern.go @@ -39,6 +39,8 @@ type pattern struct { // ParsePattern parses a gitignore pattern string into the Pattern structure. func ParsePattern(p string, domain []string) Pattern { + // storing domain, copy it to ensure it isn't changed externally + domain = append([]string(nil), domain...) res := pattern{domain: domain} if strings.HasPrefix(p, inclusionPrefix) { From cb287f782ad8b5847a721d3f06265a0e9750d41f Mon Sep 17 00:00:00 2001 From: ThinkChaos Date: Tue, 7 Mar 2023 10:10:26 -0500 Subject: [PATCH 126/205] worktree: minor speedup for doAddDirectory --- worktree_status.go | 22 +++--------- worktree_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/worktree_status.go b/worktree_status.go index a26c9e51f..389482d04 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -281,8 +281,10 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, } } + directory = filepath.ToSlash(filepath.Clean(directory)) + for name := range s { - if !isPathInDirectory(name, filepath.ToSlash(filepath.Clean(directory))) { + if !isPathInDirectory(name, directory) { continue } @@ -301,23 +303,7 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, } func isPathInDirectory(path, directory string) bool { - ps := strings.Split(path, "/") - ds := strings.Split(directory, "/") - - if len(ds) == 1 && ds[0] == "." { - return true - } - - if len(ps) < len(ds) { - return false - } - - for i := 0; i < len(ds); i++ { - if ps[i] != ds[i] { - return false - } - } - return true + return directory == "." || strings.HasPrefix(path, directory+"/") } // AddWithOptions file contents to the index, updates the index using the diff --git a/worktree_test.go b/worktree_test.go index ea55ac6f3..24d5bd50f 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -1488,6 +1488,96 @@ func (s *WorktreeSuite) TestAddRemovedInDirectory(c *C) { c.Assert(file.Staging, Equals, Unmodified) } +func (s *WorktreeSuite) TestAddRemovedInDirectoryWithTrailingSlash(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = w.Filesystem.Remove("go/example.go") + c.Assert(err, IsNil) + + err = w.Filesystem.Remove("json/short.json") + c.Assert(err, IsNil) + + hash, err := w.Add("go/") + c.Assert(err, IsNil) + c.Assert(hash.IsZero(), Equals, true) + + e, err := idx.Entry("go/example.go") + c.Assert(err, IsNil) + c.Assert(e.Hash, Equals, plumbing.NewHash("880cd14280f4b9b6ed3986d6671f907d7cc2a198")) + c.Assert(e.Mode, Equals, filemode.Regular) + + e, err = idx.Entry("json/short.json") + c.Assert(err, IsNil) + c.Assert(e.Hash, Equals, plumbing.NewHash("c8f1d8c61f9da76f4cb49fd86322b6e685dba956")) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 2) + + file := status.File("go/example.go") + c.Assert(file.Staging, Equals, Deleted) + + file = status.File("json/short.json") + c.Assert(file.Staging, Equals, Unmodified) +} + +func (s *WorktreeSuite) TestAddRemovedInDirectoryDot(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = w.Filesystem.Remove("go/example.go") + c.Assert(err, IsNil) + + err = w.Filesystem.Remove("json/short.json") + c.Assert(err, IsNil) + + hash, err := w.Add(".") + c.Assert(err, IsNil) + c.Assert(hash.IsZero(), Equals, true) + + e, err := idx.Entry("go/example.go") + c.Assert(err, IsNil) + c.Assert(e.Hash, Equals, plumbing.NewHash("880cd14280f4b9b6ed3986d6671f907d7cc2a198")) + c.Assert(e.Mode, Equals, filemode.Regular) + + e, err = idx.Entry("json/short.json") + c.Assert(err, IsNil) + c.Assert(e.Hash, Equals, plumbing.NewHash("c8f1d8c61f9da76f4cb49fd86322b6e685dba956")) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 2) + + file := status.File("go/example.go") + c.Assert(file.Staging, Equals, Deleted) + + file = status.File("json/short.json") + c.Assert(file.Staging, Equals, Deleted) +} + func (s *WorktreeSuite) TestAddSymlink(c *C) { dir, clean := s.TemporalDir() defer clean() From 3f7913e4c889b557e1b5d89faf9bbcce869fb7ce Mon Sep 17 00:00:00 2001 From: ThinkChaos Date: Wed, 31 May 2023 11:13:35 -0400 Subject: [PATCH 127/205] squash: replace `if` with `||` --- worktree_status.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/worktree_status.go b/worktree_status.go index 389482d04..61bb6f759 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -294,9 +294,7 @@ func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, return } - if !added && a { - added = true - } + added = added || a } return From 5889b758b390cba1b01db6684eb83760fd7fb58c Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Sun, 4 Jun 2023 11:28:26 +1000 Subject: [PATCH 128/205] git: Clone HEAD should not force master. Fixes #363 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- repository.go | 1 - repository_test.go | 49 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/repository.go b/repository.go index 17fa5ddda..6c3be2edd 100644 --- a/repository.go +++ b/repository.go @@ -967,7 +967,6 @@ func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { case o.SingleBranch && o.ReferenceName == plumbing.HEAD: return []config.RefSpec{ config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)), - config.RefSpec(fmt.Sprintf(refspecSingleBranch, plumbing.Master.Short(), o.RemoteName)), } case o.SingleBranch: return []config.RefSpec{ diff --git a/repository_test.go b/repository_test.go index 965f0286b..bcfaa9360 100644 --- a/repository_test.go +++ b/repository_test.go @@ -1119,6 +1119,49 @@ func (s *RepositorySuite) testCloneSingleBranchAndNonHEADReference(c *C, ref str c.Assert(branch.Hash().String(), Equals, "e8d3ffab552895c19b9fcf7aa264d277cde33881") } +func (s *RepositorySuite) TestCloneSingleBranchHEADMain(c *C) { + r, _ := Init(memory.NewStorage(), nil) + + head, err := r.Head() + c.Assert(err, Equals, plumbing.ErrReferenceNotFound) + c.Assert(head, IsNil) + + err = r.clone(context.Background(), &CloneOptions{ + URL: s.GetLocalRepositoryURL(fixtures.ByTag("no-master-head").One()), + SingleBranch: true, + }) + + c.Assert(err, IsNil) + + remotes, err := r.Remotes() + c.Assert(err, IsNil) + c.Assert(remotes, HasLen, 1) + + cfg, err := r.Config() + c.Assert(err, IsNil) + c.Assert(cfg.Branches, HasLen, 1) + c.Assert(cfg.Branches["main"].Name, Equals, "main") + c.Assert(cfg.Branches["main"].Remote, Equals, "origin") + c.Assert(cfg.Branches["main"].Merge, Equals, plumbing.ReferenceName("refs/heads/main")) + + head, err = r.Reference(plumbing.HEAD, false) + c.Assert(err, IsNil) + c.Assert(head, NotNil) + c.Assert(head.Type(), Equals, plumbing.SymbolicReference) + c.Assert(head.Target().String(), Equals, "refs/heads/main") + + branch, err := r.Reference(head.Target(), false) + c.Assert(err, IsNil) + c.Assert(branch, NotNil) + c.Assert(branch.Hash().String(), Equals, "786dafbd351e587da1ae97e5fb9fbdf868b4a28f") + + branch, err = r.Reference("refs/remotes/origin/HEAD", false) + c.Assert(err, IsNil) + c.Assert(branch, NotNil) + c.Assert(branch.Type(), Equals, plumbing.HashReference) + c.Assert(branch.Hash().String(), Equals, "786dafbd351e587da1ae97e5fb9fbdf868b4a28f") +} + func (s *RepositorySuite) TestCloneSingleBranch(c *C) { r, _ := Init(memory.NewStorage(), nil) @@ -1154,12 +1197,6 @@ func (s *RepositorySuite) TestCloneSingleBranch(c *C) { c.Assert(err, IsNil) c.Assert(branch, NotNil) c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") - - branch, err = r.Reference("refs/remotes/origin/master", false) - c.Assert(err, IsNil) - c.Assert(branch, NotNil) - c.Assert(branch.Type(), Equals, plumbing.HashReference) - c.Assert(branch.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") } func (s *RepositorySuite) TestCloneSingleTag(c *C) { From 4ed9ded8ca127922d8286be188eef87efd2c2ab9 Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Mon, 5 Jun 2023 00:25:35 +1000 Subject: [PATCH 129/205] plumbing: gitignore, Allow gitconfig to contain a gitignore relative to any user home. Fixes #578 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- plumbing/format/gitignore/dir.go | 16 +++++++-- plumbing/format/gitignore/dir_test.go | 52 ++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go index bf6a1c121..3c4469a0e 100644 --- a/plumbing/format/gitignore/dir.go +++ b/plumbing/format/gitignore/dir.go @@ -5,6 +5,7 @@ import ( "bytes" "io" "os" + "os/user" "strings" "github.com/go-git/go-billy/v5" @@ -27,9 +28,18 @@ const ( func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) { if strings.HasPrefix(ignoreFile, "~") { - home, err := os.UserHomeDir() - if err == nil { - ignoreFile = strings.Replace(ignoreFile, "~", home, 1) + firstSlash := strings.Index(ignoreFile, "/") + if firstSlash == 1 { + home, err := os.UserHomeDir() + if err == nil { + ignoreFile = strings.Replace(ignoreFile, "~", home, 1) + } + } else if firstSlash > 1 { + username := ignoreFile[1:firstSlash] + userAccount, err := user.Lookup(username) + if err == nil { + ignoreFile = strings.Replace(ignoreFile, ignoreFile[:firstSlash], userAccount.HomeDir, 1) + } } } diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go index 4bbba68b8..465c5713a 100644 --- a/plumbing/format/gitignore/dir_test.go +++ b/plumbing/format/gitignore/dir_test.go @@ -2,7 +2,9 @@ package gitignore import ( "os" + "os/user" "strconv" + "strings" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" @@ -12,7 +14,8 @@ import ( type MatcherSuite struct { GFS billy.Filesystem // git repository root RFS billy.Filesystem // root that contains user home - RFSR billy.Filesystem // root that contains user home, but with with relative ~/.gitignore_global + RFSR billy.Filesystem // root that contains user home, but with relative ~/.gitignore_global + RFSU billy.Filesystem // root that contains user home, but with relative ~user/.gitignore_global MCFS billy.Filesystem // root that contains user home, but missing ~/.gitconfig MEFS billy.Filesystem // root that contains user home, but missing excludesfile entry MIFS billy.Filesystem // root that contains user home, but missing .gitignore @@ -144,6 +147,37 @@ func (s *MatcherSuite) SetUpTest(c *C) { s.RFSR = fs + // root that contains user home, but with relative ~user/.gitignore_global + fs = memfs.New() + err = fs.MkdirAll(home, os.ModePerm) + c.Assert(err, IsNil) + + f, err = fs.Create(fs.Join(home, gitconfigFile)) + c.Assert(err, IsNil) + _, err = f.Write([]byte("[core]\n")) + c.Assert(err, IsNil) + currentUser, err := user.Current() + c.Assert(err, IsNil) + // remove domain for windows + username := currentUser.Username[strings.Index(currentUser.Username, "\\")+1:] + _, err = f.Write([]byte(" excludesfile = ~" + username + "/.gitignore_global" + "\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + + f, err = fs.Create(fs.Join(home, ".gitignore_global")) + c.Assert(err, IsNil) + _, err = f.Write([]byte("# IntelliJ\n")) + c.Assert(err, IsNil) + _, err = f.Write([]byte(".idea/\n")) + c.Assert(err, IsNil) + _, err = f.Write([]byte("*.iml\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + + s.RFSU = fs + // root that contains user home, but missing ~/.gitconfig fs = memfs.New() err = fs.MkdirAll(home, os.ModePerm) @@ -255,14 +289,16 @@ func (s *MatcherSuite) TestDir_ReadPatterns(c *C) { } func (s *MatcherSuite) TestDir_ReadRelativeGlobalGitIgnore(c *C) { - ps, err := LoadGlobalPatterns(s.RFSR) - c.Assert(err, IsNil) - c.Assert(ps, HasLen, 2) + for _, fs := range []billy.Filesystem{s.RFSR, s.RFSU} { + ps, err := LoadGlobalPatterns(fs) + c.Assert(err, IsNil) + c.Assert(ps, HasLen, 2) - m := NewMatcher(ps) - c.Assert(m.Match([]string{".idea/"}, true), Equals, false) - c.Assert(m.Match([]string{"*.iml"}, true), Equals, true) - c.Assert(m.Match([]string{"IntelliJ"}, true), Equals, false) + m := NewMatcher(ps) + c.Assert(m.Match([]string{".idea/"}, true), Equals, false) + c.Assert(m.Match([]string{"*.iml"}, true), Equals, true) + c.Assert(m.Match([]string{"IntelliJ"}, true), Equals, false) + } } func (s *MatcherSuite) TestDir_LoadGlobalPatterns(c *C) { From 42b41aa8925782ac08c329576e3e843a41e0d6f8 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 4 Jun 2023 22:56:41 +0100 Subject: [PATCH 130/205] *: Add SECURITY.md. Fixes: #527 #543 Signed-off-by: Paulo Gomes --- SECURITY.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..0d2f8d038 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,38 @@ +# go-git Security Policy + +The purpose of this security policy is to outline `go-git`'s process +for reporting, handling and disclosing security sensitive information. + +## Supported Versions + +The project follows a version support policy where only the latest minor +release is actively supported. Therefore, only issues that impact the latest +minor release will be fixed. Users are encouraged to upgrade to the latest +minor/patch release to benefit from the most up-to-date features, bug fixes, +and security enhancements.​ + +The supported versions policy applies to both the `go-git` library and its +associated repositories within the `go-git` org. + +## Reporting Security Issues + +Please report any security vulnerabilities or potential weaknesses in `go-git` +privately via go-git-security@googlegroups.com. Do not publicly disclose the +details of the vulnerability until a fix has been implemented and released. + +During the process the project maintainers will investigate the report, so please +provide detailed information, including steps to reproduce, affected versions, and any mitigations if known. + +The project maintainers will acknowledge the receipt of the report and work with +the reporter to validate and address the issue. + +Please note that `go-git` does not have any bounty programs, and therefore do +not provide financial compensation for disclosures. + +## Security Disclosure Process + +The project maintainers will make every effort to promptly address security issues. + +Once a security vulnerability is fixed, a security advisory will be published to notify users and provide appropriate mitigation measures. + +All `go-git` advisories can be found at https://github.com/go-git/go-git/security/advisories. From 2d6af16bf6d051cb3014c9970f3ea813e54f73b0 Mon Sep 17 00:00:00 2001 From: "matej.risek" Date: Tue, 9 May 2023 11:54:55 +0200 Subject: [PATCH 131/205] git: add a clone option to allow for shallow cloning of submodules This option matches the git clone option --shallow-submodules. https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---no-shallow-submodules --- options.go | 3 +++ repository.go | 8 +++++++- repository_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/options.go b/options.go index 3d75e0335..8b2602254 100644 --- a/options.go +++ b/options.go @@ -62,6 +62,9 @@ type CloneOptions struct { // within, using their default settings. This option is ignored if the // cloned repository does not have a worktree. RecurseSubmodules SubmoduleRescursivity + // ShallowSubmodules limit cloning submodules to the 1 level of depth. + // It matches the git command --shallow-submodules. + ShallowSubmodules bool // Progress is where the human readable information sent by the server is // stored, if nil nothing is stored and the capability (if supported) // no-progress, is sent to the server to avoid send this information. diff --git a/repository.go b/repository.go index 287b597bb..33a9d69c3 100644 --- a/repository.go +++ b/repository.go @@ -900,7 +900,13 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { if o.RecurseSubmodules != NoRecurseSubmodules { if err := w.updateSubmodules(&SubmoduleUpdateOptions{ RecurseSubmodules: o.RecurseSubmodules, - Auth: o.Auth, + Depth: func() int { + if o.ShallowSubmodules { + return 1 + } + return 0 + }(), + Auth: o.Auth, }); err != nil { return err } diff --git a/repository_test.go b/repository_test.go index 0080a8384..79a884b4f 100644 --- a/repository_test.go +++ b/repository_test.go @@ -884,6 +884,43 @@ func (s *RepositorySuite) TestPlainCloneWithRecurseSubmodules(c *C) { c.Assert(cfg.Submodules, HasLen, 2) } +func (s *RepositorySuite) TestPlainCloneWithShallowSubmodules(c *C) { + if testing.Short() { + c.Skip("skipping test in short mode.") + } + + dir, clean := s.TemporalDir() + defer clean() + + path := fixtures.ByTag("submodule").One().Worktree().Root() + mainRepo, err := PlainClone(dir, false, &CloneOptions{ + URL: path, + RecurseSubmodules: 1, + ShallowSubmodules: true, + }) + c.Assert(err, IsNil) + + mainWorktree, err := mainRepo.Worktree() + c.Assert(err, IsNil) + + submodule, err := mainWorktree.Submodule("basic") + c.Assert(err, IsNil) + + subRepo, err := submodule.Repository() + c.Assert(err, IsNil) + + lr, err := subRepo.Log(&LogOptions{}) + c.Assert(err, IsNil) + + commitCount := 0 + for _, err := lr.Next(); err == nil; _, err = lr.Next() { + commitCount++ + } + c.Assert(err, IsNil) + + c.Assert(commitCount, Equals, 1) +} + func (s *RepositorySuite) TestPlainCloneNoCheckout(c *C) { dir, clean := s.TemporalDir() defer clean() From 9706315a961c52184e2f9111883b08df6ac702b8 Mon Sep 17 00:00:00 2001 From: "matej.risek" Date: Mon, 5 Jun 2023 14:03:47 +0200 Subject: [PATCH 132/205] git: fix the issue with submodules having the SCP style URL fail due to the wrong URL parsing --- submodule.go | 14 +++++++------- submodule_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/submodule.go b/submodule.go index b0c416961..84f020dc7 100644 --- a/submodule.go +++ b/submodule.go @@ -5,13 +5,13 @@ import ( "context" "errors" "fmt" - "net/url" "path" "github.com/go-git/go-billy/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/index" + "github.com/go-git/go-git/v5/plumbing/transport" ) var ( @@ -133,29 +133,29 @@ func (s *Submodule) Repository() (*Repository, error) { return nil, err } - moduleURL, err := url.Parse(s.c.URL) + moduleEndpoint, err := transport.NewEndpoint(s.c.URL) if err != nil { return nil, err } - if !path.IsAbs(moduleURL.Path) { + if !path.IsAbs(moduleEndpoint.Path) && moduleEndpoint.Protocol == "file" { remotes, err := s.w.r.Remotes() if err != nil { return nil, err } - rootURL, err := url.Parse(remotes[0].c.URLs[0]) + rootEndpoint, err := transport.NewEndpoint(remotes[0].c.URLs[0]) if err != nil { return nil, err } - rootURL.Path = path.Join(rootURL.Path, moduleURL.Path) - *moduleURL = *rootURL + rootEndpoint.Path = path.Join(rootEndpoint.Path, moduleEndpoint.Path) + *moduleEndpoint = *rootEndpoint } _, err = r.CreateRemote(&config.RemoteConfig{ Name: DefaultRemoteName, - URLs: []string{moduleURL.String()}, + URLs: []string{moduleEndpoint.String()}, }) return r, err diff --git a/submodule_test.go b/submodule_test.go index a8a0681a1..a066af29a 100644 --- a/submodule_test.go +++ b/submodule_test.go @@ -6,7 +6,10 @@ import ( "path/filepath" "testing" + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/storage/memory" fixtures "github.com/go-git/go-git-fixtures/v4" . "gopkg.in/check.v1" @@ -262,3 +265,26 @@ func (s *SubmoduleSuite) TestSubmodulesFetchDepth(c *C) { c.Assert(commitCount, Equals, 1) } + +func (s *SubmoduleSuite) TestSubmoduleParseScp(c *C) { + repo := &Repository{ + Storer: memory.NewStorage(), + wt: memfs.New(), + } + worktree := &Worktree{ + Filesystem: memfs.New(), + r: repo, + } + submodule := &Submodule{ + initialized: true, + c: nil, + w: worktree, + } + + submodule.c = &config.Submodule{ + URL: "git@github.com:username/submodule_repo", + } + + _, err := submodule.Repository() + c.Assert(err, IsNil) +} From abe49196b80e367f7cc123a095b32958f8d0470b Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sat, 1 Jul 2023 15:37:24 +0100 Subject: [PATCH 133/205] plumbing: http, Fix empty repos on Git v2.41+ Git v2.41.0 comes with [changes](https://github.com/git/git/commit/933e3a4ee205353d8f093d5dfcd226fa432c4e58) that breaks go-git's assumptions for when detecting empty repositories. Go-git expects a flush instead of the first hash line. Instead, a dummy capabilities^{} with zero-id is returned. The change aims to allow for identifying the object format even when cloning empty repositories. Signed-off-by: Paulo Gomes --- plumbing/protocol/packp/advrefs_decode.go | 1 + plumbing/protocol/packp/advrefs_decode_test.go | 10 ++++++++++ plumbing/transport/http/common.go | 11 +++++++++++ remote.go | 9 +++++---- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/plumbing/protocol/packp/advrefs_decode.go b/plumbing/protocol/packp/advrefs_decode.go index 63bbe5ab1..f8d26a28e 100644 --- a/plumbing/protocol/packp/advrefs_decode.go +++ b/plumbing/protocol/packp/advrefs_decode.go @@ -133,6 +133,7 @@ func decodeFirstHash(p *advRefsDecoder) decoderStateFn { return nil } + // TODO: Use object-format (when available) for hash size. Git 2.41+ if len(p.line) < hashSize { p.error("cannot read hash, pkt-line too short") return nil diff --git a/plumbing/protocol/packp/advrefs_decode_test.go b/plumbing/protocol/packp/advrefs_decode_test.go index 83b0b0138..d1271450e 100644 --- a/plumbing/protocol/packp/advrefs_decode_test.go +++ b/plumbing/protocol/packp/advrefs_decode_test.go @@ -218,6 +218,16 @@ func (s *AdvRefsDecodeSuite) TestCaps(c *C) { {Name: capability.SymRef, Values: []string{"HEAD:refs/heads/master"}}, {Name: capability.Agent, Values: []string{"foo=bar"}}, }, + }, { + input: []string{ + "0000000000000000000000000000000000000000 capabilities^{}\x00report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta object-format=sha1 agent=git/2.41.0\n", + pktline.FlushString, + }, + capabilities: []entry{ + {Name: capability.ReportStatus, Values: []string(nil)}, + {Name: capability.ObjectFormat, Values: []string{"sha1"}}, + {Name: capability.Agent, Values: []string{"git/2.41.0"}}, + }, }} { ar := s.testDecodeOK(c, test.input) for _, fixCap := range test.capabilities { diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go index f9b7a0e59..a7cdc1e16 100644 --- a/plumbing/transport/http/common.go +++ b/plumbing/transport/http/common.go @@ -73,6 +73,17 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) ( return nil, err } + // Git 2.41+ returns a zero-id plus capabilities when an empty + // repository is being cloned. This skips the existing logic within + // advrefs_decode.decodeFirstHash, which expects a flush-pkt instead. + // + // This logic aligns with plumbing/transport/internal/common/common.go. + if ar.IsEmpty() && + // Empty repositories are valid for git-receive-pack. + transport.ReceivePackServiceName != serviceName { + return nil, transport.ErrEmptyRemoteRepository + } + transport.FilterUnsupportedCapabilities(ar.Capabilities) s.advRefs = ar diff --git a/remote.go b/remote.go index 25ec093d6..679e0af21 100644 --- a/remote.go +++ b/remote.go @@ -224,11 +224,13 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { return err } - if err = rs.Error(); err != nil { - return err + if rs != nil { + if err = rs.Error(); err != nil { + return err + } } - return r.updateRemoteReferenceStorage(req, rs) + return r.updateRemoteReferenceStorage(req) } func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool { @@ -347,7 +349,6 @@ func (r *Remote) newReferenceUpdateRequest( func (r *Remote) updateRemoteReferenceStorage( req *packp.ReferenceUpdateRequest, - result *packp.ReportStatus, ) error { for _, spec := range r.c.Fetch { From 025e1311f8473f65facf8cffeccc11e39466db4a Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Sun, 2 Jul 2023 22:51:41 +1000 Subject: [PATCH 134/205] plumbing: packp, A request is not empty if it contains shallows. Fixes #328 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- plumbing/protocol/packp/uppackreq.go | 6 +++--- plumbing/protocol/packp/uppackreq_test.go | 7 +++++++ plumbing/transport/internal/common/common.go | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/plumbing/protocol/packp/uppackreq.go b/plumbing/protocol/packp/uppackreq.go index de2206b3f..48f443856 100644 --- a/plumbing/protocol/packp/uppackreq.go +++ b/plumbing/protocol/packp/uppackreq.go @@ -38,10 +38,10 @@ func NewUploadPackRequestFromCapabilities(adv *capability.List) *UploadPackReque } } -// IsEmpty a request if empty if Haves are contained in the Wants, or if Wants -// length is zero +// IsEmpty returns whether a request is empty - it is empty if Haves are contained +// in the Wants, or if Wants length is zero, and we don't have any shallows func (r *UploadPackRequest) IsEmpty() bool { - return isSubset(r.Wants, r.Haves) + return isSubset(r.Wants, r.Haves) && len(r.Shallows) == 0 } func isSubset(needle []plumbing.Hash, haystack []plumbing.Hash) bool { diff --git a/plumbing/protocol/packp/uppackreq_test.go b/plumbing/protocol/packp/uppackreq_test.go index 5a6eb2cc8..ad38565a9 100644 --- a/plumbing/protocol/packp/uppackreq_test.go +++ b/plumbing/protocol/packp/uppackreq_test.go @@ -41,6 +41,13 @@ func (s *UploadPackRequestSuite) TestIsEmpty(c *C) { r.Haves = append(r.Haves, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) c.Assert(r.IsEmpty(), Equals, true) + + r = NewUploadPackRequest() + r.Wants = append(r.Wants, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + r.Haves = append(r.Haves, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + r.Shallows = append(r.Shallows, plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) + + c.Assert(r.IsEmpty(), Equals, false) } type UploadHavesSuite struct{} diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index 99e0850f9..5fdf4250d 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -232,7 +232,7 @@ func (s *session) handleAdvRefDecodeError(err error) error { // UploadPack performs a request to the server to fetch a packfile. A reader is // returned with the packfile content. The reader must be closed after reading. func (s *session) UploadPack(ctx context.Context, req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { - if req.IsEmpty() && len(req.Shallows) == 0 { + if req.IsEmpty() { return nil, transport.ErrEmptyUploadPackRequest } From dca0c4c669a3fcf87cab2ce9fb66762ad0339381 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 2 Jul 2023 22:34:09 +0100 Subject: [PATCH 135/205] *: Improve docs on examples, compatibility and extensibility Fixes #777 Signed-off-by: Paulo Gomes --- COMPATIBILITY.md | 344 ++++++++++++++++++++++++++++++-------------- EXTENDING.md | 78 ++++++++++ _examples/README.md | 4 + 3 files changed, 315 insertions(+), 111 deletions(-) create mode 100644 EXTENDING.md diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 2a72b501e..afd4f03bc 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -1,111 +1,233 @@ -Supported Capabilities -====================== - -Here is a non-comprehensive table of git commands and features whose equivalent -is supported by go-git. - -| Feature | Status | Notes | -|---------------------------------------|--------|-------| -| **config** | -| config | ✔ | Reading and modifying per-repository configuration (`.git/config`) is supported. Global configuration (`$HOME/.gitconfig`) is not. | -| **getting and creating repositories** | -| init | ✔ | Plain init and `--bare` are supported. Flags `--template`, `--separate-git-dir` and `--shared` are not. | -| clone | ✔ | Plain clone and equivalents to `--progress`, `--single-branch`, `--depth`, `--origin`, `--recurse-submodules` are supported. Others are not. | -| **basic snapshotting** | -| add | ✔ | Plain add is supported. Any other flags aren't supported | -| status | ✔ | -| commit | ✔ | -| reset | ✔ | -| rm | ✔ | -| mv | ✔ | -| **branching and merging** | -| branch | ✔ | -| checkout | ✔ | Basic usages of checkout are supported. | -| merge | ✖ | -| mergetool | ✖ | -| stash | ✖ | -| tag | ✔ | -| **sharing and updating projects** | -| fetch | ✔ | -| pull | ✔ | Only supports merges where the merge can be resolved as a fast-forward. | -| push | ✔ | -| remote | ✔ | -| submodule | ✔ | -| **inspection and comparison** | -| show | ✔ | -| log | ✔ | -| shortlog | (see log) | -| describe | | -| **patching** | -| apply | ✖ | -| cherry-pick | ✖ | -| diff | ✔ | Patch object with UnifiedDiff output representation | -| rebase | ✖ | -| revert | ✖ | -| **debugging** | -| bisect | ✖ | -| blame | ✔ | -| grep | ✔ | -| **email** || -| am | ✖ | -| apply | ✖ | -| format-patch | ✖ | -| send-email | ✖ | -| request-pull | ✖ | -| **external systems** | -| svn | ✖ | -| fast-import | ✖ | -| **administration** | -| clean | ✔ | -| gc | ✖ | -| fsck | ✖ | -| reflog | ✖ | -| filter-branch | ✖ | -| instaweb | ✖ | -| archive | ✖ | -| bundle | ✖ | -| prune | ✖ | -| repack | ✖ | -| **server admin** | -| daemon | | -| update-server-info | | -| **advanced** | -| notes | ✖ | -| replace | ✖ | -| worktree | ✖ | -| annotate | (see blame) | -| **gpg** | -| git-verify-commit | ✔ | -| git-verify-tag | ✔ | -| **plumbing commands** | -| cat-file | ✔ | -| check-ignore | | -| commit-tree | | -| count-objects | | -| diff-index | | -| for-each-ref | ✔ | -| hash-object | ✔ | -| ls-files | ✔ | -| merge-base | ✔ | Calculates the merge-base only between two commits, and supports `--independent` and `--is-ancestor` modifiers; Does not support `--fork-point` nor `--octopus` modifiers. | -| read-tree | | -| rev-list | ✔ | -| rev-parse | | -| show-ref | ✔ | -| symbolic-ref | ✔ | -| update-index | | -| update-ref | | -| verify-pack | | -| write-tree | | -| **protocols** | -| http(s):// (dumb) | ✖ | -| http(s):// (smart) | ✔ | -| git:// | ✔ | -| ssh:// | ✔ | -| file:// | partial | Warning: this is not pure Golang. This shells out to the `git` binary. | -| custom | ✔ | -| **other features** | -| gitignore | ✔ | -| gitattributes | ✖ | -| index version | | -| packfile version | | -| push-certs | ✖ | +# Supported Features + +Here is a non-comprehensive table of git commands and features and their +compatibility status with go-git. + +## Getting and creating repositories + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `init` | | ✅ | | | +| `init` | `--bare` | ✅ | | | +| `init` | `--template`
`--separate-git-dir`
`--shared` | ❌ | | | +| `clone` | | ✅ | | - [PlainClone](_examples/clone/main.go) | +| `clone` | Authentication:
- none
- access token
- username + password
- ssh | ✅ | | - [clone ssh](_examples/clone/auth/ssh/main.go)
- [clone access token](_examples/clone/auth/basic/access_token/main.go)
- [clone user + password](_examples/clone/auth/basic/username_password/main.go) | +| `clone` | `--progress`
`--single-branch`
`--depth`
`--origin`
`--recurse-submodules` | ✅ | | - [recurse submodules](_examples/clone/main.go)
- [progress](_examples/progress/main.go) | + +## Basic snapshotting + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `add` | | ✅ | Plain add is supported. Any other flags aren't supported | | +| `status` | | ✅ | | | +| `commit` | | ✅ | | - [commit](_examples/commit/main.go) | +| `reset` | | ✅ | | | +| `rm` | | ✅ | | | +| `mv` | | ✅ | | | + +## Branching and merging + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `branch` | | ✅ | | - [branch](_examples/branch/main.go) | +| `checkout` | | ✅ | Basic usages of checkout are supported. | - [checkout](_examples/checkout/main.go) | +| `merge` | | ❌ | | | +| `mergetool` | | ❌ | | | +| `stash` | | ❌ | | | +| `tag` | | ✅ | | - [tag](_examples/tag/main.go)
- [tag create and push](_examples/tag-create-push/main.go) | + +## Sharing and updating projects + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `fetch` | | ✅ | | | +| `pull` | | ✅ | Only supports merges where the merge can be resolved as a fast-forward. | - [pull](_examples/pull/main.go) | +| `push` | | ✅ | | - [push](_examples/push/main.go) | +| `remote` | | ✅ | | - [remotes](_examples/remotes/main.go) | +| `submodule` | | ✅ | | - [submodule](_examples/submodule/main.go) | +| `submodule` | deinit | ❌ | | | + +## Inspection and comparison + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `show` | | ✅ | | | +| `log` | | ✅ | | - [log](_examples/log/main.go) | +| `shortlog` | | (see log) | | | +| `describe` | | ❌ | | | + +## Patching + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `apply` | | ❌ | | | +| `cherry-pick` | | ❌ | | | +| `diff` | | ✅ | Patch object with UnifiedDiff output representation. | | +| `rebase` | | ❌ | | | +| `revert` | | ❌ | | | + +## Debugging + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `bisect` | | ❌ | | | +| `blame` | | ✅ | | - [blame](_examples/blame/main.go) | +| `grep` | | ✅ | | | + +## Email + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `am` | | ❌ | | | +| `apply` | | ❌ | | | +| `format-patch` | | ❌ | | | +| `send-email` | | ❌ | | | +| `request-pull` | | ❌ | | | + +## External systems + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `svn` | | ❌ | | | +| `fast-import` | | ❌ | | | +| `lfs` | | ❌ | | | + +## Administration + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `clean` | | ✅ | | | +| `gc` | | ❌ | | | +| `fsck` | | ❌ | | | +| `reflog` | | ❌ | | | +| `filter-branch` | | ❌ | | | +| `instaweb` | | ❌ | | | +| `archive` | | ❌ | | | +| `bundle` | | ❌ | | | +| `prune` | | ❌ | | | +| `repack` | | ❌ | | | + +## Server admin + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `daemon` | | ❌ | | | +| `update-server-info` | | ❌ | | | + +## Advanced + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `notes` | | ❌ | | | +| `replace` | | ❌ | | | +| `worktree` | | ❌ | | | +| `annotate` | | (see blame) | | | + +## GPG + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `git-verify-commit` | | ✅ | | | +| `git-verify-tag` | | ✅ | | | + +## Plumbing commands + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `cat-file` | | ✅ | | | +| `check-ignore` | | ❌ | | | +| `commit-tree` | | ❌ | | | +| `count-objects` | | ❌ | | | +| `diff-index` | | ❌ | | | +| `for-each-ref` | | ✅ | | | +| `hash-object` | | ✅ | | | +| `ls-files` | | ✅ | | | +| `ls-remote` | | ✅ | | - [ls-remote](_examples/ls-remote/main.go) | +| `merge-base` | `--independent`
`--is-ancestor` | ⚠️ (partial) | Calculates the merge-base only between two commits. | - [merge-base](_examples/merge_base/main.go) | +| `merge-base` | `--fork-point`
`--octopus` | ❌ | | | +| `read-tree` | | ❌ | | | +| `rev-list` | | ✅ | | | +| `rev-parse` | | ❌ | | | +| `show-ref` | | ✅ | | | +| `symbolic-ref` | | ✅ | | | +| `update-index` | | ❌ | | | +| `update-ref` | | ❌ | | | +| `verify-pack` | | ❌ | | | +| `write-tree` | | ❌ | | | + +## Indexes and Git Protocols + +| Feature | Version | Status | Notes | +|---|---|---|---| +| index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | +| index | [v2](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ✅ | | +| index | [v3](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | +| pack-protocol | [v1](https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.txt) | ✅ | | +| pack-protocol | [v2](https://github.com/git/git/blob/master/Documentation/gitprotocol-v2.txt) | ❌ | | +| multi-pack-index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | +| pack-*.rev files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | +| pack-*.mtimes files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | +| cruft packs | | ❌ | | + +## Capabilities + +| Feature | Status | Notes | +|---|---|---| +| `multi_ack` | ❌ | | +| `multi_ack_detailed` | ❌ | | +| `no-done` | ❌ | | +| `thin-pack` | ❌ | | +| `side-band` | ⚠️ (partial) | | +| `side-band-64k` | ⚠️ (partial) | | +| `ofs-delta` | ✅ | | +| `agent` | ✅ | | +| `object-format` | ❌ | | +| `symref` | ✅ | | +| `shallow` | ✅ | | +| `deepen-since` | ✅ | | +| `deepen-not` | ❌ | | +| `deepen-relative` | ❌ | | +| `no-progress` | ✅ | | +| `include-tag` | ✅ | | +| `report-status` | ✅ | | +| `report-status-v2` | ❌ | | +| `delete-refs` | ✅ | | +| `quiet` | ❌ | | +| `atomic` | ✅ | | +| `push-options` | ✅ | | +| `allow-tip-sha1-in-want` | ✅ | | +| `allow-reachable-sha1-in-want` | ❌ | | +| `push-cert=` | ❌ | | +| `filter` | ❌ | | +| `session-id=` | ❌ | | + +## Transport Schemes + +| Scheme | Status | Notes | Examples | +|---|---|---|---| +| `http(s)://` (dumb) | ❌ | | | +| `http(s)://` (smart) | ✅ | | | +| `git://` | ✅ | | | +| `ssh://` | ✅ | | | +| `file://` | ⚠️ (partial) | Warning: this is not pure Golang. This shells out to the `git` binary. | | +| Custom | ✅ | All existing schemes can be replaced by custom implementations. | - [custom_http](_examples/custom_http/main.go) | + +## SHA256 + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `init` | | ✅ | Requires building with tag sha256. | - [init](_examples/sha256/main.go) | +| `commit` | | ✅ | Requires building with tag sha256. | - [commit](_examples/sha256/main.go) | +| `pull` | | ❌ | | | +| `fetch` | | ❌ | | | +| `push` | | ❌ | | | + +## Other features + +| Feature | Sub-feature | Status | Notes | Examples | +|---|---|---|---|---| +| `config` | `--local` | ✅ | Read and write per-repository (`.git/config`). | | +| `config` | `--global`
`--system` | ✅ | Read-only. | | +| `gitignore` | | ✅ | | | +| `gitattributes` | | ✅ | | | +| `git-worktree` | | ❌ | Multiple worktrees are not supported. | | diff --git a/EXTENDING.md b/EXTENDING.md new file mode 100644 index 000000000..a2778e34a --- /dev/null +++ b/EXTENDING.md @@ -0,0 +1,78 @@ +# Extending go-git + +`go-git` was built in a highly extensible manner, which enables some of its functionalities to be changed or extended without the need of changing its codebase. Here are the key extensibility features: + +## Dot Git Storers + +Dot git storers are the components responsible for storing the Git internal files, including objects and references. + +The built-in storer implementations include [memory](storage/memory) and [filesystem](storage/filesystem). The `memory` storer stores all the data in memory, and its use look like this: + +```go + r, err := git.Init(memory.NewStorage(), nil) +``` + +The `filesystem` storer stores the data in the OS filesystem, and can be used as follows: + +```go + r, err := git.Init(filesystem.NewStorage(osfs.New("/tmp/foo")), nil) +``` + +New implementations can be created by implementing the [storage.Storer interface](storage/storer.go#L16). + +## Filesystem + +Git repository worktrees are managed using a filesystem abstraction based on [go-billy](https://github.com/go-git/go-billy). The Git operations will take place against the specific filesystem implementation. Initialising a repository in Memory can be done as follows: + +```go + fs := memfs.New() + r, err := git.Init(memory.NewStorage(), fs) +``` + +The same operation can be done against the OS filesystem: + +```go + fs := osfs.New("/tmp/foo") + r, err := git.Init(memory.NewStorage(), fs) +``` + +New filesystems (e.g. cloud based storage) could be created by implementing `go-billy`'s [Filesystem interface](https://github.com/go-git/go-billy/blob/326c59f064021b821a55371d57794fbfb86d4cb3/fs.go#L52). + +## Transport Schemes + +Git supports various transport schemes, including `http`, `https`, `ssh`, `git`, `file`. `go-git` defines the [transport.Transport interface](plumbing/transport/common.go#L48) to represent them. + +The built-in implementations can be replaced by calling `client.InstallProtocol`. + +An example of changing the built-in `https` implementation to skip TLS could look like this: + +```go + customClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + + client.InstallProtocol("https", githttp.NewClient(customClient)) +``` + +Some internal implementations enables code reuse amongst the different transport implementations. Some of these may be made public in the future (e.g. `plumbing/transport/internal/common`). + +## Cache + +Several different operations across `go-git` lean on caching of objects in order to achieve optimal performance. The caching functionality is defined by the [cache.Object interface](plumbing/cache/common.go#L17). + +Two built-in implementations are `cache.ObjectLRU` and `cache.BufferLRU`. However, the caching functionality can be customized by implementing the interface `cache.Object` interface. + +## Hash + +`go-git` uses the `crypto.Hash` interface to represent hash functions. The built-in implementations are `github.com/pjbgf/sha1cd` for SHA1 and Go's `crypto/SHA256`. + +The default hash functions can be changed by calling `hash.RegisterHash`. +```go + func init() { + hash.RegisterHash(crypto.SHA1, sha1.New) + } +``` + +New `SHA1` or `SHA256` hash functions that implement the `hash.RegisterHash` interface can be registered by calling `RegisterHash`. diff --git a/_examples/README.md b/_examples/README.md index 1f150f99b..46f1fb0fc 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -24,8 +24,12 @@ Here you can find a list of annotated _go-git_ examples: - [progress](progress/main.go) - Printing the progress information from the sideband. - [revision](revision/main.go) - Solve a revision into a commit. - [submodule](submodule/main.go) - Submodule update remote. +- [azure devops](azure_devops/main.go) - Cloning Azure DevOps repositories. +- [blame](blame/main.go) - Blame/annotate a commit. +- [ls-remote](ls-remote/main.go) - List remote tags without cloning a repository. ### Advanced - [custom_http](custom_http/main.go) - Replacing the HTTP client using a custom one. - [clone with context](context/main.go) - Cloning a repository with graceful cancellation. - [storage](storage/README.md) - Implementing a custom storage system. +- [sha256](sha256/main.go) - Init and commiting repositories that use sha256 as object format. From e0fc8a8f00c238f7f7cdf0e0c59e743550587bdf Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Thu, 6 Jul 2023 08:45:49 +1000 Subject: [PATCH 136/205] plumbing: blame, Complete rewrite. Fixes #603 Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- _examples/blame/main.go | 48 +++ blame.go | 612 ++++++++++++++++++++++++--------- blame_test.go | 134 ++++---- plumbing/object/commit.go | 11 + plumbing/object/commit_test.go | 70 ++++ references.go | 264 -------------- references_test.go | 401 --------------------- 7 files changed, 647 insertions(+), 893 deletions(-) create mode 100644 _examples/blame/main.go delete mode 100644 references.go delete mode 100644 references_test.go diff --git a/_examples/blame/main.go b/_examples/blame/main.go new file mode 100644 index 000000000..3ffae17b5 --- /dev/null +++ b/_examples/blame/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + + "github.com/go-git/go-git/v5" + . "github.com/go-git/go-git/v5/_examples" +) + +// Basic example of how to blame a repository. +func main() { + CheckArgs("", "") + url := os.Args[1] + path := os.Args[2] + + tmp, err := os.MkdirTemp("", "go-git-blame-*") + CheckIfError(err) + + defer os.RemoveAll(tmp) + + // Clone the given repository. + Info("git clone %s %s", url, tmp) + r, err := git.PlainClone( + tmp, + false, + &git.CloneOptions{ + URL: url, + Tags: git.NoTags, + }, + ) + CheckIfError(err) + + // Retrieve the branch's HEAD, to then get the HEAD commit. + ref, err := r.Head() + CheckIfError(err) + + c, err := r.CommitObject(ref.Hash()) + CheckIfError(err) + + Info("git blame %s", path) + + // Blame the given file/path. + br, err := git.Blame(c, path) + CheckIfError(err) + + fmt.Printf("%s", br.String()) +} diff --git a/blame.go b/blame.go index 43634b32c..2a877dcdf 100644 --- a/blame.go +++ b/blame.go @@ -2,16 +2,18 @@ package git import ( "bytes" + "container/heap" "errors" "fmt" + "io" "strconv" - "strings" "time" "unicode/utf8" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/utils/diff" + "github.com/sergi/go-diff/diffmatchpatch" ) // BlameResult represents the result of a Blame operation. @@ -29,67 +31,86 @@ type BlameResult struct { func Blame(c *object.Commit, path string) (*BlameResult, error) { // The file to blame is identified by the input arguments: // commit and path. commit is a Commit object obtained from a Repository. Path - // represents a path to a specific file contained into the repository. + // represents a path to a specific file contained in the repository. // - // Blaming a file is a two step process: + // Blaming a file is done by walking the tree in reverse order trying to find where each line was last modified. // - // 1. Create a linear history of the commits affecting a file. We use - // revlist.New for that. + // When a diff is found it cannot immediately assume it came from that commit, as it may have come from 1 of its + // parents, so it will first try to resolve those diffs from its parents, if it couldn't find the change in its + // parents then it will assign the change to itself. // - // 2. Then build a graph with a node for every line in every file in - // the history of the file. + // When encountering 2 parents that have made the same change to a file it will choose the parent that was merged + // into the current branch first (this is determined by the order of the parents inside the commit). // - // Each node is assigned a commit: Start by the nodes in the first - // commit. Assign that commit as the creator of all its lines. - // - // Then jump to the nodes in the next commit, and calculate the diff - // between the two files. Newly created lines get - // assigned the new commit as its origin. Modified lines also get - // this new commit. Untouched lines retain the old commit. - // - // All this work is done in the assignOrigin function which holds all - // the internal relevant data in a "blame" struct, that is not - // exported. - // - // TODO: ways to improve the efficiency of this function: - // 1. Improve revlist - // 2. Improve how to traverse the history (example a backward traversal will - // be much more efficient) - // - // TODO: ways to improve the function in general: - // 1. Add memoization between revlist and assign. - // 2. It is using much more memory than needed, see the TODOs below. + // This currently works on a line by line basis, if performance becomes an issue it could be changed to work with + // hunks rather than lines. Then when encountering diff hunks it would need to split them where necessary. b := new(blame) b.fRev = c b.path = path + b.q = new(priorityQueue) - // get all the file revisions - if err := b.fillRevs(); err != nil { + file, err := b.fRev.File(path) + if err != nil { return nil, err } - - // calculate the line tracking graph and fill in - // file contents in data. - if err := b.fillGraphAndData(); err != nil { + finalLines, err := file.Lines() + if err != nil { return nil, err } + finalLength := len(finalLines) - file, err := b.fRev.File(b.path) + needsMap := make([]lineMap, finalLength) + for i := range needsMap { + needsMap[i] = lineMap{i, i, nil, -1} + } + contents, err := file.Contents() if err != nil { return nil, err } - finalLines, err := file.Lines() + b.q.Push(&queueItem{ + nil, + nil, + c, + path, + contents, + needsMap, + 0, + false, + 0, + }) + items := make([]*queueItem, 0) + for { + items = items[:0] + for { + if b.q.Len() == 0 { + return nil, errors.New("invalid state: no items left on the blame queue") + } + item := b.q.Pop() + items = append(items, item) + next := b.q.Peek() + if next == nil || next.Hash != item.Commit.Hash { + break + } + } + finished, err := b.addBlames(items) + if err != nil { + return nil, err + } + if finished == true { + break + } + } if err != nil { return nil, err } - // Each node (line) holds the commit where it was introduced or - // last modified. To achieve that we use the FORWARD algorithm - // described in Zimmermann, et al. "Mining Version Archives for - // Co-changed Lines", in proceedings of the Mining Software - // Repositories workshop, Shanghai, May 22-23, 2006. - lines, err := newLines(finalLines, b.sliceGraph(len(b.graph)-1)) + b.lineToCommit = make([]*object.Commit, finalLength) + for i := range needsMap { + b.lineToCommit[i] = needsMap[i].Commit + } + + lines, err := newLines(finalLines, b.lineToCommit) if err != nil { return nil, err } @@ -105,6 +126,8 @@ func Blame(c *object.Commit, path string) (*BlameResult, error) { type Line struct { // Author is the email address of the last author that modified the line. Author string + // AuthorName is the name of the last author that modified the line. + AuthorName string // Text is the original text of the line. Text string // Date is when the original text of the line was introduced @@ -113,31 +136,21 @@ type Line struct { Hash plumbing.Hash } -func newLine(author, text string, date time.Time, hash plumbing.Hash) *Line { +func newLine(author, authorName, text string, date time.Time, hash plumbing.Hash) *Line { return &Line{ - Author: author, - Text: text, - Hash: hash, - Date: date, + Author: author, + AuthorName: authorName, + Text: text, + Hash: hash, + Date: date, } } func newLines(contents []string, commits []*object.Commit) ([]*Line, error) { - lcontents := len(contents) - lcommits := len(commits) - - if lcontents != lcommits { - if lcontents == lcommits-1 && contents[lcontents-1] != "\n" { - contents = append(contents, "\n") - } else { - return nil, errors.New("contents and commits have different length") - } - } - - result := make([]*Line, 0, lcontents) + result := make([]*Line, 0, len(contents)) for i := range contents { result = append(result, newLine( - commits[i].Author.Email, contents[i], + commits[i].Author.Email, commits[i].Author.Name, contents[i], commits[i].Author.When, commits[i].Hash, )) } @@ -152,151 +165,426 @@ type blame struct { path string // the commit of the final revision of the file to blame fRev *object.Commit - // the chain of revisions affecting the the file to blame - revs []*object.Commit - // the contents of the file across all its revisions - data []string - // the graph of the lines in the file across all the revisions - graph [][]*object.Commit + // resolved lines + lineToCommit []*object.Commit + // queue of commits that need resolving + q *priorityQueue } -// calculate the history of a file "path", starting from commit "from", sorted by commit date. -func (b *blame) fillRevs() error { - var err error - - b.revs, err = references(b.fRev, b.path) - return err +type lineMap struct { + Orig, Cur int + Commit *object.Commit + FromParentNo int } -// build graph of a file from its revision history -func (b *blame) fillGraphAndData() error { - //TODO: not all commits are needed, only the current rev and the prev - b.graph = make([][]*object.Commit, len(b.revs)) - b.data = make([]string, len(b.revs)) // file contents in all the revisions - // for every revision of the file, starting with the first - // one... - for i, rev := range b.revs { +func (b *blame) addBlames(curItems []*queueItem) (bool, error) { + curItem := curItems[0] + + // Simple optimisation to merge paths, there is potential to go a bit further here and check for any duplicates + // not only if they are all the same. + if len(curItems) == 1 { + curItems = nil + } else if curItem.IdenticalToChild { + allSame := true + lenCurItems := len(curItems) + lowestParentNo := curItem.ParentNo + for i := 1; i < lenCurItems; i++ { + if !curItems[i].IdenticalToChild || curItem.Child != curItems[i].Child { + allSame = false + break + } + lowestParentNo = min(lowestParentNo, curItems[i].ParentNo) + } + if allSame { + curItem.Child.numParentsNeedResolving = curItem.Child.numParentsNeedResolving - lenCurItems + 1 + curItems = nil // free the memory + curItem.ParentNo = lowestParentNo + + // Now check if we can remove the parent completely + for curItem.Child.IdenticalToChild && curItem.Child.MergedChildren == nil && curItem.Child.numParentsNeedResolving == 1 { + oldChild := curItem.Child + curItem.Child = oldChild.Child + curItem.ParentNo = oldChild.ParentNo + } + } + } + + // if we have more than 1 item for this commit, create a single needsMap + if len(curItems) > 1 { + curItem.MergedChildren = make([]childToNeedsMap, len(curItems)) + for i, c := range curItems { + curItem.MergedChildren[i] = childToNeedsMap{c.Child, c.NeedsMap, c.IdenticalToChild, c.ParentNo} + } + newNeedsMap := make([]lineMap, 0, len(curItem.NeedsMap)) + newNeedsMap = append(newNeedsMap, curItems[0].NeedsMap...) + + for i := 1; i < len(curItems); i++ { + cur := curItems[i].NeedsMap + n := 0 // position in newNeedsMap + c := 0 // position in current list + for c < len(cur) { + if n == len(newNeedsMap) { + newNeedsMap = append(newNeedsMap, cur[c:]...) + break + } else if newNeedsMap[n].Cur == cur[c].Cur { + n++ + c++ + } else if newNeedsMap[n].Cur < cur[c].Cur { + n++ + } else { + newNeedsMap = append(newNeedsMap, cur[c]) + newPos := len(newNeedsMap) - 1 + for newPos > n { + newNeedsMap[newPos-1], newNeedsMap[newPos] = newNeedsMap[newPos], newNeedsMap[newPos-1] + newPos-- + } + } + } + } + curItem.NeedsMap = newNeedsMap + curItem.IdenticalToChild = false + curItem.Child = nil + curItems = nil // free the memory + } + + parents, err := parentsContainingPath(curItem.path, curItem.Commit) + if err != nil { + return false, err + } + + anyPushed := false + for parnetNo, prev := range parents { + currentHash, err := blobHash(curItem.path, curItem.Commit) + if err != nil { + return false, err + } + prevHash, err := blobHash(prev.Path, prev.Commit) + if err != nil { + return false, err + } + if currentHash == prevHash { + if len(parents) == 1 && curItem.MergedChildren == nil && curItem.IdenticalToChild { + // commit that has 1 parent and 1 child and is the same as both, bypass it completely + b.q.Push(&queueItem{ + Child: curItem.Child, + Commit: prev.Commit, + path: prev.Path, + Contents: curItem.Contents, + NeedsMap: curItem.NeedsMap, // reuse the NeedsMap as we are throwing away this item + IdenticalToChild: true, + ParentNo: curItem.ParentNo, + }) + } else { + b.q.Push(&queueItem{ + Child: curItem, + Commit: prev.Commit, + path: prev.Path, + Contents: curItem.Contents, + NeedsMap: append([]lineMap(nil), curItem.NeedsMap...), // create new slice and copy + IdenticalToChild: true, + ParentNo: parnetNo, + }) + curItem.numParentsNeedResolving++ + } + anyPushed = true + continue + } + // get the contents of the file - file, err := rev.File(b.path) + file, err := prev.Commit.File(prev.Path) if err != nil { - return nil + return false, err } - b.data[i], err = file.Contents() + prevContents, err := file.Contents() if err != nil { - return err + return false, err } - nLines := countLines(b.data[i]) - // create a node for each line - b.graph[i] = make([]*object.Commit, nLines) - // assign a commit to each node - // if this is the first revision, then the node is assigned to - // this first commit. - if i == 0 { - for j := 0; j < nLines; j++ { - b.graph[i][j] = b.revs[i] + + hunks := diff.Do(prevContents, curItem.Contents) + prevl := -1 + curl := -1 + need := 0 + getFromParent := make([]lineMap, 0) + out: + for h := range hunks { + hLines := countLines(hunks[h].Text) + for hl := 0; hl < hLines; hl++ { + switch { + case hunks[h].Type == diffmatchpatch.DiffEqual: + prevl++ + curl++ + if curl == curItem.NeedsMap[need].Cur { + // add to needs + getFromParent = append(getFromParent, lineMap{curl, prevl, nil, -1}) + // move to next need + need++ + if need >= len(curItem.NeedsMap) { + break out + } + } + case hunks[h].Type == diffmatchpatch.DiffInsert: + curl++ + if curl == curItem.NeedsMap[need].Cur { + // the line we want is added, it may have been added here (or by another parent), skip it for now + need++ + if need >= len(curItem.NeedsMap) { + break out + } + } + case hunks[h].Type == diffmatchpatch.DiffDelete: + prevl += hLines + continue out + default: + return false, errors.New("invalid state: invalid hunk Type") + } } - } else { - // if this is not the first commit, then assign to the old - // commit or to the new one, depending on what the diff - // says. - b.assignOrigin(i, i-1) + } + + if len(getFromParent) > 0 { + b.q.Push(&queueItem{ + curItem, + nil, + prev.Commit, + prev.Path, + prevContents, + getFromParent, + 0, + false, + parnetNo, + }) + curItem.numParentsNeedResolving++ + anyPushed = true } } - return nil -} -// sliceGraph returns a slice of commits (one per line) for a particular -// revision of a file (0=first revision). -func (b *blame) sliceGraph(i int) []*object.Commit { - fVs := b.graph[i] - result := make([]*object.Commit, 0, len(fVs)) - for _, v := range fVs { - c := *v - result = append(result, &c) + curItem.Contents = "" // no longer need, free the memory + + if !anyPushed { + return finishNeeds(curItem) } - return result + + return false, nil } -// Assigns origin to vertexes in current (c) rev from data in its previous (p) -// revision -func (b *blame) assignOrigin(c, p int) { - // assign origin based on diff info - hunks := diff.Do(b.data[p], b.data[c]) - sl := -1 // source line - dl := -1 // destination line - for h := range hunks { - hLines := countLines(hunks[h].Text) - for hl := 0; hl < hLines; hl++ { - switch { - case hunks[h].Type == 0: - sl++ - dl++ - b.graph[c][dl] = b.graph[p][sl] - case hunks[h].Type == 1: - dl++ - b.graph[c][dl] = b.revs[c] - case hunks[h].Type == -1: - sl++ - default: - panic("unreachable") +func finishNeeds(curItem *queueItem) (bool, error) { + // any needs left in the needsMap must have come from this revision + for i := range curItem.NeedsMap { + if curItem.NeedsMap[i].Commit == nil { + curItem.NeedsMap[i].Commit = curItem.Commit + curItem.NeedsMap[i].FromParentNo = -1 + } + } + + if curItem.Child == nil && curItem.MergedChildren == nil { + return true, nil + } + + if curItem.MergedChildren == nil { + return applyNeeds(curItem.Child, curItem.NeedsMap, curItem.IdenticalToChild, curItem.ParentNo) + } + + for _, ctn := range curItem.MergedChildren { + m := 0 // position in merged needs map + p := 0 // position in parent needs map + for p < len(ctn.NeedsMap) { + if ctn.NeedsMap[p].Cur == curItem.NeedsMap[m].Cur { + ctn.NeedsMap[p].Commit = curItem.NeedsMap[m].Commit + m++ + p++ + } else if ctn.NeedsMap[p].Cur < curItem.NeedsMap[m].Cur { + p++ + } else { + m++ } } + finished, err := applyNeeds(ctn.Child, ctn.NeedsMap, ctn.IdenticalToChild, ctn.ParentNo) + if finished || err != nil { + return finished, err + } } -} -// GoString prints the results of a Blame using git-blame's style. -func (b *blame) GoString() string { - var buf bytes.Buffer + return false, nil +} - file, err := b.fRev.File(b.path) - if err != nil { - panic("PrettyPrint: internal error in repo.Data") +func applyNeeds(child *queueItem, needsMap []lineMap, identicalToChild bool, parentNo int) (bool, error) { + if identicalToChild { + for i := range child.NeedsMap { + l := &child.NeedsMap[i] + if l.Cur != needsMap[i].Cur || l.Orig != needsMap[i].Orig { + return false, errors.New("needsMap isn't the same? Why not??") + } + if l.Commit == nil || parentNo < l.FromParentNo { + l.Commit = needsMap[i].Commit + l.FromParentNo = parentNo + } + } + } else { + i := 0 + out: + for j := range child.NeedsMap { + l := &child.NeedsMap[j] + for needsMap[i].Orig < l.Cur { + i++ + if i == len(needsMap) { + break out + } + } + if l.Cur == needsMap[i].Orig { + if l.Commit == nil || parentNo < l.FromParentNo { + l.Commit = needsMap[i].Commit + l.FromParentNo = parentNo + } + } + } } - contents, err := file.Contents() - if err != nil { - panic("PrettyPrint: internal error in repo.Data") + child.numParentsNeedResolving-- + if child.numParentsNeedResolving == 0 { + finished, err := finishNeeds(child) + if finished || err != nil { + return finished, err + } } - lines := strings.Split(contents, "\n") + return false, nil +} + +// String prints the results of a Blame using git-blame's style. +func (b BlameResult) String() string { + var buf bytes.Buffer + // max line number length - mlnl := len(strconv.Itoa(len(lines))) + mlnl := len(strconv.Itoa(len(b.Lines))) // max author length mal := b.maxAuthorLength() - format := fmt.Sprintf("%%s (%%-%ds %%%dd) %%s\n", - mal, mlnl) + format := fmt.Sprintf("%%s (%%-%ds %%s %%%dd) %%s\n", mal, mlnl) - fVs := b.graph[len(b.graph)-1] - for ln, v := range fVs { - fmt.Fprintf(&buf, format, v.Hash.String()[:8], - prettyPrintAuthor(fVs[ln]), ln+1, lines[ln]) + for ln := range b.Lines { + _, _ = fmt.Fprintf(&buf, format, b.Lines[ln].Hash.String()[:8], + b.Lines[ln].AuthorName, b.Lines[ln].Date.Format("2006-01-02 15:04:05 -0700"), ln+1, b.Lines[ln].Text) } return buf.String() } -// utility function to pretty print the author. -func prettyPrintAuthor(c *object.Commit) string { - return fmt.Sprintf("%s %s", c.Author.Name, c.Author.When.Format("2006-01-02")) -} - // utility function to calculate the number of runes needed // to print the longest author name in the blame of a file. -func (b *blame) maxAuthorLength() int { - memo := make(map[plumbing.Hash]struct{}, len(b.graph)-1) - fVs := b.graph[len(b.graph)-1] +func (b BlameResult) maxAuthorLength() int { m := 0 - for ln := range fVs { - if _, ok := memo[fVs[ln].Hash]; ok { - continue - } - memo[fVs[ln].Hash] = struct{}{} - m = max(m, utf8.RuneCountInString(prettyPrintAuthor(fVs[ln]))) + for ln := range b.Lines { + m = max(m, utf8.RuneCountInString(b.Lines[ln].AuthorName)) } return m } +func min(a, b int) int { + if a < b { + return a + } + return b +} + func max(a, b int) int { if a > b { return a } return b } + +type childToNeedsMap struct { + Child *queueItem + NeedsMap []lineMap + IdenticalToChild bool + ParentNo int +} + +type queueItem struct { + Child *queueItem + MergedChildren []childToNeedsMap + Commit *object.Commit + path string + Contents string + NeedsMap []lineMap + numParentsNeedResolving int + IdenticalToChild bool + ParentNo int +} + +type priorityQueueImp []*queueItem + +func (pq *priorityQueueImp) Len() int { return len(*pq) } +func (pq *priorityQueueImp) Less(i, j int) bool { + return !(*pq)[i].Commit.Less((*pq)[j].Commit) +} +func (pq *priorityQueueImp) Swap(i, j int) { (*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i] } +func (pq *priorityQueueImp) Push(x any) { *pq = append(*pq, x.(*queueItem)) } +func (pq *priorityQueueImp) Pop() any { + n := len(*pq) + ret := (*pq)[n-1] + (*pq)[n-1] = nil // ovoid memory leak + *pq = (*pq)[0 : n-1] + + return ret +} +func (pq *priorityQueueImp) Peek() *object.Commit { + if len(*pq) == 0 { + return nil + } + return (*pq)[0].Commit +} + +type priorityQueue priorityQueueImp + +func (pq *priorityQueue) Init() { heap.Init((*priorityQueueImp)(pq)) } +func (pq *priorityQueue) Len() int { return (*priorityQueueImp)(pq).Len() } +func (pq *priorityQueue) Push(c *queueItem) { + heap.Push((*priorityQueueImp)(pq), c) +} +func (pq *priorityQueue) Pop() *queueItem { + return heap.Pop((*priorityQueueImp)(pq)).(*queueItem) +} +func (pq *priorityQueue) Peek() *object.Commit { return (*priorityQueueImp)(pq).Peek() } + +type parentCommit struct { + Commit *object.Commit + Path string +} + +func parentsContainingPath(path string, c *object.Commit) ([]parentCommit, error) { + // TODO: benchmark this method making git.object.Commit.parent public instead of using + // an iterator + var result []parentCommit + iter := c.Parents() + for { + parent, err := iter.Next() + if err == io.EOF { + return result, nil + } + if err != nil { + return nil, err + } + if _, err := parent.File(path); err == nil { + result = append(result, parentCommit{parent, path}) + } else { + // look for renames + patch, err := parent.Patch(c) + if err != nil { + return nil, err + } else if patch != nil { + for _, fp := range patch.FilePatches() { + from, to := fp.Files() + if from != nil && to != nil && to.Path() == path { + result = append(result, parentCommit{parent, from.Path()}) + break + } + } + } + } + } +} + +func blobHash(path string, commit *object.Commit) (plumbing.Hash, error) { + file, err := commit.File(path) + if err != nil { + return plumbing.ZeroHash, err + } + return file.Hash, nil +} diff --git a/blame_test.go b/blame_test.go index 7895b66fd..1c5db266f 100644 --- a/blame_test.go +++ b/blame_test.go @@ -28,7 +28,7 @@ func (s *BlameSuite) TestNewLines(c *C) { } func (s *BlameSuite) TestNewLinesWithNewLine(c *C) { - lines, err := newLines([]string{"foo"}, []*object.Commit{ + lines, err := newLines([]string{"foo", ""}, []*object.Commit{ {Message: "foo"}, {Message: "bar"}, }) @@ -36,7 +36,7 @@ func (s *BlameSuite) TestNewLinesWithNewLine(c *C) { c.Assert(err, IsNil) c.Assert(lines, HasLen, 2) c.Assert(lines[0].Text, Equals, "foo") - c.Assert(lines[1].Text, Equals, "\n") + c.Assert(lines[1].Text, Equals, "") } type blameTest struct { @@ -81,10 +81,11 @@ func (s *BlameSuite) mockBlame(c *C, t blameTest, r *Repository) (blame *BlameRe commit, err := r.CommitObject(plumbing.NewHash(t.blames[i])) c.Assert(err, IsNil) l := &Line{ - Author: commit.Author.Email, - Text: lines[i], - Date: commit.Author.When, - Hash: commit.Hash, + Author: commit.Author.Email, + AuthorName: commit.Author.Name, + Text: lines[i], + Date: commit.Author.When, + Hash: commit.Hash, } blamedLines = append(blamedLines, l) } @@ -146,7 +147,11 @@ var blameTests = [...]blameTest{ repeat("6ecf0ef2c2dffb796033e5a02219af86ec6584e5", 7), )}, /* - // Failed + // This fails due to the different diff tool being used to create the patches. + // For example in commit d4b48a39aba7d3bd3e8abef2274a95b112d1ae73 when "function echo_status()" is added: + // - 'git diff' adds the new "}\n\n" to the end of function and keeps the "}\n\n" beforehand blamed to the previous commit + // - our diff adds the new "}\n\n" before the function and reuses the existing "}\n\n" to close the new function + // the resultant file is the same, but it causes blame not to match. {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "InstallSpinnaker.sh", concat( repeat("ce9f123d790717599aaeb76bc62510de437761be", 2), repeat("a47d0aaeda421f06df248ad65bd58230766bf118", 1), @@ -341,7 +346,9 @@ var blameTests = [...]blameTest{ repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 185), )}, /* - // Fail by 3 + // This fails due to the different diff tool being used to create the patches. + // For commit c89dab0d42f1856d157357e9010f8cc6a12f5b1f our diff tool keeps an existing newline as moved in the file, whereas + // 'git diff' says the existing newline was deleted and a new one created. {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "pylib/spinnaker/configurator.py", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 53), repeat("c89dab0d42f1856d157357e9010f8cc6a12f5b1f", 1), @@ -423,65 +430,63 @@ var blameTests = [...]blameTest{ repeat("637ba49300f701cfbd859c1ccf13c4f39a9ba1c8", 1), repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 13), )}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/default-spinnaker-local.yml", concat( + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), + repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 2), + repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 2), + repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 5), + repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 2), + repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 5), + repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 1), + repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 25), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("eaf7614cad81e8ab5c813dd4821129d0c04ea449", 1), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 24), + repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), + repeat("3ce7b902a51bac2f10994f7d1f251b616c975e54", 1), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 6), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 14), + repeat("7c8d9a6081d9cb7a56c479bfe64d70540ea32795", 5), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), + )}, + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/spinnaker.yml", concat( + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 32), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 2), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 1), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 6), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 2), + repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), + repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 3), + repeat("7c8d9a6081d9cb7a56c479bfe64d70540ea32795", 3), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 50), + repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), + repeat("d4553dac205023fa77652308af1a2d1cf52138fb", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("eaf7614cad81e8ab5c813dd4821129d0c04ea449", 1), + repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 39), + repeat("079e42e7c979541b6fab7343838f7b9fd4a360cd", 6), + repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 15), + )}, /* - // fail a few lines - {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/default-spinnaker-local.yml", concat( - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), - repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 2), - repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 2), - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 5), - repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 2), - repeat("a596972a661d9a7deca8abd18b52ce1a39516e89", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 5), - repeat("5e09821cbd7d710405b61cab0a795c2982a71b9c", 1), - repeat("8980daf661408a3faa1f22c225702a5c1d11d5c9", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 25), - repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), - repeat("eaf7614cad81e8ab5c813dd4821129d0c04ea449", 1), - repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 24), - repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), - repeat("3ce7b902a51bac2f10994f7d1f251b616c975e54", 1), - repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 6), - repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 14), - repeat("7c8d9a6081d9cb7a56c479bfe64d70540ea32795", 5), - repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), - )}, - */ - /* - // fail one line - {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "config/spinnaker.yml", concat( - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 32), - repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 2), - repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 1), - repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 6), - repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), - repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 2), - repeat("5a2a845bc08974a36d599a4a4b7e25be833823b0", 2), - repeat("41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", 3), - repeat("7c8d9a6081d9cb7a56c479bfe64d70540ea32795", 3), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 50), - repeat("974b775a8978b120ff710cac93a21c7387b914c9", 2), - repeat("d4553dac205023fa77652308af1a2d1cf52138fb", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 9), - repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), - repeat("eaf7614cad81e8ab5c813dd4821129d0c04ea449", 1), - repeat("caf6d62e8285d4681514dd8027356fb019bc97ff", 1), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 39), - repeat("079e42e7c979541b6fab7343838f7b9fd4a360cd", 6), - repeat("ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", 15), - )}, - */ - /* + // This fails due to the different diff tool being used to create the patches + // For commit d1ff4e13e9e0b500821aa558373878f93487e34b our diff tool keeps an existing newline as moved in the file, whereas + // 'git diff' says the existing newline was deleted and a new one created {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/install_development.sh", concat( repeat("99534ecc895fe17a1d562bb3049d4168a04d0865", 1), repeat("d1ff4e13e9e0b500821aa558373878f93487e34b", 71), )}, */ /* - // FAIL two lines interchanged + // This fails due to the different diff tool being used to create the patches + // For commit 838aed816872c52ed435e4876a7b64dba0bed500 the diff tools assign the "fi\n" to different line numbers {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/bootstrap_dev.sh", concat( repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 95), repeat("838aed816872c52ed435e4876a7b64dba0bed500", 1), @@ -542,10 +547,7 @@ var blameTests = [...]blameTest{ repeat("838aed816872c52ed435e4876a7b64dba0bed500", 8), )}, */ - /* - // FAIL move? - {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/create_google_dev_vm.sh", concat( - repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 20), - )}, - */ + {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "dev/create_google_dev_vm.sh", concat( + repeat("a24001f6938d425d0e7504bdf5d27fc866a85c3d", 20), + )}, } diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index d2f718408..8a0f35c75 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -376,6 +376,17 @@ func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) { return openpgp.CheckArmoredDetachedSignature(keyring, er, signature, nil) } +// Less defines a compare function to determine which commit is 'earlier' by: +// - First use Committer.When +// - If Committer.When are equal then use Author.When +// - If Author.When also equal then compare the string value of the hash +func (c *Commit) Less(rhs *Commit) bool { + return c.Committer.When.Before(rhs.Committer.When) || + (c.Committer.When.Equal(rhs.Committer.When) && + (c.Author.When.Before(rhs.Author.When) || + (c.Author.When.Equal(rhs.Author.When) && bytes.Compare(c.Hash[:], rhs.Hash[:]) < 0))) +} + func indent(t string) string { var output []string for _, line := range strings.Split(t, "\n") { diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index a939bc6a4..4b0f6b424 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -503,3 +503,73 @@ func (s *SuiteCommit) TestEncodeWithoutSignature(c *C) { "\n"+ "Merge branch 'master' of github.com:tyba/git-fixture\n") } + +func (s *SuiteCommit) TestLess(c *C) { + when1 := time.Now() + when2 := when1.Add(time.Hour) + + hash1 := plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea") + hash2 := plumbing.NewHash("2669dce138d9b841a518c64b10914d88f5e488ea") + + commitLessTests := []struct { + Committer1When, Committer2When time.Time + Author1When, Author2When time.Time + Hash1, Hash2 plumbing.Hash + Exp bool + }{ + {when1, when1, when1, when1, hash1, hash2, true}, + {when1, when1, when1, when1, hash2, hash1, false}, + {when1, when1, when1, when2, hash1, hash2, true}, + {when1, when1, when1, when2, hash2, hash1, true}, + {when1, when1, when2, when1, hash1, hash2, false}, + {when1, when1, when2, when1, hash2, hash1, false}, + {when1, when1, when2, when2, hash1, hash2, true}, + {when1, when1, when2, when2, hash2, hash1, false}, + {when1, when2, when1, when1, hash1, hash2, true}, + {when1, when2, when1, when1, hash2, hash1, true}, + {when1, when2, when1, when2, hash1, hash2, true}, + {when1, when2, when1, when2, hash2, hash1, true}, + {when1, when2, when2, when1, hash1, hash2, true}, + {when1, when2, when2, when1, hash2, hash1, true}, + {when1, when2, when2, when2, hash1, hash2, true}, + {when1, when2, when2, when2, hash2, hash1, true}, + {when2, when1, when1, when1, hash1, hash2, false}, + {when2, when1, when1, when1, hash2, hash1, false}, + {when2, when1, when1, when2, hash1, hash2, false}, + {when2, when1, when1, when2, hash2, hash1, false}, + {when2, when1, when2, when1, hash1, hash2, false}, + {when2, when1, when2, when1, hash2, hash1, false}, + {when2, when1, when2, when2, hash1, hash2, false}, + {when2, when1, when2, when2, hash2, hash1, false}, + {when2, when2, when1, when1, hash1, hash2, true}, + {when2, when2, when1, when1, hash2, hash1, false}, + {when2, when2, when1, when2, hash1, hash2, true}, + {when2, when2, when1, when2, hash2, hash1, true}, + {when2, when2, when2, when1, hash1, hash2, false}, + {when2, when2, when2, when1, hash2, hash1, false}, + {when2, when2, when2, when2, hash1, hash2, true}, + {when2, when2, when2, when2, hash2, hash1, false}, + } + + for _, t := range commitLessTests { + commit1 := &Commit{ + Hash: t.Hash1, + Author: Signature{ + When: t.Author1When, + }, + Committer: Signature{ + When: t.Committer1When, + }, + } + commit2 := &Commit{ + Hash: t.Hash2, + Author: Signature{ + When: t.Author2When, + }, + Committer: Signature{ + When: t.Committer2When, + }, + } + c.Assert(commit1.Less(commit2), Equals, t.Exp) + } +} diff --git a/references.go b/references.go deleted file mode 100644 index 6d96035af..000000000 --- a/references.go +++ /dev/null @@ -1,264 +0,0 @@ -package git - -import ( - "io" - "sort" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/utils/diff" - - "github.com/sergi/go-diff/diffmatchpatch" -) - -// References returns a slice of Commits for the file at "path", starting from -// the commit provided that contains the file from the provided path. The last -// commit into the returned slice is the commit where the file was created. -// If the provided commit does not contains the specified path, a nil slice is -// returned. The commits are sorted in commit order, newer to older. -// -// Caveats: -// -// - Moves and copies are not currently supported. -// -// - Cherry-picks are not detected unless there are no commits between them and -// therefore can appear repeated in the list. (see git path-id for hints on how -// to fix this). -func references(c *object.Commit, path string) ([]*object.Commit, error) { - var result []*object.Commit - seen := make(map[plumbing.Hash]struct{}) - if err := walkGraph(&result, &seen, c, path); err != nil { - return nil, err - } - - // TODO result should be returned without ordering - sortCommits(result) - - // for merges of identical cherry-picks - return removeComp(path, result, equivalent) -} - -type commitSorterer struct { - l []*object.Commit -} - -func (s commitSorterer) Len() int { - return len(s.l) -} - -func (s commitSorterer) Less(i, j int) bool { - return s.l[i].Committer.When.Before(s.l[j].Committer.When) || - s.l[i].Committer.When.Equal(s.l[j].Committer.When) && - s.l[i].Author.When.Before(s.l[j].Author.When) -} - -func (s commitSorterer) Swap(i, j int) { - s.l[i], s.l[j] = s.l[j], s.l[i] -} - -// SortCommits sorts a commit list by commit date, from older to newer. -func sortCommits(l []*object.Commit) { - s := &commitSorterer{l} - sort.Sort(s) -} - -// Recursive traversal of the commit graph, generating a linear history of the -// path. -func walkGraph(result *[]*object.Commit, seen *map[plumbing.Hash]struct{}, current *object.Commit, path string) error { - // check and update seen - if _, ok := (*seen)[current.Hash]; ok { - return nil - } - (*seen)[current.Hash] = struct{}{} - - // if the path is not in the current commit, stop searching. - if _, err := current.File(path); err != nil { - return nil - } - - // optimization: don't traverse branches that does not - // contain the path. - parents, err := parentsContainingPath(path, current) - if err != nil { - return err - } - switch len(parents) { - // if the path is not found in any of its parents, the path was - // created by this commit; we must add it to the revisions list and - // stop searching. This includes the case when current is the - // initial commit. - case 0: - *result = append(*result, current) - return nil - case 1: // only one parent contains the path - // if the file contents has change, add the current commit - different, err := differentContents(path, current, parents) - if err != nil { - return err - } - if len(different) == 1 { - *result = append(*result, current) - } - // in any case, walk the parent - return walkGraph(result, seen, parents[0], path) - default: // more than one parent contains the path - // TODO: detect merges that had a conflict, because they must be - // included in the result here. - for _, p := range parents { - err := walkGraph(result, seen, p, path) - if err != nil { - return err - } - } - } - return nil -} - -func parentsContainingPath(path string, c *object.Commit) ([]*object.Commit, error) { - // TODO: benchmark this method making git.object.Commit.parent public instead of using - // an iterator - var result []*object.Commit - iter := c.Parents() - for { - parent, err := iter.Next() - if err == io.EOF { - return result, nil - } - if err != nil { - return nil, err - } - if _, err := parent.File(path); err == nil { - result = append(result, parent) - } - } -} - -// Returns an slice of the commits in "cs" that has the file "path", but with different -// contents than what can be found in "c". -func differentContents(path string, c *object.Commit, cs []*object.Commit) ([]*object.Commit, error) { - result := make([]*object.Commit, 0, len(cs)) - h, found := blobHash(path, c) - if !found { - return nil, object.ErrFileNotFound - } - for _, cx := range cs { - if hx, found := blobHash(path, cx); found && h != hx { - result = append(result, cx) - } - } - return result, nil -} - -// blobHash returns the hash of a path in a commit -func blobHash(path string, commit *object.Commit) (hash plumbing.Hash, found bool) { - file, err := commit.File(path) - if err != nil { - var empty plumbing.Hash - return empty, found - } - return file.Hash, true -} - -type contentsComparatorFn func(path string, a, b *object.Commit) (bool, error) - -// Returns a new slice of commits, with duplicates removed. Expects a -// sorted commit list. Duplication is defined according to "comp". It -// will always keep the first commit of a series of duplicated commits. -func removeComp(path string, cs []*object.Commit, comp contentsComparatorFn) ([]*object.Commit, error) { - result := make([]*object.Commit, 0, len(cs)) - if len(cs) == 0 { - return result, nil - } - result = append(result, cs[0]) - for i := 1; i < len(cs); i++ { - equals, err := comp(path, cs[i], cs[i-1]) - if err != nil { - return nil, err - } - if !equals { - result = append(result, cs[i]) - } - } - return result, nil -} - -// Equivalent commits are commits whose patch is the same. -func equivalent(path string, a, b *object.Commit) (bool, error) { - numParentsA := a.NumParents() - numParentsB := b.NumParents() - - // the first commit is not equivalent to anyone - // and "I think" merges can not be equivalent to anything - if numParentsA != 1 || numParentsB != 1 { - return false, nil - } - - diffsA, err := patch(a, path) - if err != nil { - return false, err - } - diffsB, err := patch(b, path) - if err != nil { - return false, err - } - - return sameDiffs(diffsA, diffsB), nil -} - -func patch(c *object.Commit, path string) ([]diffmatchpatch.Diff, error) { - // get contents of the file in the commit - file, err := c.File(path) - if err != nil { - return nil, err - } - content, err := file.Contents() - if err != nil { - return nil, err - } - - // get contents of the file in the first parent of the commit - var contentParent string - iter := c.Parents() - parent, err := iter.Next() - if err != nil { - return nil, err - } - file, err = parent.File(path) - if err != nil { - contentParent = "" - } else { - contentParent, err = file.Contents() - if err != nil { - return nil, err - } - } - - // compare the contents of parent and child - return diff.Do(content, contentParent), nil -} - -func sameDiffs(a, b []diffmatchpatch.Diff) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if !sameDiff(a[i], b[i]) { - return false - } - } - return true -} - -func sameDiff(a, b diffmatchpatch.Diff) bool { - if a.Type != b.Type { - return false - } - switch a.Type { - case 0: - return countLines(a.Text) == countLines(b.Text) - case 1, -1: - return a.Text == b.Text - default: - panic("unreachable") - } -} diff --git a/references_test.go b/references_test.go deleted file mode 100644 index 28d1bb9b7..000000000 --- a/references_test.go +++ /dev/null @@ -1,401 +0,0 @@ -package git - -import ( - "bytes" - "fmt" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/storage/memory" - - fixtures "github.com/go-git/go-git-fixtures/v4" - . "gopkg.in/check.v1" -) - -type ReferencesSuite struct { - BaseSuite -} - -var _ = Suite(&ReferencesSuite{}) - -var referencesTests = [...]struct { - // input data to revlist - repo string - commit string - path string - // expected output data form the revlist - revs []string -}{ - // Tyba git-fixture - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "binary.jpg", []string{ - "35e85108805c84807bc66a02d91535e1e24b38b9", - }}, - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "CHANGELOG", []string{ - "b8e471f58bcbca63b07bda20e428190409c2db47", - }}, - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "go/example.go", []string{ - "918c48b83bd081e863dbe1b80f8998f058cd8294", - }}, - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/long.json", []string{ - "af2d6a6954d532f8ffb47615169c8fdf9d383a1a", - }}, - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "json/short.json", []string{ - "af2d6a6954d532f8ffb47615169c8fdf9d383a1a", - }}, - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "LICENSE", []string{ - "b029517f6300c2da0f4b651b8642506cd6aaf45d", - }}, - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "php/crappy.php", []string{ - "918c48b83bd081e863dbe1b80f8998f058cd8294", - }}, - {"https://github.com/git-fixtures/basic.git", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", "vendor/foo.go", []string{ - "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", - }}, - {"https://github.com/jamesob/desk.git", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "LICENSE", []string{ - "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", - }}, - {"https://github.com/jamesob/desk.git", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "README.md", []string{ - "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", - "2e87a2dcc63a115f9a61bd969d1e85fb132a431b", - "215b0ac06225b0671bc3460d10da88c3406f796f", - "0260eb7a2623dd2309ab439f74e8681fccdc4285", - "d46b48933e94f30992486374fa9a6becfd28ea17", - "9cb4df2a88efee8836f9b8ad27ca2717f624164e", - "8c49acdec2ed441706d8799f8b17878aae4c1ffe", - "ebaca0c6f54c23193ee8175c3530e370cb2dabe3", - "77675f82039551a19de4fbccbe69366fe63680df", - "b9741594fb8ab7374f9be07d6a09a3bf96719816", - "04db6acd94de714ca48128c606b17ee1149a630e", - "ff737bd8a962a714a446d7592fae423a56e61e12", - "eadd03f7a1cc54810bd10eef6747ad9562ad246d", - "b5072ab5c1cf89191d71f1244eecc5d1f369ef7e", - "bfa6ebc9948f1939402b063c0a2a24bf2b1c1cc3", - "d9aef39828c670dfdb172502021a2ebcda8cf2fb", - "1a6b6e45c91e1831494eb139ee3f8e21649c7fb0", - "09fdbe4612066cf63ea46aee43c7cfaaff02ecfb", - "236f6526b1150cc1f1723566b4738f443fc70777", - "7862953f470b62397d22f6782a884f5bea6d760d", - "b0b0152d08c2333680266977a5bc9c4e50e1e968", - "13ce6c1c77c831f381974aa1c62008a414bd2b37", - "d3f3c8faca048d11709969fbfc0cdf2901b87578", - "8777dde1abe18c805d021366643218d3f3356dd9", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/reconfigure_spinnaker.py", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/validate_configuration.py", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", - "1e3d328a2cabda5d0aaddc5dec65271343e0dc37", - "b5d999e2986e190d81767cd3cfeda0260f9f6fb8", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/fetch.py", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pylib/spinnaker/yaml_util.py", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", - "b5d999e2986e190d81767cd3cfeda0260f9f6fb8", - "023d4fb17b76e0fe0764971df8b8538b735a1d67", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "dev/build_release.py", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", - "f42771ba298b93a7c4f5b16c5b30ab96c15305a8", - "dd52703a50e71891f63fcf05df1f69836f4e7056", - "0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", - "d375f1994ff4d0bdc32d614e698f1b50e1093f14", - "abad497f11a366548aa95303c8c2f165fe7ae918", - "6986d885626792dee4ef6b7474dfc9230c5bda54", - "5422a86a10a8c5a1ef6728f5fc8894d9a4c54cb9", - "09a4ea729b25714b6368959eea5113c99938f7b6", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "pkg_scripts/postUninstall.sh", []string{ - "ce9f123d790717599aaeb76bc62510de437761be", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/first_google_boot.sh", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - "de25f576b888569192e6442b0202d30ca7b2d8ec", - "a596972a661d9a7deca8abd18b52ce1a39516e89", - "9467ec579708b3c71dd9e5b3906772841c144a30", - "c4a9091e4076cb740fa46e790dd5b658e19012ad", - "6eb5d9c5225224bfe59c401182a2939d6c27fc00", - "495c7118e7cf757aa04eab410b64bfb5b5149ad2", - "dd2d03c19658ff96d371aef00e75e2e54702da0e", - "2a3b1d3b134e937c7bafdab6cc2950e264bf5dee", - "a57b08a9072f6a865f760551be2a4944f72f804a", - "0777fadf4ca6f458d7071de414f9bd5417911037", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.sh", []string{ - "0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_fake_openjdk8.sh", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/install_spinnaker.py", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - "37f94770d81232b1895fca447878f68d65aac652", - "46c9dcbb55ca3f4735e82ad006e8cae2fdd050d9", - "124a88cfda413cb7182ca9c739a284a9e50042a1", - "eb4faf67a8b775d7985d07a708e3ffeac4273580", - "0d9c9cef53af38cefcb6801bb492aaed3f2c9a42", - "01171a8a2e843bef3a574ba73b258ac29e5d5405", - "739d8c6fe16edcb6ef9185dc74197de561b84315", - "d33c2d1e350b03fb989eefc612e8c9d5fa7cadc2", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "install/__init__.py", []string{ - "a24001f6938d425d0e7504bdf5d27fc866a85c3d", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "experimental/docker-compose/docker-compose.yml", []string{ - "fda357835d889595dc39dfebc6181d863cce7d4f", - "57c59e7144354a76e1beba69ae2f85db6b1727af", - "7682dff881029c722d893a112a64fea6849a0428", - "66f1c938c380a4096674b27540086656076a597f", - "56dc238f6f397e93f1d1aad702976889c830e8bf", - "b95e442c064935709e789fa02126f17ddceef10b", - "f98965a8f42037bd038b86c3401da7e6dfbf4f2e", - "5344429749e8b68b168d2707b7903692436cc2ea", - "6a31f5d219766b0cec4ea4fbbbfe47bdcdb0ab8e", - "ddaae195b628150233b0a48f50a1674fd9d1a924", - "7119ad9cf7d4e4d8b059e5337374baae4adc7458", - }}, - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/validate_configuration_test.py", []string{ - "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", - "1e3d328a2cabda5d0aaddc5dec65271343e0dc37", - }}, - {"https://github.com/spinnaker/spinnaker.git", "f39d86f59a0781f130e8de6b2115329c1fbe9545", "README.adoc", []string{ - "638f61b3331695f46f1a88095e26dea0f09f176b", - "bd42370d3fe8d410e78acb96f81cb3d838ad1c21", - "d6905eab6fec1841c7cf8e4484499f5c8d7d423e", - "c0a70a0f5aa494f0ae01c55ba191f2325556489a", - "811795c8a185e88f5d269195cb68b29c8d0fe170", - "d6e6fe0194447cc280f942d6a2e0521b68ea7796", - "174bdbf9edfb0ca88415dd4a673852d5b22e7036", - "9944d6cf72b8f82d622d85dad7434472bc8f397d", - "e805183c72f0426fb073728c01901c2fd2db1da6", - "8ef83dd443a05e9122681950399edaa58a38d466", - "d73f9cee49a5ad27a42a6e18af7c49a8f28ad8a8", - }}, - // FAILS - /* - // this contains an empty move - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "google/dev/build_google_tarball.py", []string{ - "88e60ac93f832efc2616b3c165e99a8f2ffc3e0c", - "9e49443da49b8c862cc140b660744f84eebcfa51", - }}, - */ - /* - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/yaml_util_test.py", []string{ - "edf909edb9319c5e615e4ce73da47bbdca388ebe", - "023d4fb17b76e0fe0764971df8b8538b735a1d67", - }}, - */ - /* - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "unittest/configurator_test.py", []string{ - "1e14f94bcf82694fdc7e2dcbbfdbbed58db0f4d9", - "edf909edb9319c5e615e4ce73da47bbdca388ebe", - "d14f793a6cd7169ef708a4fc276ad876bd3edd4e", - "023d4fb17b76e0fe0764971df8b8538b735a1d67", - }}, - */ - /* - // this contains a cherry-pick at 094d0e7d5d691 (with 3f34438d) - {"https://github.com/jamesob/desk.git", "d4edaf0e8101fcea437ebd982d899fe2cc0f9f7b", "desk", []string{ - "ffcda27c2de6768ee83f3f4a027fa4ab57d50f09", - "a0c1e853158ccbaf95574220bbf3b54509034a9f", - "decfc524570c407d6bba0f217e534c8b47dbdbee", - "1413872d5b3af7cd674bbe0e1f23387cd5d940e6", - "40cd5a91d916e7b2f331e4e85fdc52636fd7cff7", - "8e07d73aa0e3780f8c7cf8ad1a6b263df26a0a52", - "19c56f95720ac3630efe9f29b1a252581d6cbc0c", - "9ea46ccc6d253cffb4b7b66e936987d87de136e4", - "094d0e7d5d69141c98a606910ba64786c5565da0", - "801e62706a9e4fef75fcaca9c78744de0bc36e6a", - "eddf335f31c73624ed3f40dc5fcad50136074b2b", - "c659093f06eb2bd68c6252caeab605e5cd8aa49e", - "d94b3fe8ce0e3a474874d742992d432cd040582f", - "93cddf036df2d8509f910063696acd556ca7600f", - "b3d4cb0c826b16b301f088581d681654d8de6c07", - "52d90f9b513dd3c5330663cba39396e6b8a3ba4e", - "15919e99ded03c6ceea9ff98558e77a322a4dadb", - "803bf37847633e2f685a46a27b11facf22efebec", - "c07ad524ee1e616c70bf2ea7a0ee4f4a01195d78", - "b91aff30f318fda461d009c308490613b394f3e2", - "67cec1e8a3f21c6eb11678e3f31ffd228b55b783", - "bbe404c78af7525fabc57b9e7aa7c100b0d39f7a", - "5dd078848786c2babc2511e9502fa98518cf3535", - "7970ae7cc165c5205945dfb704d67d53031f550a", - "33091ac904747747ff30f107d4d0f22fa872eccf", - "069f81cab12d185ba1b509be946c47897cd4fb1f", - "13ce6c1c77c831f381974aa1c62008a414bd2b37", - }}, - */ - /* - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "InstallSpinnaker.sh", []string{ - "ce9f123d790717599aaeb76bc62510de437761be", - "23673af3ad70b50bba7fdafadc2323302f5ba520", - "b7015a5d36990d69a054482556127b9c7404a24a", - "582da9622e3a72a19cd261a017276d72b5b0051a", - "0c5bb1e4392e751f884f3c57de5d4aee72c40031", - "c9c2a0ec03968ab17e8b16fdec9661eb1dbea173", - "a3cdf880826b4d9af42b93f4a2df29a91ab31d35", - "18526c447f5174d33c96aac6d6433318b0e2021c", - "2a6288be1c8ea160c443ca3cd0fe826ff2387d37", - "9e74d009894d73dd07773ea6b3bdd8323db980f7", - "d2f6214b625db706384b378a29cc4c22237db97a", - "202a9c720b3ba8106e022a0ad027ebe279040c78", - "791bcd1592828d9d5d16e83f3a825fb08b0ba22d", - "01e65d67eed8afcb67a6bdf1c962541f62b299c9", - "6328ee836affafc1b52127147b5ca07300ac78e6", - "3de4f77c105f700f50d9549d32b9a05a01b46c4b", - "8980daf661408a3faa1f22c225702a5c1d11d5c9", - "8eb116de9128c314ac8a6f5310ca500b8c74f5db", - "88e841aad37b71b78a8fb88bc75fe69499d527c7", - "370d61cdbc1f3c90db6759f1599ccbabd40ad6c1", - "505577dc87d300cf562dc4702a05a5615d90d855", - "b5c6053a46993b20d1b91e7b7206bffa54669ad7", - "ba486de7c025457963701114c683dcd4708e1dee", - "b41d7c0e5b20bbe7c8eb6606731a3ff68f4e3941", - "a47d0aaeda421f06df248ad65bd58230766bf118", - "495c7118e7cf757aa04eab410b64bfb5b5149ad2", - "46670eb6477c353d837dbaba3cf36c5f8b86f037", - "dd2d03c19658ff96d371aef00e75e2e54702da0e", - "4bbcad219ec55a465fb48ce236cb10ca52d43b1f", - "50d0556563599366f29cb286525780004fa5a317", - "9a06d3f20eabb254d0a1e2ff7735ef007ccd595e", - "d4b48a39aba7d3bd3e8abef2274a95b112d1ae73", - }}, - */ - /* - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/default-spinnaker-local.yml", []string{ - "ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", - "99534ecc895fe17a1d562bb3049d4168a04d0865", - "caf6d62e8285d4681514dd8027356fb019bc97ff", - "eaf7614cad81e8ab5c813dd4821129d0c04ea449", - "5a2a845bc08974a36d599a4a4b7e25be833823b0", - "41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", - "974b775a8978b120ff710cac93a21c7387b914c9", - "87e459a9a044b3109dfeb943cc82c627b61d84a6", - "5e09821cbd7d710405b61cab0a795c2982a71b9c", - "8cc2d4bdb0a15aafc7fe02cdcb03ab90c974cafa", - "3ce7b902a51bac2f10994f7d1f251b616c975e54", - "a596972a661d9a7deca8abd18b52ce1a39516e89", - "8980daf661408a3faa1f22c225702a5c1d11d5c9", - }}, - */ - /* - {"https://github.com/spinnaker/spinnaker.git", "b32b2aecae2cfca4840dd480f8082da206a538da", "config/spinnaker.yml", []string{ - "ae904e8d60228c21c47368f6a10f1cc9ca3aeebf", - "caf6d62e8285d4681514dd8027356fb019bc97ff", - "eaf7614cad81e8ab5c813dd4821129d0c04ea449", - "5a2a845bc08974a36d599a4a4b7e25be833823b0", - "41e96c54a478e5d09dd07ed7feb2d8d08d8c7e3c", - "974b775a8978b120ff710cac93a21c7387b914c9", - "ed887f6547d7cd2b2d741184a06f97a0a704152b", - "d4553dac205023fa77652308af1a2d1cf52138fb", - "a596972a661d9a7deca8abd18b52ce1a39516e89", - "66ac94f0b4442707fb6f695fbed91d62b3bd9d4a", - "079e42e7c979541b6fab7343838f7b9fd4a360cd", - }}, - */ -} - -func (s *ReferencesSuite) TestObjectNotFoundError(c *C) { - h1 := plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a") - hParent := plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea") - - url := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One().DotGit().Root() - storer := memory.NewStorage() - r, err := Clone(storer, nil, &CloneOptions{ - URL: url, - }) - c.Assert(err, IsNil) - - delete(storer.Objects, hParent) - - commit, err := r.CommitObject(h1) - c.Assert(err, IsNil) - - _, err = references(commit, "LICENSE") - c.Assert(err, Equals, plumbing.ErrObjectNotFound) -} - -func (s *ReferencesSuite) TestRevList(c *C) { - for _, t := range referencesTests { - r := s.NewRepositoryFromPackfile(fixtures.ByURL(t.repo).One()) - - commit, err := r.CommitObject(plumbing.NewHash(t.commit)) - c.Assert(err, IsNil) - - revs, err := references(commit, t.path) - c.Assert(err, IsNil) - c.Assert(len(revs), Equals, len(t.revs)) - - for i := range revs { - if revs[i].Hash.String() != t.revs[i] { - commit, err := s.Repository.CommitObject(plumbing.NewHash(t.revs[i])) - c.Assert(err, IsNil) - equiv, err := equivalent(t.path, revs[i], commit) - c.Assert(err, IsNil) - if equiv { - fmt.Printf("cherry-pick detected: %s %s\n", revs[i].Hash.String(), t.revs[i]) - } else { - c.Fatalf("\nrepo=%s, commit=%s, path=%s, \n%s", - t.repo, t.commit, t.path, compareSideBySide(t.revs, revs)) - } - } - } - } -} - -// same length is assumed -func compareSideBySide(a []string, b []*object.Commit) string { - var buf bytes.Buffer - buf.WriteString("\t EXPECTED OBTAINED ") - var sep string - var obt string - for i := range a { - obt = b[i].Hash.String() - if a[i] != obt { - sep = "------" - } else { - sep = " " - } - buf.WriteString(fmt.Sprintf("\n%d", i+1)) - buf.WriteString(sep) - buf.WriteString(a[i]) - buf.WriteString(sep) - buf.WriteString(obt) - } - return buf.String() -} - -var cherryPicks = [...][]string{ - // repo, path, commit a, commit b - {"https://github.com/jamesob/desk.git", "desk", "094d0e7d5d69141c98a606910ba64786c5565da0", "3f34438d54f4a1ca86db8c0f03ed8eb38f20e22c"}, -} - -// should detect cherry picks -func (s *ReferencesSuite) TestEquivalent(c *C) { - for _, t := range cherryPicks { - cs := s.commits(c, t[0], t[2], t[3]) - equiv, err := equivalent(t[1], cs[0], cs[1]) - c.Assert(err, IsNil) - c.Assert(equiv, Equals, true, Commentf("repo=%s, file=%s, a=%s b=%s", t[0], t[1], t[2], t[3])) - } -} - -// returns the commits from a slice of hashes -func (s *ReferencesSuite) commits(c *C, repo string, hs ...string) []*object.Commit { - r := s.NewRepositoryFromPackfile(fixtures.ByURL(repo).One()) - - result := make([]*object.Commit, 0, len(hs)) - for _, h := range hs { - commit, err := r.CommitObject(plumbing.NewHash(h)) - c.Assert(err, IsNil) - - result = append(result, commit) - } - - return result -} From 252b5d240274e99eec8a63e4906b54f097c5f693 Mon Sep 17 00:00:00 2001 From: ricci2511 Date: Thu, 6 Jul 2023 18:18:38 +0200 Subject: [PATCH 137/205] *: Handle paths starting with tilde --- repository.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/repository.go b/repository.go index dd822a542..168303f0b 100644 --- a/repository.go +++ b/repository.go @@ -322,8 +322,16 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) } func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) { - if path, err = filepath.Abs(path); err != nil { - return nil, nil, err + if strings.HasPrefix(path, "~/") { + home, err := os.UserHomeDir() + if err != nil { + return nil, nil, err + } + path = filepath.Join(home, path[2:]) + } else { + if path, err = filepath.Abs(path); err != nil { + return nil, nil, err + } } var fs billy.Filesystem From a9a658ee4ecb0c2f0fcb1962b7c6a408a8116309 Mon Sep 17 00:00:00 2001 From: ricci2511 Date: Fri, 7 Jul 2023 14:44:54 +0200 Subject: [PATCH 138/205] *: Add test for paths starting with tilde --- common_test.go | 20 ++++++++++++++++++++ repository_test.go | 15 +++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/common_test.go b/common_test.go index 7f9b84b40..ff4d6b813 100644 --- a/common_test.go +++ b/common_test.go @@ -151,6 +151,26 @@ func (s *BaseSuite) TemporalDir() (path string, clean func()) { return } +func (s *BaseSuite) TemporalHomeDir() (path string, clean func()) { + home, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + fs := osfs.New(home) + relPath, err := util.TempDir(fs, "", "") + if err != nil { + panic(err) + } + + path = fs.Join(fs.Root(), relPath) + clean = func() { + _ = util.RemoveAll(fs, relPath) + } + + return +} + func (s *BaseSuite) TemporalFilesystem() (fs billy.Filesystem, clean func()) { fs = osfs.New(os.TempDir()) path, err := util.TempDir(fs, "", "") diff --git a/repository_test.go b/repository_test.go index d18816f42..50384e788 100644 --- a/repository_test.go +++ b/repository_test.go @@ -543,6 +543,21 @@ func (s *RepositorySuite) TestPlainOpen(c *C) { c.Assert(r, NotNil) } +func (s *RepositorySuite) TestPlainOpenTildePath(c *C) { + dir, clean := s.TemporalHomeDir() + defer clean() + + r, err := PlainInit(dir, false) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + path := strings.Replace(dir, strings.Split(dir, ".tmp")[0], "~/", 1) + + r, err = PlainOpen(path) + c.Assert(err, IsNil) + c.Assert(r, NotNil) +} + func (s *RepositorySuite) TestPlainOpenBare(c *C) { dir, clean := s.TemporalDir() defer clean() From 5dad9b23030e344a4fd1458df0c50e6ada55a01a Mon Sep 17 00:00:00 2001 From: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:08:04 +1000 Subject: [PATCH 139/205] *: Handle paths starting with ~username Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com> --- internal/path_util/path_util.go | 29 +++++++++++++++++++++++++++++ plumbing/format/gitignore/dir.go | 18 ++---------------- repository.go | 18 ++++++++---------- repository_test.go | 17 +++++++++++++---- 4 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 internal/path_util/path_util.go diff --git a/internal/path_util/path_util.go b/internal/path_util/path_util.go new file mode 100644 index 000000000..48e4a3d0e --- /dev/null +++ b/internal/path_util/path_util.go @@ -0,0 +1,29 @@ +package path_util + +import ( + "os" + "os/user" + "strings" +) + +func ReplaceTildeWithHome(path string) (string, error) { + if strings.HasPrefix(path, "~") { + firstSlash := strings.Index(path, "/") + if firstSlash == 1 { + home, err := os.UserHomeDir() + if err != nil { + return path, err + } + return strings.Replace(path, "~", home, 1), nil + } else if firstSlash > 1 { + username := path[1:firstSlash] + userAccount, err := user.Lookup(username) + if err != nil { + return path, err + } + return strings.Replace(path, path[:firstSlash], userAccount.HomeDir, 1), nil + } + } + + return path, nil +} diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go index 3c4469a0e..d8fb30c16 100644 --- a/plumbing/format/gitignore/dir.go +++ b/plumbing/format/gitignore/dir.go @@ -5,10 +5,10 @@ import ( "bytes" "io" "os" - "os/user" "strings" "github.com/go-git/go-billy/v5" + "github.com/go-git/go-git/v5/internal/path_util" "github.com/go-git/go-git/v5/plumbing/format/config" gioutil "github.com/go-git/go-git/v5/utils/ioutil" ) @@ -27,21 +27,7 @@ const ( // readIgnoreFile reads a specific git ignore file. func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) { - if strings.HasPrefix(ignoreFile, "~") { - firstSlash := strings.Index(ignoreFile, "/") - if firstSlash == 1 { - home, err := os.UserHomeDir() - if err == nil { - ignoreFile = strings.Replace(ignoreFile, "~", home, 1) - } - } else if firstSlash > 1 { - username := ignoreFile[1:firstSlash] - userAccount, err := user.Lookup(username) - if err == nil { - ignoreFile = strings.Replace(ignoreFile, ignoreFile[:firstSlash], userAccount.HomeDir, 1) - } - } - } + ignoreFile, _ = path_util.ReplaceTildeWithHome(ignoreFile) f, err := fs.Open(fs.Join(append(path, ignoreFile)...)) if err == nil { diff --git a/repository.go b/repository.go index 168303f0b..02edb66cd 100644 --- a/repository.go +++ b/repository.go @@ -19,6 +19,7 @@ import ( "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-billy/v5/util" "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/internal/path_util" "github.com/go-git/go-git/v5/internal/revision" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" @@ -322,16 +323,13 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) } func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) { - if strings.HasPrefix(path, "~/") { - home, err := os.UserHomeDir() - if err != nil { - return nil, nil, err - } - path = filepath.Join(home, path[2:]) - } else { - if path, err = filepath.Abs(path); err != nil { - return nil, nil, err - } + path, err = path_util.ReplaceTildeWithHome(path) + if err != nil { + return nil, nil, err + } + + if path, err = filepath.Abs(path); err != nil { + return nil, nil, err } var fs billy.Filesystem diff --git a/repository_test.go b/repository_test.go index 50384e788..9e000a3da 100644 --- a/repository_test.go +++ b/repository_test.go @@ -8,6 +8,7 @@ import ( "io" "os" "os/exec" + "os/user" "path/filepath" "regexp" "strings" @@ -551,11 +552,19 @@ func (s *RepositorySuite) TestPlainOpenTildePath(c *C) { c.Assert(err, IsNil) c.Assert(r, NotNil) - path := strings.Replace(dir, strings.Split(dir, ".tmp")[0], "~/", 1) - - r, err = PlainOpen(path) + currentUser, err := user.Current() c.Assert(err, IsNil) - c.Assert(r, NotNil) + // remove domain for windows + username := currentUser.Username[strings.Index(currentUser.Username, "\\")+1:] + + homes := []string{"~/", "~" + username + "/"} + for _, home := range homes { + path := strings.Replace(dir, strings.Split(dir, ".tmp")[0], home, 1) + + r, err = PlainOpen(path) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + } } func (s *RepositorySuite) TestPlainOpenBare(c *C) { From 939ad4a432f58d2a301db9bae1cf4143bfd659ea Mon Sep 17 00:00:00 2001 From: Savely Krasovsky Date: Sat, 15 Jul 2023 17:31:05 +0300 Subject: [PATCH 140/205] storage: filesystem/dotgit, add support for tmp_objdir prefix --- storage/filesystem/dotgit/dotgit.go | 4 +++- storage/filesystem/dotgit/dotgit_test.go | 25 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go index 19d702632..e02e6ddfd 100644 --- a/storage/filesystem/dotgit/dotgit.go +++ b/storage/filesystem/dotgit/dotgit.go @@ -582,7 +582,9 @@ func (d *DotGit) hasIncomingObjects() bool { directoryContents, err := d.fs.ReadDir(objectsPath) if err == nil { for _, file := range directoryContents { - if strings.HasPrefix(file.Name(), "incoming-") && file.IsDir() { + if file.IsDir() && (strings.HasPrefix(file.Name(), "tmp_objdir-incoming-") || + // Before Git 2.35 incoming commits directory had another prefix + strings.HasPrefix(file.Name(), "incoming-")) { d.incomingDirName = file.Name() } } diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go index 6a339d1e5..1b6c11351 100644 --- a/storage/filesystem/dotgit/dotgit_test.go +++ b/storage/filesystem/dotgit/dotgit_test.go @@ -640,6 +640,27 @@ func (s *SuiteDotGit) TestObject(c *C) { fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit() dir := New(fs) + hash := plumbing.NewHash("03db8e1fbe133a480f2867aac478fd866686d69e") + file, err := dir.Object(hash) + c.Assert(err, IsNil) + c.Assert(strings.HasSuffix( + file.Name(), fs.Join("objects", "03", "db8e1fbe133a480f2867aac478fd866686d69e")), + Equals, true, + ) + incomingHash := "9d25e0f9bde9f82882b49fe29117b9411cb157b7" //made up hash + incomingDirPath := fs.Join("objects", "tmp_objdir-incoming-123456") + incomingFilePath := fs.Join(incomingDirPath, incomingHash[0:2], incomingHash[2:40]) + fs.MkdirAll(incomingDirPath, os.FileMode(0755)) + fs.Create(incomingFilePath) + + _, err = dir.Object(plumbing.NewHash(incomingHash)) + c.Assert(err, IsNil) +} + +func (s *SuiteDotGit) TestPreGit235Object(c *C) { + fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit() + dir := New(fs) + hash := plumbing.NewHash("03db8e1fbe133a480f2867aac478fd866686d69e") file, err := dir.Object(hash) c.Assert(err, IsNil) @@ -665,7 +686,7 @@ func (s *SuiteDotGit) TestObjectStat(c *C) { _, err := dir.ObjectStat(hash) c.Assert(err, IsNil) incomingHash := "9d25e0f9bde9f82882b49fe29117b9411cb157b7" //made up hash - incomingDirPath := fs.Join("objects", "incoming-123456") + incomingDirPath := fs.Join("objects", "tmp_objdir-incoming-123456") incomingFilePath := fs.Join(incomingDirPath, incomingHash[0:2], incomingHash[2:40]) fs.MkdirAll(incomingDirPath, os.FileMode(0755)) fs.Create(incomingFilePath) @@ -683,7 +704,7 @@ func (s *SuiteDotGit) TestObjectDelete(c *C) { c.Assert(err, IsNil) incomingHash := "9d25e0f9bde9f82882b49fe29117b9411cb157b7" //made up hash - incomingDirPath := fs.Join("objects", "incoming-123456") + incomingDirPath := fs.Join("objects", "tmp_objdir-incoming-123456") incomingSubDirPath := fs.Join(incomingDirPath, incomingHash[0:2]) incomingFilePath := fs.Join(incomingSubDirPath, incomingHash[2:40]) From fe5ec4028b288fc064e7cc98e3c7ae70ba66fb50 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 20 Jul 2023 21:27:47 +0100 Subject: [PATCH 141/205] *: Fix broken CI apt-get is struggling to find libcurl4-openssl-dev out of the box. 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 638f9bdfd..60cfa1235 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v3 - name: Install build dependencies - run: sudo apt-get install gettext libcurl4-openssl-dev + run: sudo apt-get update && sudo apt-get install gettext libcurl4-openssl-dev - name: Git Build run: make build-git From efe3292fb1c0b275872ee1e40ccd63d4946083af Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 20 Jul 2023 21:47:49 +0100 Subject: [PATCH 142/205] *: Bump dependencies - dario.cat/mergo v1.0.0 - github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 - github.com/skeema/knownhosts v1.2.0 - golang.org/x/crypto v0.11.0 - golang.org/x/net v0.12.0 - golang.org/x/sys v0.10.0 - golang.org/x/text v0.11.0 Signed-off-by: Paulo Gomes --- go.mod | 21 ++++++++++++--------- go.sum | 51 +++++++++++++++++++++++++++++++-------------------- repository.go | 2 +- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index ee58848c8..ea4714528 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ module github.com/go-git/go-git/v5 go 1.18 require ( - github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 + dario.cat/mergo v1.0.0 + github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 @@ -15,26 +16,28 @@ require ( github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/go-cmp v0.5.9 - github.com/imdario/mergo v0.3.15 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jessevdk/go-flags v1.5.0 github.com/kevinburke/ssh_config v1.2.0 github.com/pjbgf/sha1cd v0.3.0 github.com/sergi/go-diff v1.1.0 - github.com/skeema/knownhosts v1.1.1 + github.com/skeema/knownhosts v1.2.0 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.9.0 - golang.org/x/net v0.10.0 - golang.org/x/sys v0.8.0 - golang.org/x/text v0.9.0 + golang.org/x/crypto v0.11.0 + golang.org/x/net v0.12.0 + golang.org/x/sys v0.10.0 + golang.org/x/text v0.11.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) require ( - github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/cloudflare/circl v1.3.3 // indirect - github.com/kr/pretty v0.2.1 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/tools v0.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index fcfc9f5b0..375a22a58 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,17 @@ -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 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/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -35,8 +37,6 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 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= @@ -44,8 +44,9 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c 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= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -55,16 +56,19 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= -github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= +github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= 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= @@ -77,22 +81,26 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/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/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/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= @@ -102,34 +110,37 @@ 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-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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.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/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= 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/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/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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= diff --git a/repository.go b/repository.go index 02edb66cd..3154ac019 100644 --- a/repository.go +++ b/repository.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "dario.cat/mergo" "github.com/ProtonMail/go-crypto/openpgp" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/osfs" @@ -32,7 +33,6 @@ import ( "github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/filesystem/dotgit" "github.com/go-git/go-git/v5/utils/ioutil" - "github.com/imdario/mergo" ) // GitDirName this is a special folder where all the git stuff is. From a105da84747df637fa7913c3ab880be73019e502 Mon Sep 17 00:00:00 2001 From: merlin Date: Wed, 26 Jul 2023 12:15:52 +0300 Subject: [PATCH 143/205] plumbing: transport, handle IPv6 while parsing endpoint. Fixes #740 --- plumbing/transport/common.go | 8 +++++++- plumbing/transport/common_test.go | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index 89bd3d528..c6a054a65 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -227,11 +227,17 @@ func parseURL(endpoint string) (*Endpoint, error) { pass, _ = u.User.Password() } + host := u.Hostname() + if strings.Contains(host, ":") { + // IPv6 address + host = "[" + host + "]" + } + return &Endpoint{ Protocol: u.Scheme, User: user, Password: pass, - Host: u.Hostname(), + Host: host, Port: getPort(u), Path: getPath(u), }, nil diff --git a/plumbing/transport/common_test.go b/plumbing/transport/common_test.go index db11303a4..d9f12ab18 100644 --- a/plumbing/transport/common_test.go +++ b/plumbing/transport/common_test.go @@ -198,3 +198,15 @@ func (s *SuiteCommon) TestFilterUnsupportedCapabilities(c *C) { FilterUnsupportedCapabilities(l) c.Assert(l.Supports(capability.MultiACK), Equals, false) } + +func (s *SuiteCommon) TestNewEndpointIPv6(c *C) { + // see issue https://github.com/go-git/go-git/issues/740 + // + // IPv6 host names are not being properly handled, which results in unhelpful + // error messages depending on the format used. + // + e, err := NewEndpoint("http://[::1]:8080/foo.git") + c.Assert(err, IsNil) + c.Assert(e.Host, Equals, "[::1]") + c.Assert(e.String(), Equals, "http://[::1]:8080/foo.git") +} From f71a44910a6ff0353bb5584083f03bdd2b0aa395 Mon Sep 17 00:00:00 2001 From: Siddhesh Ghadi Date: Tue, 29 Aug 2023 15:06:45 +0530 Subject: [PATCH 144/205] *: Bump goproxy dep. Fixes #826 CVE-2023-37788 is patched in goproxy v0.0.0-20230731152917-f99041a5c027 Signed-off-by: Siddhesh Ghadi --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ea4714528..b92e1d67a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 + github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 github.com/emirpasic/gods v1.18.1 github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 diff --git a/go.sum b/go.sum index 375a22a58..2c9cb40ce 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE= +github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= From 753b0d5cef6d8ef27c35de884b3c1673a33d1916 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 28 Jul 2023 11:08:55 +0200 Subject: [PATCH 145/205] git: worktree, reset ignored files that are part of the worktree: Fixes #819 --- worktree.go | 4 ++-- worktree_status.go | 9 +++++--- worktree_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/worktree.go b/worktree.go index 595dceaae..f9c01af28 100644 --- a/worktree.go +++ b/worktree.go @@ -368,7 +368,7 @@ func (w *Worktree) resetIndex(t *object.Tree, dirs []string) error { } func (w *Worktree) resetWorktree(t *object.Tree) error { - changes, err := w.diffStagingWithWorktree(true) + changes, err := w.diffStagingWithWorktree(true, false) if err != nil { return err } @@ -420,7 +420,7 @@ func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *ind } func (w *Worktree) containsUnstagedChanges() (bool, error) { - ch, err := w.diffStagingWithWorktree(false) + ch, err := w.diffStagingWithWorktree(false, true) if err != nil { return false, err } diff --git a/worktree_status.go b/worktree_status.go index 61bb6f759..730108754 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -74,7 +74,7 @@ func (w *Worktree) status(commit plumbing.Hash) (Status, error) { } } - right, err := w.diffStagingWithWorktree(false) + right, err := w.diffStagingWithWorktree(false, true) if err != nil { return nil, err } @@ -113,7 +113,7 @@ func nameFromAction(ch *merkletrie.Change) string { return name } -func (w *Worktree) diffStagingWithWorktree(reverse bool) (merkletrie.Changes, error) { +func (w *Worktree) diffStagingWithWorktree(reverse, excludeIgnoredChanges bool) (merkletrie.Changes, error) { idx, err := w.r.Storer.Index() if err != nil { return nil, err @@ -138,7 +138,10 @@ func (w *Worktree) diffStagingWithWorktree(reverse bool) (merkletrie.Changes, er return nil, err } - return w.excludeIgnoredChanges(c), nil + if excludeIgnoredChanges { + return w.excludeIgnoredChanges(c), nil + } + return c, nil } func (w *Worktree) excludeIgnoredChanges(changes merkletrie.Changes) merkletrie.Changes { diff --git a/worktree_test.go b/worktree_test.go index 24d5bd50f..c69c61717 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -29,6 +29,10 @@ import ( . "gopkg.in/check.v1" ) +var ( + defaultTestCommitOptions = &CommitOptions{Author: &object.Signature{Name: "testuser", Email: "testemail"}} +) + type WorktreeSuite struct { BaseSuite } @@ -884,14 +888,15 @@ func (s *WorktreeSuite) TestStatusCheckedInBeforeIgnored(c *C) { c.Assert(err, IsNil) _, err = w.Add("fileToIgnore") c.Assert(err, IsNil) - _, err = w.Commit("Added file that will be ignored later", &CommitOptions{}) + + _, err = w.Commit("Added file that will be ignored later", defaultTestCommitOptions) c.Assert(err, IsNil) err = util.WriteFile(fs, ".gitignore", []byte("fileToIgnore\nsecondIgnoredFile"), 0755) c.Assert(err, IsNil) _, err = w.Add(".gitignore") c.Assert(err, IsNil) - _, err = w.Commit("Added .gitignore", &CommitOptions{}) + _, err = w.Commit("Added .gitignore", defaultTestCommitOptions) c.Assert(err, IsNil) status, err := w.Status() c.Assert(err, IsNil) @@ -1097,6 +1102,49 @@ func (s *WorktreeSuite) TestResetHard(c *C) { c.Assert(branch.Hash(), Equals, commit) } +func (s *WorktreeSuite) TestResetHardWithGitIgnore(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{}) + c.Assert(err, IsNil) + + tf, err := fs.Create("newTestFile.txt") + c.Assert(err, IsNil) + _, err = tf.Write([]byte("testfile content")) + c.Assert(err, IsNil) + err = tf.Close() + c.Assert(err, IsNil) + _, err = w.Add("newTestFile.txt") + c.Assert(err, IsNil) + _, err = w.Commit("testcommit", &CommitOptions{Author: &object.Signature{Name: "name", Email: "email"}}) + c.Assert(err, IsNil) + + err = fs.Remove("newTestFile.txt") + c.Assert(err, IsNil) + f, err := fs.Create(".gitignore") + c.Assert(err, IsNil) + _, err = f.Write([]byte("foo\n")) + _, err = f.Write([]byte("newTestFile.txt\n")) + c.Assert(err, IsNil) + err = f.Close() + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, false) + + err = w.Reset(&ResetOptions{Mode: HardReset}) + c.Assert(err, IsNil) + + status, err = w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) +} + func (s *WorktreeSuite) TestStatusAfterCheckout(c *C) { fs := memfs.New() w := &Worktree{ From 5ad72db3bd60351da9ab42727952654e3ad5bcb2 Mon Sep 17 00:00:00 2001 From: "matej.risek" Date: Wed, 12 Jul 2023 14:40:09 +0200 Subject: [PATCH 146/205] plumbing: Do not swallow http message coming from VCS providers. For diagnostics reasons we want to surface error messages coming from VCS providers. That's why we introduce the reason field to Err struct in http package. This field can be used by an end user of the library in order to better understand failures. --- plumbing/transport/http/common.go | 18 ++++++++++++++++-- plumbing/transport/http/common_test.go | 19 +++++++++++++++++++ plumbing/transport/http/receive_pack.go | 1 - plumbing/transport/http/upload_pack.go | 1 - 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go index a7cdc1e16..54126febf 100644 --- a/plumbing/transport/http/common.go +++ b/plumbing/transport/http/common.go @@ -406,14 +406,28 @@ func (a *TokenAuth) String() string { // Err is a dedicated error to return errors based on status code type Err struct { Response *http.Response + Reason string } -// NewErr returns a new Err based on a http response +// NewErr returns a new Err based on a http response and closes response body +// if needed func NewErr(r *http.Response) error { if r.StatusCode >= http.StatusOK && r.StatusCode < http.StatusMultipleChoices { return nil } + var reason string + + // If a response message is present, add it to error + var messageBuffer bytes.Buffer + if r.Body != nil { + messageLength, _ := messageBuffer.ReadFrom(r.Body) + if messageLength > 0 { + reason = messageBuffer.String() + } + _ = r.Body.Close() + } + switch r.StatusCode { case http.StatusUnauthorized: return transport.ErrAuthenticationRequired @@ -423,7 +437,7 @@ func NewErr(r *http.Response) error { return transport.ErrRepositoryNotFound } - return plumbing.NewUnexpectedError(&Err{r}) + return plumbing.NewUnexpectedError(&Err{r, reason}) } // StatusCode returns the status code of the response diff --git a/plumbing/transport/http/common_test.go b/plumbing/transport/http/common_test.go index 151722876..6bd018bb4 100644 --- a/plumbing/transport/http/common_test.go +++ b/plumbing/transport/http/common_test.go @@ -3,6 +3,7 @@ package http import ( "crypto/tls" "fmt" + "io" "log" "net" "net/http" @@ -14,6 +15,7 @@ import ( "strings" "testing" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/transport" fixtures "github.com/go-git/go-git-fixtures/v4" @@ -90,6 +92,23 @@ func (s *ClientSuite) TestNewHTTPError40x(c *C) { "unexpected client error.*") } +func (s *ClientSuite) TestNewUnexpectedError(c *C) { + res := &http.Response{ + StatusCode: 500, + Body: io.NopCloser(strings.NewReader("Unexpected error")), + } + + err := NewErr(res) + c.Assert(err, NotNil) + c.Assert(err, FitsTypeOf, &plumbing.UnexpectedError{}) + + unexpectedError, _ := err.(*plumbing.UnexpectedError) + c.Assert(unexpectedError.Err, FitsTypeOf, &Err{}) + + httpError, _ := unexpectedError.Err.(*Err) + c.Assert(httpError.Reason, Equals, "Unexpected error") +} + func (s *ClientSuite) Test_newSession(c *C) { cl := NewClientWithOptions(nil, &ClientOptions{ CacheMaxEntries: 2, diff --git a/plumbing/transport/http/receive_pack.go b/plumbing/transport/http/receive_pack.go index 4387ecffe..3e736cd95 100644 --- a/plumbing/transport/http/receive_pack.go +++ b/plumbing/transport/http/receive_pack.go @@ -102,7 +102,6 @@ func (s *rpSession) doRequest( } if err := NewErr(res); err != nil { - _ = res.Body.Close() return nil, err } diff --git a/plumbing/transport/http/upload_pack.go b/plumbing/transport/http/upload_pack.go index 4f851459f..3432618ab 100644 --- a/plumbing/transport/http/upload_pack.go +++ b/plumbing/transport/http/upload_pack.go @@ -100,7 +100,6 @@ func (s *upSession) doRequest( } if err := NewErr(res); err != nil { - _ = res.Body.Close() return nil, err } From 32975ee2c72d60121c37aa969d70b3b9953cd76a Mon Sep 17 00:00:00 2001 From: Zhizhen He Date: Fri, 8 Sep 2023 09:30:43 +0800 Subject: [PATCH 147/205] *: fix some typos Signed-off-by: Zhizhen He --- _examples/sha256/main.go | 2 +- config/config.go | 12 +- plumbing/filemode/filemode.go | 2 +- plumbing/hash/hash.go | 2 +- plumbing/object.go | 2 +- plumbing/object/commitgraph/commitnode.go | 196 ++++++------ .../object/commitgraph/commitnode_test.go | 296 +++++++++--------- plumbing/protocol/packp/ulreq_decode.go | 2 +- plumbing/protocol/packp/ulreq_decode_test.go | 2 +- plumbing/transport/common.go | 2 +- remote.go | 6 +- remote_test.go | 2 +- utils/binary/read.go | 2 +- utils/merkletrie/difftree.go | 2 +- utils/merkletrie/internal/fsnoder/file.go | 2 +- worktree_test.go | 2 +- 16 files changed, 267 insertions(+), 267 deletions(-) diff --git a/_examples/sha256/main.go b/_examples/sha256/main.go index e1772d274..03927724d 100644 --- a/_examples/sha256/main.go +++ b/_examples/sha256/main.go @@ -15,7 +15,7 @@ import ( // This example requires building with the sha256 tag for it to work: // go run -tags sha256 main.go /tmp/repository -// Basic example of how to initialise a repository using sha256 as the hashing algorithmn. +// Basic example of how to initialise a repository using sha256 as the hashing algorithm. func main() { CheckArgs("") directory := os.Args[1] diff --git a/config/config.go b/config/config.go index 82af12d28..da425a784 100644 --- a/config/config.go +++ b/config/config.go @@ -63,9 +63,9 @@ type Config struct { } User struct { - // Name is the personal name of the author and the commiter of a commit. + // Name is the personal name of the author and the committer of a commit. Name string - // Email is the email of the author and the commiter of a commit. + // Email is the email of the author and the committer of a commit. Email string } @@ -77,9 +77,9 @@ type Config struct { } Committer struct { - // Name is the personal name of the commiter of a commit. + // Name is the personal name of the committer of a commit. Name string - // Email is the email of the the commiter of a commit. + // Email is the email of the committer of a commit. Email string } @@ -157,8 +157,8 @@ func ReadConfig(r io.Reader) (*Config, error) { } // LoadConfig loads a config file from a given scope. The returned Config, -// contains exclusively information fom the given scope. If couldn't find a -// config file to the given scope, a empty one is returned. +// contains exclusively information from the given scope. If it couldn't find a +// config file to the given scope, an 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") diff --git a/plumbing/filemode/filemode.go b/plumbing/filemode/filemode.go index b848a9796..ea1a45755 100644 --- a/plumbing/filemode/filemode.go +++ b/plumbing/filemode/filemode.go @@ -133,7 +133,7 @@ func (m FileMode) IsMalformed() bool { m != Submodule } -// String returns the FileMode as a string in the standatd git format, +// String returns the FileMode as a string in the standard git format, // this is, an octal number padded with ceros to 7 digits. Malformed // modes are printed in that same format, for easier debugging. // diff --git a/plumbing/hash/hash.go b/plumbing/hash/hash.go index 82d185616..8609848f6 100644 --- a/plumbing/hash/hash.go +++ b/plumbing/hash/hash.go @@ -24,7 +24,7 @@ func reset() { algos[crypto.SHA256] = crypto.SHA256.New } -// RegisterHash allows for the hash algorithm used to be overriden. +// RegisterHash allows for the hash algorithm used to be overridden. // 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 { diff --git a/plumbing/object.go b/plumbing/object.go index 2655dee43..3ee9de9f3 100644 --- a/plumbing/object.go +++ b/plumbing/object.go @@ -82,7 +82,7 @@ func (t ObjectType) Valid() bool { return t >= CommitObject && t <= REFDeltaObject } -// IsDelta returns true for any ObjectTyoe that represents a delta (i.e. +// IsDelta returns true for any ObjectType that represents a delta (i.e. // REFDeltaObject or OFSDeltaObject). func (t ObjectType) IsDelta() bool { return t == REFDeltaObject || t == OFSDeltaObject diff --git a/plumbing/object/commitgraph/commitnode.go b/plumbing/object/commitgraph/commitnode.go index 7abc58b80..d92c9064f 100644 --- a/plumbing/object/commitgraph/commitnode.go +++ b/plumbing/object/commitgraph/commitnode.go @@ -1,98 +1,98 @@ -package commitgraph - -import ( - "io" - "time" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/plumbing/storer" -) - -// CommitNode is generic interface encapsulating a lightweight commit object retrieved -// from CommitNodeIndex -type CommitNode interface { - // ID returns the Commit object id referenced by the commit graph node. - ID() plumbing.Hash - // Tree returns the Tree referenced by the commit graph node. - Tree() (*object.Tree, error) - // CommitTime returns the Commiter.When time of the Commit referenced by the commit graph node. - CommitTime() time.Time - // NumParents returns the number of parents in a commit. - NumParents() int - // ParentNodes return a CommitNodeIter for parents of specified node. - ParentNodes() CommitNodeIter - // ParentNode returns the ith parent of a commit. - ParentNode(i int) (CommitNode, error) - // ParentHashes returns hashes of the parent commits for a specified node - ParentHashes() []plumbing.Hash - // Generation returns the generation of the commit for reachability analysis. - // Objects with newer generation are not reachable from objects of older generation. - Generation() uint64 - // Commit returns the full commit object from the node - Commit() (*object.Commit, error) -} - -// CommitNodeIndex is generic interface encapsulating an index of CommitNode objects -type CommitNodeIndex interface { - // Get returns a commit node from a commit hash - Get(hash plumbing.Hash) (CommitNode, error) -} - -// CommitNodeIter is a generic closable interface for iterating over commit nodes. -type CommitNodeIter interface { - Next() (CommitNode, error) - ForEach(func(CommitNode) error) error - Close() -} - -// parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex. -type parentCommitNodeIter struct { - node CommitNode - i int -} - -func newParentgraphCommitNodeIter(node CommitNode) CommitNodeIter { - return &parentCommitNodeIter{node, 0} -} - -// Next moves the iterator to the next commit and returns a pointer to it. If -// there are no more commits, it returns io.EOF. -func (iter *parentCommitNodeIter) Next() (CommitNode, error) { - obj, err := iter.node.ParentNode(iter.i) - if err == object.ErrParentNotFound { - return nil, io.EOF - } - if err == nil { - iter.i++ - } - - return obj, err -} - -// ForEach call the cb function for each commit contained on this iter until -// an error appends or the end of the iter is reached. If ErrStop is sent -// the iteration is stopped but no error is returned. The iterator is closed. -func (iter *parentCommitNodeIter) ForEach(cb func(CommitNode) error) error { - for { - obj, err := iter.Next() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - if err := cb(obj); err != nil { - if err == storer.ErrStop { - return nil - } - - return err - } - } -} - -func (iter *parentCommitNodeIter) Close() { -} +package commitgraph + +import ( + "io" + "time" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/storer" +) + +// CommitNode is generic interface encapsulating a lightweight commit object retrieved +// from CommitNodeIndex +type CommitNode interface { + // ID returns the Commit object id referenced by the commit graph node. + ID() plumbing.Hash + // Tree returns the Tree referenced by the commit graph node. + Tree() (*object.Tree, error) + // CommitTime returns the Committer.When time of the Commit referenced by the commit graph node. + CommitTime() time.Time + // NumParents returns the number of parents in a commit. + NumParents() int + // ParentNodes return a CommitNodeIter for parents of specified node. + ParentNodes() CommitNodeIter + // ParentNode returns the ith parent of a commit. + ParentNode(i int) (CommitNode, error) + // ParentHashes returns hashes of the parent commits for a specified node + ParentHashes() []plumbing.Hash + // Generation returns the generation of the commit for reachability analysis. + // Objects with newer generation are not reachable from objects of older generation. + Generation() uint64 + // Commit returns the full commit object from the node + Commit() (*object.Commit, error) +} + +// CommitNodeIndex is generic interface encapsulating an index of CommitNode objects +type CommitNodeIndex interface { + // Get returns a commit node from a commit hash + Get(hash plumbing.Hash) (CommitNode, error) +} + +// CommitNodeIter is a generic closable interface for iterating over commit nodes. +type CommitNodeIter interface { + Next() (CommitNode, error) + ForEach(func(CommitNode) error) error + Close() +} + +// parentCommitNodeIter provides an iterator for parent commits from associated CommitNodeIndex. +type parentCommitNodeIter struct { + node CommitNode + i int +} + +func newParentgraphCommitNodeIter(node CommitNode) CommitNodeIter { + return &parentCommitNodeIter{node, 0} +} + +// Next moves the iterator to the next commit and returns a pointer to it. If +// there are no more commits, it returns io.EOF. +func (iter *parentCommitNodeIter) Next() (CommitNode, error) { + obj, err := iter.node.ParentNode(iter.i) + if err == object.ErrParentNotFound { + return nil, io.EOF + } + if err == nil { + iter.i++ + } + + return obj, err +} + +// ForEach call the cb function for each commit contained on this iter until +// an error appends or the end of the iter is reached. If ErrStop is sent +// the iteration is stopped but no error is returned. The iterator is closed. +func (iter *parentCommitNodeIter) ForEach(cb func(CommitNode) error) error { + for { + obj, err := iter.Next() + if err != nil { + if err == io.EOF { + return nil + } + + return err + } + + if err := cb(obj); err != nil { + if err == storer.ErrStop { + return nil + } + + return err + } + } +} + +func (iter *parentCommitNodeIter) Close() { +} diff --git a/plumbing/object/commitgraph/commitnode_test.go b/plumbing/object/commitgraph/commitnode_test.go index 6c9a64333..91fb21117 100644 --- a/plumbing/object/commitgraph/commitnode_test.go +++ b/plumbing/object/commitgraph/commitnode_test.go @@ -1,148 +1,148 @@ -package commitgraph - -import ( - "path" - "testing" - - "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/commitgraph" - "github.com/go-git/go-git/v5/plumbing/format/packfile" - "github.com/go-git/go-git/v5/storage/filesystem" - - fixtures "github.com/go-git/go-git-fixtures/v4" - . "gopkg.in/check.v1" -) - -func Test(t *testing.T) { TestingT(t) } - -type CommitNodeSuite struct { - fixtures.Suite -} - -var _ = Suite(&CommitNodeSuite{}) - -func unpackRepositry(f *fixtures.Fixture) *filesystem.Storage { - storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) - p := f.Packfile() - defer p.Close() - packfile.UpdateObjectStorage(storer, p) - return storer -} - -func testWalker(c *C, nodeIndex CommitNodeIndex) { - head, err := nodeIndex.Get(plumbing.NewHash("b9d69064b190e7aedccf84731ca1d917871f8a1c")) - c.Assert(err, IsNil) - - iter := NewCommitNodeIterCTime( - head, - nil, - nil, - ) - - var commits []CommitNode - iter.ForEach(func(c CommitNode) error { - commits = append(commits, c) - return nil - }) - - c.Assert(commits, HasLen, 9) - - expected := []string{ - "b9d69064b190e7aedccf84731ca1d917871f8a1c", - "6f6c5d2be7852c782be1dd13e36496dd7ad39560", - "a45273fe2d63300e1962a9e26a6b15c276cd7082", - "c0edf780dd0da6a65a7a49a86032fcf8a0c2d467", - "bb13916df33ed23004c3ce9ed3b8487528e655c1", - "03d2c021ff68954cf3ef0a36825e194a4b98f981", - "ce275064ad67d51e99f026084e20827901a8361c", - "e713b52d7e13807e87a002e812041f248db3f643", - "347c91919944a68e9413581a1bc15519550a3afe", - } - for i, commit := range commits { - c.Assert(commit.ID().String(), Equals, expected[i]) - } -} - -func testParents(c *C, nodeIndex CommitNodeIndex) { - merge3, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) - c.Assert(err, IsNil) - - var parents []CommitNode - merge3.ParentNodes().ForEach(func(c CommitNode) error { - parents = append(parents, c) - return nil - }) - - c.Assert(parents, HasLen, 3) - - expected := []string{ - "ce275064ad67d51e99f026084e20827901a8361c", - "bb13916df33ed23004c3ce9ed3b8487528e655c1", - "a45273fe2d63300e1962a9e26a6b15c276cd7082", - } - for i, parent := range parents { - c.Assert(parent.ID().String(), Equals, expected[i]) - } -} - -func testCommitAndTree(c *C, nodeIndex CommitNodeIndex) { - merge3node, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) - c.Assert(err, IsNil) - merge3commit, err := merge3node.Commit() - c.Assert(err, IsNil) - c.Assert(merge3node.ID().String(), Equals, merge3commit.ID().String()) - tree, err := merge3node.Tree() - c.Assert(err, IsNil) - c.Assert(tree.ID().String(), Equals, merge3commit.TreeHash.String()) -} - -func (s *CommitNodeSuite) TestObjectGraph(c *C) { - f := fixtures.ByTag("commit-graph").One() - storer := unpackRepositry(f) - - nodeIndex := NewObjectCommitNodeIndex(storer) - testWalker(c, nodeIndex) - testParents(c, nodeIndex) - testCommitAndTree(c, nodeIndex) -} - -func (s *CommitNodeSuite) TestCommitGraph(c *C) { - f := fixtures.ByTag("commit-graph").One() - storer := unpackRepositry(f) - reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) - c.Assert(err, IsNil) - defer reader.Close() - index, err := commitgraph.OpenFileIndex(reader) - c.Assert(err, IsNil) - - nodeIndex := NewGraphCommitNodeIndex(index, storer) - testWalker(c, nodeIndex) - testParents(c, nodeIndex) - testCommitAndTree(c, nodeIndex) -} - -func (s *CommitNodeSuite) TestMixedGraph(c *C) { - f := fixtures.ByTag("commit-graph").One() - storer := unpackRepositry(f) - - // Take the commit-graph file and copy it to memory index without the last commit - reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) - c.Assert(err, IsNil) - defer reader.Close() - fileIndex, err := commitgraph.OpenFileIndex(reader) - c.Assert(err, IsNil) - memoryIndex := commitgraph.NewMemoryIndex() - for i, hash := range fileIndex.Hashes() { - if hash.String() != "b9d69064b190e7aedccf84731ca1d917871f8a1c" { - node, err := fileIndex.GetCommitDataByIndex(i) - c.Assert(err, IsNil) - memoryIndex.Add(hash, node) - } - } - - nodeIndex := NewGraphCommitNodeIndex(memoryIndex, storer) - testWalker(c, nodeIndex) - testParents(c, nodeIndex) - testCommitAndTree(c, nodeIndex) -} +package commitgraph + +import ( + "path" + "testing" + + "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/commitgraph" + "github.com/go-git/go-git/v5/plumbing/format/packfile" + "github.com/go-git/go-git/v5/storage/filesystem" + + fixtures "github.com/go-git/go-git-fixtures/v4" + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type CommitNodeSuite struct { + fixtures.Suite +} + +var _ = Suite(&CommitNodeSuite{}) + +func unpackRepository(f *fixtures.Fixture) *filesystem.Storage { + storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + p := f.Packfile() + defer p.Close() + packfile.UpdateObjectStorage(storer, p) + return storer +} + +func testWalker(c *C, nodeIndex CommitNodeIndex) { + head, err := nodeIndex.Get(plumbing.NewHash("b9d69064b190e7aedccf84731ca1d917871f8a1c")) + c.Assert(err, IsNil) + + iter := NewCommitNodeIterCTime( + head, + nil, + nil, + ) + + var commits []CommitNode + iter.ForEach(func(c CommitNode) error { + commits = append(commits, c) + return nil + }) + + c.Assert(commits, HasLen, 9) + + expected := []string{ + "b9d69064b190e7aedccf84731ca1d917871f8a1c", + "6f6c5d2be7852c782be1dd13e36496dd7ad39560", + "a45273fe2d63300e1962a9e26a6b15c276cd7082", + "c0edf780dd0da6a65a7a49a86032fcf8a0c2d467", + "bb13916df33ed23004c3ce9ed3b8487528e655c1", + "03d2c021ff68954cf3ef0a36825e194a4b98f981", + "ce275064ad67d51e99f026084e20827901a8361c", + "e713b52d7e13807e87a002e812041f248db3f643", + "347c91919944a68e9413581a1bc15519550a3afe", + } + for i, commit := range commits { + c.Assert(commit.ID().String(), Equals, expected[i]) + } +} + +func testParents(c *C, nodeIndex CommitNodeIndex) { + merge3, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) + c.Assert(err, IsNil) + + var parents []CommitNode + merge3.ParentNodes().ForEach(func(c CommitNode) error { + parents = append(parents, c) + return nil + }) + + c.Assert(parents, HasLen, 3) + + expected := []string{ + "ce275064ad67d51e99f026084e20827901a8361c", + "bb13916df33ed23004c3ce9ed3b8487528e655c1", + "a45273fe2d63300e1962a9e26a6b15c276cd7082", + } + for i, parent := range parents { + c.Assert(parent.ID().String(), Equals, expected[i]) + } +} + +func testCommitAndTree(c *C, nodeIndex CommitNodeIndex) { + merge3node, err := nodeIndex.Get(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) + c.Assert(err, IsNil) + merge3commit, err := merge3node.Commit() + c.Assert(err, IsNil) + c.Assert(merge3node.ID().String(), Equals, merge3commit.ID().String()) + tree, err := merge3node.Tree() + c.Assert(err, IsNil) + c.Assert(tree.ID().String(), Equals, merge3commit.TreeHash.String()) +} + +func (s *CommitNodeSuite) TestObjectGraph(c *C) { + f := fixtures.ByTag("commit-graph").One() + storer := unpackRepository(f) + + nodeIndex := NewObjectCommitNodeIndex(storer) + testWalker(c, nodeIndex) + testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) +} + +func (s *CommitNodeSuite) TestCommitGraph(c *C) { + f := fixtures.ByTag("commit-graph").One() + storer := unpackRepository(f) + reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) + c.Assert(err, IsNil) + defer reader.Close() + index, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + + nodeIndex := NewGraphCommitNodeIndex(index, storer) + testWalker(c, nodeIndex) + testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) +} + +func (s *CommitNodeSuite) TestMixedGraph(c *C) { + f := fixtures.ByTag("commit-graph").One() + storer := unpackRepository(f) + + // Take the commit-graph file and copy it to memory index without the last commit + reader, err := storer.Filesystem().Open(path.Join("objects", "info", "commit-graph")) + c.Assert(err, IsNil) + defer reader.Close() + fileIndex, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + memoryIndex := commitgraph.NewMemoryIndex() + for i, hash := range fileIndex.Hashes() { + if hash.String() != "b9d69064b190e7aedccf84731ca1d917871f8a1c" { + node, err := fileIndex.GetCommitDataByIndex(i) + c.Assert(err, IsNil) + memoryIndex.Add(hash, node) + } + } + + nodeIndex := NewGraphCommitNodeIndex(memoryIndex, storer) + testWalker(c, nodeIndex) + testParents(c, nodeIndex) + testCommitAndTree(c, nodeIndex) +} diff --git a/plumbing/protocol/packp/ulreq_decode.go b/plumbing/protocol/packp/ulreq_decode.go index 895a3bf6d..3da29985e 100644 --- a/plumbing/protocol/packp/ulreq_decode.go +++ b/plumbing/protocol/packp/ulreq_decode.go @@ -43,7 +43,7 @@ func (d *ulReqDecoder) Decode(v *UploadRequest) error { return d.err } -// fills out the parser stiky error +// fills out the parser sticky error func (d *ulReqDecoder) error(format string, a ...interface{}) { msg := fmt.Sprintf( "pkt-line %d: %s", d.nLine, diff --git a/plumbing/protocol/packp/ulreq_decode_test.go b/plumbing/protocol/packp/ulreq_decode_test.go index efcc7b456..7658922de 100644 --- a/plumbing/protocol/packp/ulreq_decode_test.go +++ b/plumbing/protocol/packp/ulreq_decode_test.go @@ -398,7 +398,7 @@ func (s *UlReqDecodeSuite) TestDeepenCommits(c *C) { c.Assert(int(commits), Equals, 1234) } -func (s *UlReqDecodeSuite) TestDeepenCommitsInfiniteInplicit(c *C) { +func (s *UlReqDecodeSuite) TestDeepenCommitsInfiniteImplicit(c *C) { payloads := []string{ "want 3333333333333333333333333333333333333333 ofs-delta multi_ack", "deepen 0", diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index c6a054a65..b05437fbf 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -108,7 +108,7 @@ type Endpoint struct { // Host is the host. Host string // Port is the port to connect, if 0 the default port for the given protocol - // wil be used. + // will be used. Port int // Path is the repository path. Path string diff --git a/remote.go b/remote.go index 679e0af21..2ffffe7b6 100644 --- a/remote.go +++ b/remote.go @@ -614,7 +614,7 @@ func (r *Remote) addOrUpdateReferences( req *packp.ReferenceUpdateRequest, forceWithLease *ForceWithLease, ) error { - // If it is not a wilcard refspec we can directly search for the reference + // If it is not a wildcard refspec we can directly search for the reference // in the references dictionary. if !rs.IsWildcard() { ref, ok := refsDict[rs.Src()] @@ -693,7 +693,7 @@ func (r *Remote) addCommit(rs config.RefSpec, remoteRef, err := remoteRefs.Reference(cmd.Name) if err == nil { if remoteRef.Type() != plumbing.HashReference { - //TODO: check actual git behavior here + // TODO: check actual git behavior here return nil } @@ -735,7 +735,7 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, remoteRef, err := remoteRefs.Reference(cmd.Name) if err == nil { if remoteRef.Type() != plumbing.HashReference { - //TODO: check actual git behavior here + // TODO: check actual git behavior here return nil } diff --git a/remote_test.go b/remote_test.go index ca5f261c7..e0c333294 100644 --- a/remote_test.go +++ b/remote_test.go @@ -196,7 +196,7 @@ func (s *RemoteSuite) TestFetchToNewBranchWithAllTags(c *C) { }) } -func (s *RemoteSuite) TestFetchNonExistantReference(c *C) { +func (s *RemoteSuite) TestFetchNonExistentReference(c *C) { r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, }) diff --git a/utils/binary/read.go b/utils/binary/read.go index a14d48db9..b8f9df1a2 100644 --- a/utils/binary/read.go +++ b/utils/binary/read.go @@ -1,4 +1,4 @@ -// Package binary implements sintax-sugar functions on top of the standard +// Package binary implements syntax-sugar functions on top of the standard // library binary package package binary diff --git a/utils/merkletrie/difftree.go b/utils/merkletrie/difftree.go index 9f5145a26..8090942dd 100644 --- a/utils/merkletrie/difftree.go +++ b/utils/merkletrie/difftree.go @@ -55,7 +55,7 @@ package merkletrie // Here is a full list of all the cases that are similar and how to // merge them together into more general cases. Each general case // is labeled with an uppercase letter for further reference, and it -// is followed by the pseudocode of the checks you have to perfrom +// is followed by the pseudocode of the checks you have to perform // on both noders to see if you are in such a case, the actions to // perform (i.e. what changes to output) and how to advance the // iterators of each tree to continue the comparison process. diff --git a/utils/merkletrie/internal/fsnoder/file.go b/utils/merkletrie/internal/fsnoder/file.go index 0bb908b7a..453efee04 100644 --- a/utils/merkletrie/internal/fsnoder/file.go +++ b/utils/merkletrie/internal/fsnoder/file.go @@ -32,7 +32,7 @@ func newFile(name, contents string) (*file, error) { func (f *file) Hash() []byte { if f.hash == nil { h := fnv.New64a() - h.Write([]byte(f.contents)) // it nevers returns an error. + h.Write([]byte(f.contents)) // it never returns an error. f.hash = h.Sum(nil) } diff --git a/worktree_test.go b/worktree_test.go index c69c61717..712695ae2 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -903,7 +903,7 @@ func (s *WorktreeSuite) TestStatusCheckedInBeforeIgnored(c *C) { c.Assert(status.IsClean(), Equals, true) c.Assert(status, NotNil) - err = util.WriteFile(fs, "secondIgnoredFile", []byte("Should be completly ignored"), 0755) + err = util.WriteFile(fs, "secondIgnoredFile", []byte("Should be completely ignored"), 0755) c.Assert(err, IsNil) status = nil status, err = w.Status() From cf3a75c4233dc5694a209a336daf71b8171f1013 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Mon, 11 Sep 2023 22:27:13 +0100 Subject: [PATCH 148/205] *: Bump dependencies Signed-off-by: Paulo Gomes --- go.mod | 21 +++++++++++---------- go.sum | 44 +++++++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index b92e1d67a..b4bae69e7 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,14 @@ go 1.18 require ( dario.cat/mergo v1.0.0 - github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 + github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a github.com/emirpasic/gods v1.18.1 github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 - github.com/go-git/go-billy/v5 v5.4.1 + github.com/go-git/go-billy/v5 v5.4.2-0.20230911161450-5c1dfec7a041 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/go-cmp v0.5.9 @@ -23,10 +23,10 @@ require ( github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.2.0 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.11.0 - golang.org/x/net v0.12.0 - golang.org/x/sys v0.10.0 - golang.org/x/text v0.11.0 + golang.org/x/crypto v0.13.0 + golang.org/x/net v0.15.0 + golang.org/x/sys v0.12.0 + golang.org/x/text v0.13.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) @@ -34,10 +34,11 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/cloudflare/circl v1.3.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/tools v0.6.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 2c9cb40ce..8ebfab7b9 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -15,11 +15,13 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 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/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE= -github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -29,8 +31,8 @@ github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4x github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-billy/v5 v5.4.2-0.20230911161450-5c1dfec7a041 h1:NhF/q99xBg3vtiM34ZAy+cyST/jFIMv6e2g/8HtO85g= +github.com/go-git/go-billy/v5 v5.4.2-0.20230911161450-5c1dfec7a041/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -54,6 +56,7 @@ 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/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -62,8 +65,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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= @@ -83,11 +87,12 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -96,12 +101,12 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 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= @@ -119,15 +124,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.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/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -135,13 +140,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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= From cbbeb495ae1cd9d75190160ad73ad1e1256e0d96 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Mon, 11 Sep 2023 23:08:46 +0100 Subject: [PATCH 149/205] *: Bump to Go 1.19 Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 2 +- .github/workflows/test.yml | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index 60cfa1235..68776fce6 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -19,7 +19,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.20.x + go-version: 1.21.x - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce5872d03..74c9bdeb5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.19.x, 1.20.x] + go-version: [1.19.x, 1.20.x, 1.21.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} diff --git a/go.mod b/go.mod index b4bae69e7..514f2b1c7 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/go-git/go-git/v5 // go-git supports the last 3 stable Go versions. -go 1.18 +go 1.19 require ( dario.cat/mergo v1.0.0 From e24e0f714c4ecaf086b5783f099a885e6a2c9a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Tue, 12 Sep 2023 11:26:49 +0200 Subject: [PATCH 150/205] *: Bump go-billy to v5.5.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 514f2b1c7..8de45b2be 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/emirpasic/gods v1.18.1 github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 - github.com/go-git/go-billy/v5 v5.4.2-0.20230911161450-5c1dfec7a041 + github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/go-cmp v0.5.9 diff --git a/go.sum b/go.sum index 8ebfab7b9..8deb90eaa 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4x github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.2-0.20230911161450-5c1dfec7a041 h1:NhF/q99xBg3vtiM34ZAy+cyST/jFIMv6e2g/8HtO85g= -github.com/go-git/go-billy/v5 v5.4.2-0.20230911161450-5c1dfec7a041/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= From c67eceb3c2b90ce392f577f8425d066197132860 Mon Sep 17 00:00:00 2001 From: nodivbyzero Date: Thu, 14 Sep 2023 17:03:16 -0700 Subject: [PATCH 151/205] git: worktree:: apply ProxyOption on Pull. Fixes #840 --- worktree.go | 1 + 1 file changed, 1 insertion(+) diff --git a/worktree.go b/worktree.go index f9c01af28..f8b854dda 100644 --- a/worktree.go +++ b/worktree.go @@ -78,6 +78,7 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error { Force: o.Force, InsecureSkipTLS: o.InsecureSkipTLS, CABundle: o.CABundle, + ProxyOptions: o.ProxyOptions, }) updated := true From 1ad7d8dd024abca82779e2dfdff69d9712161ab8 Mon Sep 17 00:00:00 2001 From: ThinkChaos Date: Wed, 31 May 2023 11:58:44 -0400 Subject: [PATCH 152/205] git: add `PlainInitOptions.Bare` Refactor `PlainInit` to call `PlainInitWithOptions` --- options.go | 2 ++ repository.go | 23 ++++++++++++----------- repository_test.go | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/options.go b/options.go index 757bdc84e..00d9b9776 100644 --- a/options.go +++ b/options.go @@ -737,6 +737,8 @@ type PlainOpenOptions struct { func (o *PlainOpenOptions) Validate() error { return nil } type PlainInitOptions struct { + // Determines if the repository will have a worktree (non-bare) or not (bare). + Bare bool ObjectFormat formatcfg.ObjectFormat } diff --git a/repository.go b/repository.go index 3154ac019..e94fc4893 100644 --- a/repository.go +++ b/repository.go @@ -235,9 +235,19 @@ func CloneContext( // if the repository will have worktree (non-bare) or not (bare), if the path // is not empty ErrRepositoryAlreadyExists is returned. func PlainInit(path string, isBare bool) (*Repository, error) { + return PlainInitWithOptions(path, &PlainInitOptions{ + Bare: isBare, + }) +} + +func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, error) { + if opts == nil { + opts = &PlainInitOptions{} + } + var wt, dot billy.Filesystem - if isBare { + if opts.Bare { dot = osfs.New(path) } else { wt = osfs.New(path) @@ -246,15 +256,6 @@ func PlainInit(path string, isBare bool) (*Repository, error) { s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) - return Init(s, wt) -} - -func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, error) { - wt := osfs.New(path) - dot, _ := wt.Chroot(GitDirName) - - s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) - r, err := Init(s, wt) if err != nil { return nil, err @@ -265,7 +266,7 @@ func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, err return nil, err } - if opts != nil { + if opts.ObjectFormat != "" { if opts.ObjectFormat == formatcfg.SHA256 && hash.CryptoType != crypto.SHA256 { return nil, ErrSHA256NotSupported } diff --git a/repository_test.go b/repository_test.go index 9e000a3da..bc6b18853 100644 --- a/repository_test.go +++ b/repository_test.go @@ -518,6 +518,21 @@ func (s *RepositorySuite) TestPlainInit(c *C) { c.Assert(cfg.Core.IsBare, Equals, true) } +func (s *RepositorySuite) TestPlainInitWithOptions(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + r, err := PlainInitWithOptions(dir, &PlainInitOptions{ + Bare: true, + }) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + cfg, err := r.Config() + c.Assert(err, IsNil) + c.Assert(cfg.Core.IsBare, Equals, true) +} + func (s *RepositorySuite) TestPlainInitAlreadyExists(c *C) { dir, clean := s.TemporalDir() defer clean() From 644929ade3ac7c07c370be8065fa2ac6faf081be Mon Sep 17 00:00:00 2001 From: ThinkChaos Date: Wed, 31 May 2023 11:54:26 -0400 Subject: [PATCH 153/205] git: allow using `InitOptions` with `PlainInitWithOptions` --- options.go | 1 + repository.go | 2 +- repository_test.go | 13 +++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/options.go b/options.go index 00d9b9776..c68bf2bed 100644 --- a/options.go +++ b/options.go @@ -737,6 +737,7 @@ type PlainOpenOptions struct { func (o *PlainOpenOptions) Validate() error { return nil } type PlainInitOptions struct { + InitOptions // Determines if the repository will have a worktree (non-bare) or not (bare). Bare bool ObjectFormat formatcfg.ObjectFormat diff --git a/repository.go b/repository.go index e94fc4893..013b53f80 100644 --- a/repository.go +++ b/repository.go @@ -256,7 +256,7 @@ func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, err s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) - r, err := Init(s, wt) + r, err := InitWithOptions(s, wt, opts.InitOptions) if err != nil { return nil, err } diff --git a/repository_test.go b/repository_test.go index bc6b18853..3154f1d1c 100644 --- a/repository_test.go +++ b/repository_test.go @@ -523,14 +523,23 @@ func (s *RepositorySuite) TestPlainInitWithOptions(c *C) { defer clean() r, err := PlainInitWithOptions(dir, &PlainInitOptions{ - Bare: true, + InitOptions: InitOptions{ + DefaultBranch: "refs/heads/foo", + }, + Bare: false, }) c.Assert(err, IsNil) c.Assert(r, NotNil) cfg, err := r.Config() c.Assert(err, IsNil) - c.Assert(cfg.Core.IsBare, Equals, true) + c.Assert(cfg.Core.IsBare, Equals, false) + + createCommit(c, r) + + ref, err := r.Head() + c.Assert(err, IsNil) + c.Assert(ref.Name().String(), Equals, "refs/heads/foo") } func (s *RepositorySuite) TestPlainInitAlreadyExists(c *C) { From cbfd0c5005523b131449c6fdd7fa0c0bc84012a9 Mon Sep 17 00:00:00 2001 From: Aditya Sirish Date: Wed, 27 Sep 2023 11:54:31 -0400 Subject: [PATCH 154/205] plumbing/object: Support mergetag in merge commits When a merge commit is created from merging a signed tag, the tag object is embedded in the commit object. This commit adds support for this tag data when encoding and decoding a commit object. Signed-off-by: Aditya Sirish --- plumbing/object/commit.go | 38 ++++++++++++++++++++++++++++ plumbing/object/commit_test.go | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index 8a0f35c75..508e93c8c 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -20,6 +20,11 @@ const ( beginpgp string = "-----BEGIN PGP SIGNATURE-----" endpgp string = "-----END PGP SIGNATURE-----" headerpgp string = "gpgsig" + + // https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153 + // When a merge commit is created from a signed tag, the tag is embedded in + // the commit with the "mergetag" header. + headermergetag string = "mergetag" ) // Hash represents the hash of an object @@ -38,6 +43,9 @@ type Commit struct { // Committer is the one performing the commit, might be different from // Author. Committer Signature + // MergeTag is the embedded tag object when a merge commit is created by + // merging a signed tag. + MergeTag string // PGPSignature is the PGP signature of the commit. PGPSignature string // Message is the commit message, contains arbitrary text. @@ -184,6 +192,7 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { defer sync.PutBufioReader(r) var message bool + var mergetag bool var pgpsig bool var msgbuf bytes.Buffer for { @@ -192,6 +201,16 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { return err } + if mergetag { + if len(line) > 0 && line[0] == ' ' { + line = bytes.TrimLeft(line, " ") + c.MergeTag += string(line) + continue + } else { + mergetag = false + } + } + if pgpsig { if len(line) > 0 && line[0] == ' ' { line = bytes.TrimLeft(line, " ") @@ -225,6 +244,9 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { c.Author.Decode(data) case "committer": c.Committer.Decode(data) + case headermergetag: + c.MergeTag += string(data) + "\n" + mergetag = true case headerpgp: c.PGPSignature += string(data) + "\n" pgpsig = true @@ -286,6 +308,22 @@ func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) { return err } + if c.MergeTag != "" { + if _, err = fmt.Fprint(w, "\n"+headermergetag+" "); err != nil { + return err + } + + // Split tag information lines and re-write with a left padding and + // newline. Use join for this so it's clear that a newline should not be + // added after this section. The newline will be added either as part of + // the PGP signature or the commit message. + mergetag := strings.TrimSuffix(c.MergeTag, "\n") + lines := strings.Split(mergetag, "\n") + if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil { + return err + } + } + if c.PGPSignature != "" && includeSig { if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil { return err diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index 4b0f6b424..f1a344775 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -3,6 +3,7 @@ package object import ( "bytes" "context" + "fmt" "io" "strings" "time" @@ -197,6 +198,27 @@ func (s *SuiteCommit) TestPatchContext_ToNil(c *C) { } func (s *SuiteCommit) TestCommitEncodeDecodeIdempotent(c *C) { + pgpsignature := `-----BEGIN PGP SIGNATURE----- + +iQEcBAABAgAGBQJTZbQlAAoJEF0+sviABDDrZbQH/09PfE51KPVPlanr6q1v4/Ut +LQxfojUWiLQdg2ESJItkcuweYg+kc3HCyFejeDIBw9dpXt00rY26p05qrpnG+85b +hM1/PswpPLuBSr+oCIDj5GMC2r2iEKsfv2fJbNW8iWAXVLoWZRF8B0MfqX/YTMbm +ecorc4iXzQu7tupRihslbNkfvfciMnSDeSvzCpWAHl7h8Wj6hhqePmLm9lAYqnKp +8S5B/1SSQuEAjRZgI4IexpZoeKGVDptPHxLLS38fozsyi0QyDyzEgJxcJQVMXxVi +RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk= +=EFTF +-----END PGP SIGNATURE----- +` + + tag := fmt.Sprintf(`object f000000000000000000000000000000000000000 +type commit +tag change +tagger Foo 1695827841 -0400 + +change +%s +`, pgpsignature) + ts, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05-07:00") c.Assert(err, IsNil) commits := []*Commit{ @@ -219,6 +241,29 @@ func (s *SuiteCommit) TestCommitEncodeDecodeIdempotent(c *C) { plumbing.NewHash("f000000000000000000000000000000000000007"), }, }, + { + Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, + Committer: Signature{Name: "Bar", Email: "bar@example.local", When: ts}, + Message: "Testing mergetag\n\nHere, commit is not signed", + TreeHash: plumbing.NewHash("f000000000000000000000000000000000000001"), + ParentHashes: []plumbing.Hash{ + plumbing.NewHash("f000000000000000000000000000000000000002"), + plumbing.NewHash("f000000000000000000000000000000000000003"), + }, + MergeTag: tag, + }, + { + Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, + Committer: Signature{Name: "Bar", Email: "bar@example.local", When: ts}, + Message: "Testing mergetag\n\nHere, commit is also signed", + TreeHash: plumbing.NewHash("f000000000000000000000000000000000000001"), + ParentHashes: []plumbing.Hash{ + plumbing.NewHash("f000000000000000000000000000000000000002"), + plumbing.NewHash("f000000000000000000000000000000000000003"), + }, + MergeTag: tag, + PGPSignature: pgpsignature, + }, } for _, commit := range commits { obj := &plumbing.MemoryObject{} From db02bbcb2422bf5cc9f7c6af409821b79362f28b Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 27 Sep 2023 23:03:52 +0100 Subject: [PATCH 155/205] *: Add bot to close stale issues Due to the limited amount of active maintainers, adding a stale bot to focus the available maintainership on active PRs and issues. Signed-off-by: Paulo Gomes --- .github/workflows/stale-issues-bot.yaml | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/stale-issues-bot.yaml diff --git a/.github/workflows/stale-issues-bot.yaml b/.github/workflows/stale-issues-bot.yaml new file mode 100644 index 000000000..a62b9e00d --- /dev/null +++ b/.github/workflows/stale-issues-bot.yaml @@ -0,0 +1,33 @@ +name: "stale issues bot" +on: + schedule: + - cron: "0 7 * * *" + +permissions: + issues: write + pull-requests: write + +jobs: + stale-bot: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + operations-per-run: 30 + days-before-stale: 90 + days-before-close: 30 + stale-issue-label: stale + stale-pr-label: stale + exempt-issue-labels: no-autoclose + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: | + To help us keep things tidy and focus on the active tasks, we've introduced a stale bot to spot + issues/PRs that haven't had any activity in a while. + + This particular issue hasn't had any updates or activity in the past 90 days, so it's been labeled + as 'stale'. If it remains inactive for the next 30 days, it'll be automatically closed. + + We understand everyone's busy, but if this issue is still important to you, please feel free to add + a comment or make an update to keep it active. + + Thanks for your understanding and cooperation! From 85f63b17e011872817117a38f92f08a1e01297bb Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 28 Sep 2023 23:55:05 +0100 Subject: [PATCH 156/205] *: Change stale bot order to ascending and improve msg format Signed-off-by: Paulo Gomes --- .github/workflows/stale-issues-bot.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/stale-issues-bot.yaml b/.github/workflows/stale-issues-bot.yaml index a62b9e00d..69a0d3558 100644 --- a/.github/workflows/stale-issues-bot.yaml +++ b/.github/workflows/stale-issues-bot.yaml @@ -13,6 +13,7 @@ jobs: steps: - uses: actions/stale@v8 with: + ascending: true operations-per-run: 30 days-before-stale: 90 days-before-close: 30 @@ -21,13 +22,10 @@ jobs: exempt-issue-labels: no-autoclose repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | - To help us keep things tidy and focus on the active tasks, we've introduced a stale bot to spot - issues/PRs that haven't had any activity in a while. + To help us keep things tidy and focus on the active tasks, we've introduced a stale bot to spot issues/PRs that haven't had any activity in a while. - This particular issue hasn't had any updates or activity in the past 90 days, so it's been labeled - as 'stale'. If it remains inactive for the next 30 days, it'll be automatically closed. + This particular issue hasn't had any updates or activity in the past 90 days, so it's been labeled as 'stale'. If it remains inactive for the next 30 days, it'll be automatically closed. - We understand everyone's busy, but if this issue is still important to you, please feel free to add - a comment or make an update to keep it active. + We understand everyone's busy, but if this issue is still important to you, please feel free to add a comment or make an update to keep it active. Thanks for your understanding and cooperation! From 9c9c8c331b6bdb8fd5710c433e0dfd42f756d119 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 30 Sep 2023 09:09:10 +0100 Subject: [PATCH 157/205] plumbing: commitgraph, allow SHA256 commit-graphs Since the build-tag sha256 was introduced the commit graph code should be switched to use hash.Size and only use a graph if it has the correct hash version for the version of go-git that is built. Signed-off-by: Andrew Thornton --- plumbing/format/commitgraph/encoder.go | 9 +++++-- plumbing/format/commitgraph/file.go | 35 ++++++++++++++++++-------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/plumbing/format/commitgraph/encoder.go b/plumbing/format/commitgraph/encoder.go index f61025bb4..674f52e7c 100644 --- a/plumbing/format/commitgraph/encoder.go +++ b/plumbing/format/commitgraph/encoder.go @@ -1,6 +1,7 @@ package commitgraph import ( + "crypto" "io" "github.com/go-git/go-git/v5/plumbing" @@ -30,7 +31,7 @@ func (e *Encoder) Encode(idx Index) error { hashToIndex, fanout, extraEdgesCount := e.prepare(idx, hashes) chunkSignatures := [][]byte{oidFanoutSignature, oidLookupSignature, commitDataSignature} - chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * hash.Size, uint64(len(hashes)) * 36} + chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * hash.Size, uint64(len(hashes)) * (hash.Size + commitDataSize)} if extraEdgesCount > 0 { chunkSignatures = append(chunkSignatures, extraEdgeListSignature) chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*4) @@ -88,7 +89,11 @@ func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[pl func (e *Encoder) encodeFileHeader(chunkCount int) (err error) { if _, err = e.Write(commitFileSignature); err == nil { - _, err = e.Write([]byte{1, 1, byte(chunkCount), 0}) + version := byte(1) + if hash.CryptoType == crypto.SHA256 { + version = byte(2) + } + _, err = e.Write([]byte{1, version, byte(chunkCount), 0}) } return } diff --git a/plumbing/format/commitgraph/file.go b/plumbing/format/commitgraph/file.go index 1d25238f5..17c1c5d11 100644 --- a/plumbing/format/commitgraph/file.go +++ b/plumbing/format/commitgraph/file.go @@ -2,12 +2,14 @@ package commitgraph import ( "bytes" + "crypto" encbin "encoding/binary" "errors" "io" "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" ) @@ -36,6 +38,8 @@ var ( parentLast = uint32(0x80000000) ) +const commitDataSize = 16 + type fileIndex struct { reader io.ReaderAt fanout [256]int @@ -65,7 +69,7 @@ func OpenFileIndex(reader io.ReaderAt) (Index, error) { func (fi *fileIndex) verifyFileHeader() error { // Verify file signature - var signature = make([]byte, 4) + signature := make([]byte, 4) if _, err := fi.reader.ReadAt(signature, 0); err != nil { return err } @@ -74,22 +78,31 @@ func (fi *fileIndex) verifyFileHeader() error { } // Read and verify the file header - var header = make([]byte, 4) + header := make([]byte, 4) if _, err := fi.reader.ReadAt(header, 4); err != nil { return err } if header[0] != 1 { return ErrUnsupportedVersion } - if header[1] != 1 { - return ErrUnsupportedHash + if hash.CryptoType == crypto.SHA1 { + if header[1] != 1 { + return ErrUnsupportedVersion + } + } else if hash.CryptoType == crypto.SHA256 { + if header[1] != 2 { + return ErrUnsupportedVersion + } + } else { + // Unknown hash type + return ErrUnsupportedVersion } return nil } func (fi *fileIndex) readChunkHeaders() error { - var chunkID = make([]byte, 4) + chunkID := make([]byte, 4) for i := 0; ; i++ { chunkHeader := io.NewSectionReader(fi.reader, 8+(int64(i)*12), 12) if _, err := io.ReadAtLeast(chunkHeader, chunkID, 4); err != nil { @@ -148,7 +161,7 @@ func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (int, error) { high := fi.fanout[h[0]] for low < high { mid := (low + high) >> 1 - offset := fi.oidLookupOffset + int64(mid)*20 + offset := fi.oidLookupOffset + int64(mid)*hash.Size if _, err := fi.reader.ReadAt(oid[:], offset); err != nil { return 0, err } @@ -170,8 +183,8 @@ func (fi *fileIndex) GetCommitDataByIndex(idx int) (*CommitData, error) { return nil, plumbing.ErrObjectNotFound } - offset := fi.commitDataOffset + int64(idx)*36 - commitDataReader := io.NewSectionReader(fi.reader, offset, 36) + offset := fi.commitDataOffset + int64(idx)*(hash.Size+commitDataSize) + commitDataReader := io.NewSectionReader(fi.reader, offset, hash.Size+commitDataSize) treeHash, err := binary.ReadHash(commitDataReader) if err != nil { @@ -237,7 +250,7 @@ func (fi *fileIndex) getHashesFromIndexes(indexes []int) ([]plumbing.Hash, error return nil, ErrMalformedCommitGraphFile } - offset := fi.oidLookupOffset + int64(idx)*20 + offset := fi.oidLookupOffset + int64(idx)*hash.Size if _, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil { return nil, err } @@ -250,8 +263,8 @@ func (fi *fileIndex) getHashesFromIndexes(indexes []int) ([]plumbing.Hash, error func (fi *fileIndex) Hashes() []plumbing.Hash { hashes := make([]plumbing.Hash, fi.fanout[0xff]) for i := 0; i < fi.fanout[0xff]; i++ { - offset := fi.oidLookupOffset + int64(i)*20 - if n, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil || n < 20 { + offset := fi.oidLookupOffset + int64(i)*hash.Size + if n, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil || n < hash.Size { return nil } } From c135ec2f6a34116f63ebbdfde25001b21d560f24 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Sun, 1 Oct 2023 14:53:58 +0530 Subject: [PATCH 158/205] fuzzing : fuzz testing support for oss-fuzz integration Signed-off-by: Arjun Singh --- Makefile | 10 ++++++++++ internal/revision/parser_test.go | 9 +++++++++ plumbing/format/config/decoder_test.go | 11 +++++++++++ plumbing/format/packfile/delta_test.go | 12 ++++++++++++ plumbing/object/signature_test.go | 7 +++++++ plumbing/object/tree_test.go | 17 +++++++++++++++++ plumbing/protocol/packp/uppackresp_test.go | 12 ++++++++++++ plumbing/transport/common_test.go | 7 +++++++ utils/merkletrie/internal/fsnoder/new_test.go | 9 +++++++++ 9 files changed, 94 insertions(+) diff --git a/Makefile b/Makefile index 66adc8ced..6c6289231 100644 --- a/Makefile +++ b/Makefile @@ -42,3 +42,13 @@ test-coverage: clean: rm -rf $(GIT_DIST_PATH) + +fuzz: + @go test -fuzz=FuzzParser $(PWD)/internal/revision + @go test -fuzz=FuzzParseSignedByte $(PWD)/plumbing/object + @go test -fuzz=FuzzDecode $(PWD)/plumbing/object + @go test -fuzz=FuzzNewEndpoint $(PWD)/plumbing/transport + @go test -fuzz=FuzzDecoder $(PWD)/plumbing/protocol/packp + @go test -fuzz=FuzzDecoder $(PWD)/plumbing/format/config + @go test -fuzz=FuzzPatchDelta $(PWD)/plumbing/format/packfile + @go test -fuzz=FuzzDecodeFile $(PWD)/utils/merkletrie/internal/fsnoder diff --git a/internal/revision/parser_test.go b/internal/revision/parser_test.go index 3a77b2f11..1eb386100 100644 --- a/internal/revision/parser_test.go +++ b/internal/revision/parser_test.go @@ -3,6 +3,7 @@ package revision import ( "bytes" "regexp" + "testing" "time" . "gopkg.in/check.v1" @@ -397,3 +398,11 @@ func (s *ParserSuite) TestParseRefWithInvalidName(c *C) { c.Assert(err, DeepEquals, e) } } + +func FuzzParser(f *testing.F) { + + f.Fuzz(func(t *testing.T, input string) { + parser := NewParser(bytes.NewBufferString(input)) + parser.Parse() + }) +} diff --git a/plumbing/format/config/decoder_test.go b/plumbing/format/config/decoder_test.go index 0a8e92c83..6283f5e14 100644 --- a/plumbing/format/config/decoder_test.go +++ b/plumbing/format/config/decoder_test.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "testing" . "gopkg.in/check.v1" ) @@ -91,3 +92,13 @@ func decodeFails(c *C, text string) { err := d.Decode(cfg) c.Assert(err, NotNil) } + +func FuzzDecoder(f *testing.F) { + + f.Fuzz(func(t *testing.T, input []byte) { + + d := NewDecoder(bytes.NewReader(input)) + cfg := &Config{} + d.Decode(cfg) + }) +} diff --git a/plumbing/format/packfile/delta_test.go b/plumbing/format/packfile/delta_test.go index e8f5ea68f..9417e558a 100644 --- a/plumbing/format/packfile/delta_test.go +++ b/plumbing/format/packfile/delta_test.go @@ -4,6 +4,7 @@ import ( "bytes" "io" "math/rand" + "testing" "github.com/go-git/go-git/v5/plumbing" . "gopkg.in/check.v1" @@ -176,3 +177,14 @@ func (s *DeltaSuite) TestMaxCopySizeDeltaReader(c *C) { c.Assert(err, IsNil) c.Assert(result, DeepEquals, targetBuf) } + +func FuzzPatchDelta(f *testing.F) { + + f.Fuzz(func(t *testing.T, input []byte) { + + input_0 := input[:len(input)/2] + input_1 := input[len(input)/2:] + + PatchDelta(input_0, input_1) + }) +} diff --git a/plumbing/object/signature_test.go b/plumbing/object/signature_test.go index 1bdb1d1ca..3b20cded4 100644 --- a/plumbing/object/signature_test.go +++ b/plumbing/object/signature_test.go @@ -178,3 +178,10 @@ signed tag`), }) } } + +func FuzzParseSignedBytes(f *testing.F) { + + f.Fuzz(func(t *testing.T, input []byte) { + parseSignedBytes(input) + }) +} diff --git a/plumbing/object/tree_test.go b/plumbing/object/tree_test.go index d9dad4775..bb5fc7a09 100644 --- a/plumbing/object/tree_test.go +++ b/plumbing/object/tree_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "testing" fixtures "github.com/go-git/go-git-fixtures/v4" "github.com/go-git/go-git/v5/plumbing" @@ -1623,3 +1624,19 @@ func (s *TreeSuite) TestTreeDecodeReadBug(c *C) { c.Assert(err, IsNil) c.Assert(entriesEquals(obtained.Entries, expected.Entries), Equals, true) } + +func FuzzDecode(f *testing.F) { + + f.Fuzz(func(t *testing.T, input []byte) { + + obj := &SortReadObject{ + t: plumbing.TreeObject, + h: plumbing.ZeroHash, + cont: input, + sz: int64(len(input)), + } + + newTree := &Tree{} + newTree.Decode(obj) + }) +} diff --git a/plumbing/protocol/packp/uppackresp_test.go b/plumbing/protocol/packp/uppackresp_test.go index 8fbf92467..ec56507e2 100644 --- a/plumbing/protocol/packp/uppackresp_test.go +++ b/plumbing/protocol/packp/uppackresp_test.go @@ -3,6 +3,7 @@ package packp import ( "bytes" "io" + "testing" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" @@ -128,3 +129,14 @@ func (s *UploadPackResponseSuite) TestEncodeMultiACK(c *C) { b := bytes.NewBuffer(nil) c.Assert(res.Encode(b), NotNil) } + +func FuzzDecoder(f *testing.F) { + + f.Fuzz(func(t *testing.T, input []byte) { + req := NewUploadPackRequest() + res := NewUploadPackResponse(req) + defer res.Close() + + res.Decode(io.NopCloser(bytes.NewReader(input))) + }) +} diff --git a/plumbing/transport/common_test.go b/plumbing/transport/common_test.go index d9f12ab18..3efc555e7 100644 --- a/plumbing/transport/common_test.go +++ b/plumbing/transport/common_test.go @@ -210,3 +210,10 @@ func (s *SuiteCommon) TestNewEndpointIPv6(c *C) { c.Assert(e.Host, Equals, "[::1]") c.Assert(e.String(), Equals, "http://[::1]:8080/foo.git") } + +func FuzzNewEndpoint(f *testing.F) { + + f.Fuzz(func(t *testing.T, input string) { + NewEndpoint(input) + }) +} diff --git a/utils/merkletrie/internal/fsnoder/new_test.go b/utils/merkletrie/internal/fsnoder/new_test.go index ad069c7fc..52b3dc4b4 100644 --- a/utils/merkletrie/internal/fsnoder/new_test.go +++ b/utils/merkletrie/internal/fsnoder/new_test.go @@ -1,6 +1,8 @@ package fsnoder import ( + "testing" + "github.com/go-git/go-git/v5/utils/merkletrie/noder" . "gopkg.in/check.v1" @@ -352,3 +354,10 @@ func (s *FSNoderSuite) TestHashEqual(c *C) { c.Assert(HashEqual(t3, t1), Equals, false) c.Assert(HashEqual(t1, t3), Equals, false) } + +func FuzzDecodeFile(f *testing.F) { + + f.Fuzz(func(t *testing.T, input []byte) { + decodeFile(input) + }) +} From 3dbb11f2d99839e618cf8e100f0e8f77f543637a Mon Sep 17 00:00:00 2001 From: liwenqiu Date: Sun, 7 May 2023 22:37:08 +0800 Subject: [PATCH 159/205] plumbing: parse the encoding header of the commit object other part can re-code the commit message according to the encoding to this encoding info --- plumbing/object/commit.go | 23 ++++++++++++++++++++--- plumbing/object/commit_test.go | 6 +++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index 508e93c8c..ceed5d01e 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -17,19 +17,25 @@ import ( ) const ( - beginpgp string = "-----BEGIN PGP SIGNATURE-----" - endpgp string = "-----END PGP SIGNATURE-----" - headerpgp string = "gpgsig" + beginpgp string = "-----BEGIN PGP SIGNATURE-----" + endpgp string = "-----END PGP SIGNATURE-----" + headerpgp string = "gpgsig" + headerencoding string = "encoding" // https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153 // When a merge commit is created from a signed tag, the tag is embedded in // the commit with the "mergetag" header. headermergetag string = "mergetag" + + defaultUtf8CommitMesageEncoding MessageEncoding = "UTF-8" ) // Hash represents the hash of an object type Hash plumbing.Hash +// MessageEncoding represents the encoding of a commit +type MessageEncoding string + // Commit points to a single tree, marking it as what the project looked like // at a certain point in time. It contains meta-information about that point // in time, such as a timestamp, the author of the changes since the last @@ -54,6 +60,8 @@ type Commit struct { TreeHash plumbing.Hash // ParentHashes are the hashes of the parent commits of the commit. ParentHashes []plumbing.Hash + // Encoding is the encoding of the commit. + Encoding MessageEncoding s storer.EncodedObjectStorer } @@ -181,6 +189,7 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { } c.Hash = o.Hash() + c.Encoding = defaultUtf8CommitMesageEncoding reader, err := o.Reader() if err != nil { @@ -247,6 +256,8 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { case headermergetag: c.MergeTag += string(data) + "\n" mergetag = true + case headerencoding: + c.Encoding = MessageEncoding(data) case headerpgp: c.PGPSignature += string(data) + "\n" pgpsig = true @@ -324,6 +335,12 @@ func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) { } } + if string(c.Encoding) != "" && c.Encoding != defaultUtf8CommitMesageEncoding { + if _, err = fmt.Fprintf(w, "\n%s %s", headerencoding, c.Encoding); err != nil { + return err + } + } + if c.PGPSignature != "" && includeSig { if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil { return err diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index f1a344775..3e1fe1b90 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -228,6 +228,7 @@ change Message: "Message\n\nFoo\nBar\nWith trailing blank lines\n\n", TreeHash: plumbing.NewHash("f000000000000000000000000000000000000001"), ParentHashes: []plumbing.Hash{plumbing.NewHash("f000000000000000000000000000000000000002")}, + Encoding: defaultUtf8CommitMesageEncoding, }, { Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, @@ -240,6 +241,7 @@ change plumbing.NewHash("f000000000000000000000000000000000000006"), plumbing.NewHash("f000000000000000000000000000000000000007"), }, + Encoding: MessageEncoding("ISO-8859-1"), }, { Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, @@ -251,6 +253,7 @@ change plumbing.NewHash("f000000000000000000000000000000000000003"), }, MergeTag: tag, + Encoding: defaultUtf8CommitMesageEncoding, }, { Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts}, @@ -263,6 +266,7 @@ change }, MergeTag: tag, PGPSignature: pgpsignature, + Encoding: defaultUtf8CommitMesageEncoding, }, } for _, commit := range commits { @@ -530,7 +534,7 @@ func (s *SuiteCommit) TestMalformedHeader(c *C) { } func (s *SuiteCommit) TestEncodeWithoutSignature(c *C) { - //Similar to TestString since no signature + // Similar to TestString since no signature encoded := &plumbing.MemoryObject{} err := s.Commit.EncodeWithoutSignature(encoded) c.Assert(err, IsNil) From d32d6cdd1e5e144f7d3bb1f90f2076b3f3378674 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Wed, 4 Oct 2023 11:04:06 +0530 Subject: [PATCH 160/205] [fuzzing] cifuzz, update fuzzers, bug fix Signed-off-by: Arjun Singh --- .github/workflows/cifuzz.yml | 35 +++++++++++++++++++ Makefile | 9 +++-- oss-fuzz.sh | 35 +++++++++++++++++++ utils/merkletrie/internal/fsnoder/new_test.go | 9 ----- 4 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/cifuzz.yml create mode 100644 oss-fuzz.sh diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 000000000..2b17ac18b --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,35 @@ +name: CIFuzz +on: [pull_request] +permissions: {} +jobs: + Fuzzing: + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'go-git' + language: go + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'go-git' + language: go + fuzz-seconds: 300 + output-sarif: true + - name: Upload Crash + uses: actions/upload-artifact@v3 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts + - name: Upload Sarif + if: always() && steps.build.outcome == 'success' + uses: github/codeql-action/upload-sarif@v2 + with: + # Path to SARIF file relative to the root of the repository + sarif_file: cifuzz-sarif/results.sarif + checkout_path: cifuzz-sarif diff --git a/Makefile b/Makefile index 6c6289231..1e1039674 100644 --- a/Makefile +++ b/Makefile @@ -45,10 +45,9 @@ clean: fuzz: @go test -fuzz=FuzzParser $(PWD)/internal/revision - @go test -fuzz=FuzzParseSignedByte $(PWD)/plumbing/object - @go test -fuzz=FuzzDecode $(PWD)/plumbing/object - @go test -fuzz=FuzzNewEndpoint $(PWD)/plumbing/transport - @go test -fuzz=FuzzDecoder $(PWD)/plumbing/protocol/packp @go test -fuzz=FuzzDecoder $(PWD)/plumbing/format/config @go test -fuzz=FuzzPatchDelta $(PWD)/plumbing/format/packfile - @go test -fuzz=FuzzDecodeFile $(PWD)/utils/merkletrie/internal/fsnoder + @go test -fuzz=FuzzParseSignedBytes $(PWD)/plumbing/object + @go test -fuzz=FuzzDecode $(PWD)/plumbing/object + @go test -fuzz=FuzzDecoder $(PWD)/plumbing/protocol/packp + @go test -fuzz=FuzzNewEndpoint $(PWD)/plumbing/transport diff --git a/oss-fuzz.sh b/oss-fuzz.sh new file mode 100644 index 000000000..885548f40 --- /dev/null +++ b/oss-fuzz.sh @@ -0,0 +1,35 @@ +#!/bin/bash -eu +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + + +go mod download +go get github.com/AdamKorcz/go-118-fuzz-build/testing + +if [ "$SANITIZER" != "coverage" ]; then + sed -i '/func (s \*DecoderSuite) TestDecode(/,/^}/ s/^/\/\//' plumbing/format/config/decoder_test.go + sed -n '35,$p' plumbing/format/packfile/common_test.go >> plumbing/format/packfile/delta_test.go + sed -n '20,53p' plumbing/object/object_test.go >> plumbing/object/tree_test.go + sed -i 's|func Test|// func Test|' plumbing/transport/common_test.go +fi + +compile_native_go_fuzzer $(pwd)/internal/revision FuzzParser fuzz_parser +compile_native_go_fuzzer $(pwd)/plumbing/format/config FuzzDecoder fuzz_decoder_config +compile_native_go_fuzzer $(pwd)/plumbing/format/packfile FuzzPatchDelta fuzz_patch_delta +compile_native_go_fuzzer $(pwd)/plumbing/object FuzzParseSignedBytes fuzz_parse_signed_bytes +compile_native_go_fuzzer $(pwd)/plumbing/object FuzzDecode fuzz_decode +compile_native_go_fuzzer $(pwd)/plumbing/protocol/packp FuzzDecoder fuzz_decoder_packp +compile_native_go_fuzzer $(pwd)/plumbing/transport FuzzNewEndpoint fuzz_new_endpoint diff --git a/utils/merkletrie/internal/fsnoder/new_test.go b/utils/merkletrie/internal/fsnoder/new_test.go index 52b3dc4b4..ad069c7fc 100644 --- a/utils/merkletrie/internal/fsnoder/new_test.go +++ b/utils/merkletrie/internal/fsnoder/new_test.go @@ -1,8 +1,6 @@ package fsnoder import ( - "testing" - "github.com/go-git/go-git/v5/utils/merkletrie/noder" . "gopkg.in/check.v1" @@ -354,10 +352,3 @@ func (s *FSNoderSuite) TestHashEqual(c *C) { c.Assert(HashEqual(t3, t1), Equals, false) c.Assert(HashEqual(t1, t3), Equals, false) } - -func FuzzDecodeFile(f *testing.F) { - - f.Fuzz(func(t *testing.T, input []byte) { - decodeFile(input) - }) -} From 1d26511c717ea334a16499a018d2b877b557be79 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sat, 7 Oct 2023 18:44:31 +0100 Subject: [PATCH 161/205] plumbing: protocol/packp, Add validation for decodeLine Signed-off-by: Paulo Gomes --- plumbing/protocol/packp/srvresp.go | 12 ++++++----- plumbing/protocol/packp/srvresp_test.go | 27 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/plumbing/protocol/packp/srvresp.go b/plumbing/protocol/packp/srvresp.go index 8cd0a7247..a9ddb538b 100644 --- a/plumbing/protocol/packp/srvresp.go +++ b/plumbing/protocol/packp/srvresp.go @@ -101,12 +101,14 @@ func (r *ServerResponse) decodeLine(line []byte) error { return fmt.Errorf("unexpected flush") } - if bytes.Equal(line[0:3], ack) { - return r.decodeACKLine(line) - } + if len(line) >= 3 { + if bytes.Equal(line[0:3], ack) { + return r.decodeACKLine(line) + } - if bytes.Equal(line[0:3], nak) { - return nil + if bytes.Equal(line[0:3], nak) { + return nil + } } return fmt.Errorf("unexpected content %q", string(line)) diff --git a/plumbing/protocol/packp/srvresp_test.go b/plumbing/protocol/packp/srvresp_test.go index aa0af528a..b7270e79e 100644 --- a/plumbing/protocol/packp/srvresp_test.go +++ b/plumbing/protocol/packp/srvresp_test.go @@ -3,6 +3,7 @@ package packp import ( "bufio" "bytes" + "fmt" "github.com/go-git/go-git/v5/plumbing" @@ -23,6 +24,32 @@ func (s *ServerResponseSuite) TestDecodeNAK(c *C) { c.Assert(sr.ACKs, HasLen, 0) } +func (s *ServerResponseSuite) TestDecodeNewLine(c *C) { + raw := "\n" + + sr := &ServerResponse{} + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "invalid pkt-len found") +} + +func (s *ServerResponseSuite) TestDecodeEmpty(c *C) { + raw := "" + + sr := &ServerResponse{} + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) + c.Assert(err, IsNil) +} + +func (s *ServerResponseSuite) TestDecodePartial(c *C) { + raw := "000600\n" + + sr := &ServerResponse{} + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, fmt.Sprintf("unexpected content %q", "00")) +} + func (s *ServerResponseSuite) TestDecodeACK(c *C) { raw := "0031ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e5\n" From 479d3e952e75d6e41b71a81cc9a92dec792825ba Mon Sep 17 00:00:00 2001 From: enverbisevac Date: Fri, 6 Oct 2023 02:54:20 +0200 Subject: [PATCH 162/205] git: clone --shared implemented --- .gitignore | 1 + COMPATIBILITY.md | 344 ++++++++++++++-------------- options.go | 9 + plumbing/storer/object.go | 1 + plumbing/storer/object_test.go | 4 + repository.go | 26 +++ repository_test.go | 96 ++++++++ storage/filesystem/dotgit/dotgit.go | 33 ++- storage/filesystem/storage.go | 4 + storage/memory/storage.go | 4 + storage/transactional/object.go | 4 + 11 files changed, 353 insertions(+), 173 deletions(-) diff --git a/.gitignore b/.gitignore index 361133d03..b7f2c5807 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ coverage.txt profile.out .tmp/ .git-dist/ +.vscode diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index afd4f03bc..bbffea522 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -5,229 +5,229 @@ compatibility status with go-git. ## Getting and creating repositories -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `init` | | ✅ | | | -| `init` | `--bare` | ✅ | | | -| `init` | `--template`
`--separate-git-dir`
`--shared` | ❌ | | | -| `clone` | | ✅ | | - [PlainClone](_examples/clone/main.go) | -| `clone` | Authentication:
- none
- access token
- username + password
- ssh | ✅ | | - [clone ssh](_examples/clone/auth/ssh/main.go)
- [clone access token](_examples/clone/auth/basic/access_token/main.go)
- [clone user + password](_examples/clone/auth/basic/username_password/main.go) | -| `clone` | `--progress`
`--single-branch`
`--depth`
`--origin`
`--recurse-submodules` | ✅ | | - [recurse submodules](_examples/clone/main.go)
- [progress](_examples/progress/main.go) | +| Feature | Sub-feature | Status | Notes | Examples | +| ------- | ------------------------------------------------------------------------------------------------------------------ | ------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `init` | | ✅ | | | +| `init` | `--bare` | ✅ | | | +| `init` | `--template`
`--separate-git-dir`
`--shared` | ❌ | | | +| `clone` | | ✅ | | - [PlainClone](_examples/clone/main.go) | +| `clone` | Authentication:
- none
- access token
- username + password
- ssh | ✅ | | - [clone ssh](_examples/clone/auth/ssh/main.go)
- [clone access token](_examples/clone/auth/basic/access_token/main.go)
- [clone user + password](_examples/clone/auth/basic/username_password/main.go) | +| `clone` | `--progress`
`--single-branch`
`--depth`
`--origin`
`--recurse-submodules`
`--shared` | ✅ | | - [recurse submodules](_examples/clone/main.go)
- [progress](_examples/progress/main.go) | ## Basic snapshotting -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `add` | | ✅ | Plain add is supported. Any other flags aren't supported | | -| `status` | | ✅ | | | -| `commit` | | ✅ | | - [commit](_examples/commit/main.go) | -| `reset` | | ✅ | | | -| `rm` | | ✅ | | | -| `mv` | | ✅ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| -------- | ----------- | ------ | -------------------------------------------------------- | ------------------------------------ | +| `add` | | ✅ | Plain add is supported. Any other flags aren't supported | | +| `status` | | ✅ | | | +| `commit` | | ✅ | | - [commit](_examples/commit/main.go) | +| `reset` | | ✅ | | | +| `rm` | | ✅ | | | +| `mv` | | ✅ | | | ## Branching and merging -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `branch` | | ✅ | | - [branch](_examples/branch/main.go) | -| `checkout` | | ✅ | Basic usages of checkout are supported. | - [checkout](_examples/checkout/main.go) | -| `merge` | | ❌ | | | -| `mergetool` | | ❌ | | | -| `stash` | | ❌ | | | -| `tag` | | ✅ | | - [tag](_examples/tag/main.go)
- [tag create and push](_examples/tag-create-push/main.go) | +| Feature | Sub-feature | Status | Notes | Examples | +| ----------- | ----------- | ------ | --------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `branch` | | ✅ | | - [branch](_examples/branch/main.go) | +| `checkout` | | ✅ | Basic usages of checkout are supported. | - [checkout](_examples/checkout/main.go) | +| `merge` | | ❌ | | | +| `mergetool` | | ❌ | | | +| `stash` | | ❌ | | | +| `tag` | | ✅ | | - [tag](_examples/tag/main.go)
- [tag create and push](_examples/tag-create-push/main.go) | ## Sharing and updating projects -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `fetch` | | ✅ | | | -| `pull` | | ✅ | Only supports merges where the merge can be resolved as a fast-forward. | - [pull](_examples/pull/main.go) | -| `push` | | ✅ | | - [push](_examples/push/main.go) | -| `remote` | | ✅ | | - [remotes](_examples/remotes/main.go) | -| `submodule` | | ✅ | | - [submodule](_examples/submodule/main.go) | -| `submodule` | deinit | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| ----------- | ----------- | ------ | ----------------------------------------------------------------------- | ------------------------------------------ | +| `fetch` | | ✅ | | | +| `pull` | | ✅ | Only supports merges where the merge can be resolved as a fast-forward. | - [pull](_examples/pull/main.go) | +| `push` | | ✅ | | - [push](_examples/push/main.go) | +| `remote` | | ✅ | | - [remotes](_examples/remotes/main.go) | +| `submodule` | | ✅ | | - [submodule](_examples/submodule/main.go) | +| `submodule` | deinit | ❌ | | | ## Inspection and comparison -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `show` | | ✅ | | | -| `log` | | ✅ | | - [log](_examples/log/main.go) | -| `shortlog` | | (see log) | | | -| `describe` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| ---------- | ----------- | --------- | ----- | ------------------------------ | +| `show` | | ✅ | | | +| `log` | | ✅ | | - [log](_examples/log/main.go) | +| `shortlog` | | (see log) | | | +| `describe` | | ❌ | | | ## Patching -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `apply` | | ❌ | | | -| `cherry-pick` | | ❌ | | | -| `diff` | | ✅ | Patch object with UnifiedDiff output representation. | | -| `rebase` | | ❌ | | | -| `revert` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| ------------- | ----------- | ------ | ---------------------------------------------------- | -------- | +| `apply` | | ❌ | | | +| `cherry-pick` | | ❌ | | | +| `diff` | | ✅ | Patch object with UnifiedDiff output representation. | | +| `rebase` | | ❌ | | | +| `revert` | | ❌ | | | ## Debugging -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `bisect` | | ❌ | | | -| `blame` | | ✅ | | - [blame](_examples/blame/main.go) | -| `grep` | | ✅ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| -------- | ----------- | ------ | ----- | ---------------------------------- | +| `bisect` | | ❌ | | | +| `blame` | | ✅ | | - [blame](_examples/blame/main.go) | +| `grep` | | ✅ | | | ## Email -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `am` | | ❌ | | | -| `apply` | | ❌ | | | -| `format-patch` | | ❌ | | | -| `send-email` | | ❌ | | | -| `request-pull` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| -------------- | ----------- | ------ | ----- | -------- | +| `am` | | ❌ | | | +| `apply` | | ❌ | | | +| `format-patch` | | ❌ | | | +| `send-email` | | ❌ | | | +| `request-pull` | | ❌ | | | ## External systems -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `svn` | | ❌ | | | -| `fast-import` | | ❌ | | | -| `lfs` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| ------------- | ----------- | ------ | ----- | -------- | +| `svn` | | ❌ | | | +| `fast-import` | | ❌ | | | +| `lfs` | | ❌ | | | ## Administration -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `clean` | | ✅ | | | -| `gc` | | ❌ | | | -| `fsck` | | ❌ | | | -| `reflog` | | ❌ | | | -| `filter-branch` | | ❌ | | | -| `instaweb` | | ❌ | | | -| `archive` | | ❌ | | | -| `bundle` | | ❌ | | | -| `prune` | | ❌ | | | -| `repack` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| --------------- | ----------- | ------ | ----- | -------- | +| `clean` | | ✅ | | | +| `gc` | | ❌ | | | +| `fsck` | | ❌ | | | +| `reflog` | | ❌ | | | +| `filter-branch` | | ❌ | | | +| `instaweb` | | ❌ | | | +| `archive` | | ❌ | | | +| `bundle` | | ❌ | | | +| `prune` | | ❌ | | | +| `repack` | | ❌ | | | ## Server admin -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `daemon` | | ❌ | | | -| `update-server-info` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| -------------------- | ----------- | ------ | ----- | -------- | +| `daemon` | | ❌ | | | +| `update-server-info` | | ❌ | | | ## Advanced -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `notes` | | ❌ | | | -| `replace` | | ❌ | | | -| `worktree` | | ❌ | | | -| `annotate` | | (see blame) | | | +| Feature | Sub-feature | Status | Notes | Examples | +| ---------- | ----------- | ----------- | ----- | -------- | +| `notes` | | ❌ | | | +| `replace` | | ❌ | | | +| `worktree` | | ❌ | | | +| `annotate` | | (see blame) | | | ## GPG -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `git-verify-commit` | | ✅ | | | -| `git-verify-tag` | | ✅ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| ------------------- | ----------- | ------ | ----- | -------- | +| `git-verify-commit` | | ✅ | | | +| `git-verify-tag` | | ✅ | | | ## Plumbing commands -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `cat-file` | | ✅ | | | -| `check-ignore` | | ❌ | | | -| `commit-tree` | | ❌ | | | -| `count-objects` | | ❌ | | | -| `diff-index` | | ❌ | | | -| `for-each-ref` | | ✅ | | | -| `hash-object` | | ✅ | | | -| `ls-files` | | ✅ | | | -| `ls-remote` | | ✅ | | - [ls-remote](_examples/ls-remote/main.go) | -| `merge-base` | `--independent`
`--is-ancestor` | ⚠️ (partial) | Calculates the merge-base only between two commits. | - [merge-base](_examples/merge_base/main.go) | -| `merge-base` | `--fork-point`
`--octopus` | ❌ | | | -| `read-tree` | | ❌ | | | -| `rev-list` | | ✅ | | | -| `rev-parse` | | ❌ | | | -| `show-ref` | | ✅ | | | -| `symbolic-ref` | | ✅ | | | -| `update-index` | | ❌ | | | -| `update-ref` | | ❌ | | | -| `verify-pack` | | ❌ | | | -| `write-tree` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| --------------- | ------------------------------------- | ------------ | --------------------------------------------------- | -------------------------------------------- | +| `cat-file` | | ✅ | | | +| `check-ignore` | | ❌ | | | +| `commit-tree` | | ❌ | | | +| `count-objects` | | ❌ | | | +| `diff-index` | | ❌ | | | +| `for-each-ref` | | ✅ | | | +| `hash-object` | | ✅ | | | +| `ls-files` | | ✅ | | | +| `ls-remote` | | ✅ | | - [ls-remote](_examples/ls-remote/main.go) | +| `merge-base` | `--independent`
`--is-ancestor` | ⚠️ (partial) | Calculates the merge-base only between two commits. | - [merge-base](_examples/merge_base/main.go) | +| `merge-base` | `--fork-point`
`--octopus` | ❌ | | | +| `read-tree` | | ❌ | | | +| `rev-list` | | ✅ | | | +| `rev-parse` | | ❌ | | | +| `show-ref` | | ✅ | | | +| `symbolic-ref` | | ✅ | | | +| `update-index` | | ❌ | | | +| `update-ref` | | ❌ | | | +| `verify-pack` | | ❌ | | | +| `write-tree` | | ❌ | | | ## Indexes and Git Protocols -| Feature | Version | Status | Notes | -|---|---|---|---| -| index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | -| index | [v2](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ✅ | | -| index | [v3](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | -| pack-protocol | [v1](https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.txt) | ✅ | | -| pack-protocol | [v2](https://github.com/git/git/blob/master/Documentation/gitprotocol-v2.txt) | ❌ | | -| multi-pack-index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | -| pack-*.rev files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | -| pack-*.mtimes files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | -| cruft packs | | ❌ | | +| Feature | Version | Status | Notes | +| -------------------- | ------------------------------------------------------------------------------- | ------ | ----- | +| index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | +| index | [v2](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ✅ | | +| index | [v3](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | +| pack-protocol | [v1](https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.txt) | ✅ | | +| pack-protocol | [v2](https://github.com/git/git/blob/master/Documentation/gitprotocol-v2.txt) | ❌ | | +| multi-pack-index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | +| pack-\*.rev files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | +| pack-\*.mtimes files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | +| cruft packs | | ❌ | | ## Capabilities -| Feature | Status | Notes | -|---|---|---| -| `multi_ack` | ❌ | | -| `multi_ack_detailed` | ❌ | | -| `no-done` | ❌ | | -| `thin-pack` | ❌ | | -| `side-band` | ⚠️ (partial) | | -| `side-band-64k` | ⚠️ (partial) | | -| `ofs-delta` | ✅ | | -| `agent` | ✅ | | -| `object-format` | ❌ | | -| `symref` | ✅ | | -| `shallow` | ✅ | | -| `deepen-since` | ✅ | | -| `deepen-not` | ❌ | | -| `deepen-relative` | ❌ | | -| `no-progress` | ✅ | | -| `include-tag` | ✅ | | -| `report-status` | ✅ | | -| `report-status-v2` | ❌ | | -| `delete-refs` | ✅ | | -| `quiet` | ❌ | | -| `atomic` | ✅ | | -| `push-options` | ✅ | | -| `allow-tip-sha1-in-want` | ✅ | | -| `allow-reachable-sha1-in-want` | ❌ | | -| `push-cert=` | ❌ | | -| `filter` | ❌ | | -| `session-id=` | ❌ | | +| Feature | Status | Notes | +| ------------------------------ | ------------ | ----- | +| `multi_ack` | ❌ | | +| `multi_ack_detailed` | ❌ | | +| `no-done` | ❌ | | +| `thin-pack` | ❌ | | +| `side-band` | ⚠️ (partial) | | +| `side-band-64k` | ⚠️ (partial) | | +| `ofs-delta` | ✅ | | +| `agent` | ✅ | | +| `object-format` | ❌ | | +| `symref` | ✅ | | +| `shallow` | ✅ | | +| `deepen-since` | ✅ | | +| `deepen-not` | ❌ | | +| `deepen-relative` | ❌ | | +| `no-progress` | ✅ | | +| `include-tag` | ✅ | | +| `report-status` | ✅ | | +| `report-status-v2` | ❌ | | +| `delete-refs` | ✅ | | +| `quiet` | ❌ | | +| `atomic` | ✅ | | +| `push-options` | ✅ | | +| `allow-tip-sha1-in-want` | ✅ | | +| `allow-reachable-sha1-in-want` | ❌ | | +| `push-cert=` | ❌ | | +| `filter` | ❌ | | +| `session-id=` | ❌ | | ## Transport Schemes -| Scheme | Status | Notes | Examples | -|---|---|---|---| -| `http(s)://` (dumb) | ❌ | | | -| `http(s)://` (smart) | ✅ | | | -| `git://` | ✅ | | | -| `ssh://` | ✅ | | | -| `file://` | ⚠️ (partial) | Warning: this is not pure Golang. This shells out to the `git` binary. | | -| Custom | ✅ | All existing schemes can be replaced by custom implementations. | - [custom_http](_examples/custom_http/main.go) | +| Scheme | Status | Notes | Examples | +| -------------------- | ------------ | ---------------------------------------------------------------------- | ---------------------------------------------- | +| `http(s)://` (dumb) | ❌ | | | +| `http(s)://` (smart) | ✅ | | | +| `git://` | ✅ | | | +| `ssh://` | ✅ | | | +| `file://` | ⚠️ (partial) | Warning: this is not pure Golang. This shells out to the `git` binary. | | +| Custom | ✅ | All existing schemes can be replaced by custom implementations. | - [custom_http](_examples/custom_http/main.go) | ## SHA256 -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `init` | | ✅ | Requires building with tag sha256. | - [init](_examples/sha256/main.go) | -| `commit` | | ✅ | Requires building with tag sha256. | - [commit](_examples/sha256/main.go) | -| `pull` | | ❌ | | | -| `fetch` | | ❌ | | | -| `push` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| -------- | ----------- | ------ | ---------------------------------- | ------------------------------------ | +| `init` | | ✅ | Requires building with tag sha256. | - [init](_examples/sha256/main.go) | +| `commit` | | ✅ | Requires building with tag sha256. | - [commit](_examples/sha256/main.go) | +| `pull` | | ❌ | | | +| `fetch` | | ❌ | | | +| `push` | | ❌ | | | ## Other features -| Feature | Sub-feature | Status | Notes | Examples | -|---|---|---|---|---| -| `config` | `--local` | ✅ | Read and write per-repository (`.git/config`). | | -| `config` | `--global`
`--system` | ✅ | Read-only. | | -| `gitignore` | | ✅ | | | -| `gitattributes` | | ✅ | | | -| `git-worktree` | | ❌ | Multiple worktrees are not supported. | | +| Feature | Sub-feature | Status | Notes | Examples | +| --------------- | --------------------------- | ------ | ---------------------------------------------- | -------- | +| `config` | `--local` | ✅ | Read and write per-repository (`.git/config`). | | +| `config` | `--global`
`--system` | ✅ | Read-only. | | +| `gitignore` | | ✅ | | | +| `gitattributes` | | ✅ | | | +| `git-worktree` | | ❌ | Multiple worktrees are not supported. | | diff --git a/options.go b/options.go index c68bf2bed..8902b7e3e 100644 --- a/options.go +++ b/options.go @@ -78,6 +78,15 @@ type CloneOptions struct { CABundle []byte // ProxyOptions provides info required for connecting to a proxy. ProxyOptions transport.ProxyOptions + // When the repository to clone is on the local machine, instead of + // using hard links, automatically setup .git/objects/info/alternates + // to share the objects with the source repository. + // The resulting repository starts out without any object of its own. + // NOTE: this is a possibly dangerous operation; do not use it unless + // you understand what it does. + // + // [Reference]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---shared + Shared bool } // Validate validates the fields and sets the default values. diff --git a/plumbing/storer/object.go b/plumbing/storer/object.go index d8a9c27a6..126b3742d 100644 --- a/plumbing/storer/object.go +++ b/plumbing/storer/object.go @@ -42,6 +42,7 @@ type EncodedObjectStorer interface { HasEncodedObject(plumbing.Hash) error // EncodedObjectSize returns the plaintext size of the encoded object. EncodedObjectSize(plumbing.Hash) (int64, error) + AddAlternate(remote string) error } // DeltaObjectStorer is an EncodedObjectStorer that can return delta diff --git a/plumbing/storer/object_test.go b/plumbing/storer/object_test.go index 30424ffd3..f2e6a5e05 100644 --- a/plumbing/storer/object_test.go +++ b/plumbing/storer/object_test.go @@ -168,3 +168,7 @@ func (o *MockObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (EncodedOb func (o *MockObjectStorage) Begin() Transaction { return nil } + +func (o *MockObjectStorage) AddAlternate(remote string) error { + return nil +} diff --git a/repository.go b/repository.go index 013b53f80..48988383d 100644 --- a/repository.go +++ b/repository.go @@ -22,6 +22,7 @@ import ( "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/internal/path_util" "github.com/go-git/go-git/v5/internal/revision" + "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" formatcfg "github.com/go-git/go-git/v5/plumbing/format/config" @@ -62,6 +63,7 @@ var ( ErrUnableToResolveCommit = errors.New("unable to resolve commit") ErrPackedObjectsNotSupported = errors.New("packed objects not supported") ErrSHA256NotSupported = errors.New("go-git was not compiled with SHA256 support") + ErrAlternatePathNotSupported = errors.New("alternate path must use the file scheme") ) // Repository represents a git repository @@ -887,6 +889,30 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { return err } + // When the repository to clone is on the local machine, + // instead of using hard links, automatically setup .git/objects/info/alternates + // to share the objects with the source repository + if o.Shared { + if !url.IsLocalEndpoint(o.URL) { + return ErrAlternatePathNotSupported + } + altpath := o.URL + remoteRepo, err := PlainOpen(o.URL) + if err != nil { + return fmt.Errorf("failed to open remote repository: %w", err) + } + conf, err := remoteRepo.Config() + if err != nil { + return fmt.Errorf("failed to read remote repository configuration: %w", err) + } + if !conf.Core.IsBare { + altpath = path.Join(altpath, GitDirName) + } + if err := r.Storer.AddAlternate(altpath); err != nil { + return fmt.Errorf("failed to add alternate file to git objects dir: %w", err) + } + } + ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{ RefSpecs: c.Fetch, Depth: o.Depth, diff --git a/repository_test.go b/repository_test.go index 3154f1d1c..f6839b6da 100644 --- a/repository_test.go +++ b/repository_test.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "os/user" + "path" "path/filepath" "regexp" "strings" @@ -791,6 +792,101 @@ func (s *RepositorySuite) TestPlainClone(c *C) { c.Assert(cfg.Branches["master"].Name, Equals, "master") } +func (s *RepositorySuite) TestPlainCloneBareAndShared(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + remote := s.GetBasicLocalRepositoryURL() + + r, err := PlainClone(dir, true, &CloneOptions{ + URL: remote, + Shared: true, + }) + c.Assert(err, IsNil) + + altpath := path.Join(dir, "objects", "info", "alternates") + _, err = os.Stat(altpath) + c.Assert(err, IsNil) + + data, err := os.ReadFile(altpath) + c.Assert(err, IsNil) + + line := path.Join(remote, GitDirName, "objects") + "\n" + c.Assert(string(data), Equals, line) + + cfg, err := r.Config() + c.Assert(err, IsNil) + c.Assert(cfg.Branches, HasLen, 1) + c.Assert(cfg.Branches["master"].Name, Equals, "master") +} + +func (s *RepositorySuite) TestPlainCloneShared(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + remote := s.GetBasicLocalRepositoryURL() + + r, err := PlainClone(dir, false, &CloneOptions{ + URL: remote, + Shared: true, + }) + c.Assert(err, IsNil) + + altpath := path.Join(dir, GitDirName, "objects", "info", "alternates") + _, err = os.Stat(altpath) + c.Assert(err, IsNil) + + data, err := os.ReadFile(altpath) + c.Assert(err, IsNil) + + line := path.Join(remote, GitDirName, "objects") + "\n" + c.Assert(string(data), Equals, line) + + cfg, err := r.Config() + c.Assert(err, IsNil) + c.Assert(cfg.Branches, HasLen, 1) + c.Assert(cfg.Branches["master"].Name, Equals, "master") +} + +func (s *RepositorySuite) TestPlainCloneSharedHttpShouldReturnError(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + remote := "http://somerepo" + + _, err := PlainClone(dir, false, &CloneOptions{ + URL: remote, + Shared: true, + }) + c.Assert(err, Equals, ErrAlternatePathNotSupported) +} + +func (s *RepositorySuite) TestPlainCloneSharedHttpsShouldReturnError(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + remote := "https://somerepo" + + _, err := PlainClone(dir, false, &CloneOptions{ + URL: remote, + Shared: true, + }) + c.Assert(err, Equals, ErrAlternatePathNotSupported) +} + +func (s *RepositorySuite) TestPlainCloneSharedSSHShouldReturnError(c *C) { + dir, clean := s.TemporalDir() + defer clean() + + remote := "ssh://somerepo" + + _, err := PlainClone(dir, false, &CloneOptions{ + URL: remote, + Shared: true, + }) + c.Assert(err, Equals, ErrAlternatePathNotSupported) +} + func (s *RepositorySuite) TestPlainCloneWithRemoteName(c *C) { dir, clean := s.TemporalDir() defer clean() diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go index e02e6ddfd..3080e4acc 100644 --- a/storage/filesystem/dotgit/dotgit.go +++ b/storage/filesystem/dotgit/dotgit.go @@ -8,7 +8,9 @@ import ( "fmt" "io" "os" + "path" "path/filepath" + "runtime" "sort" "strings" "time" @@ -38,6 +40,7 @@ const ( remotesPath = "remotes" logsPath = "logs" worktreesPath = "worktrees" + alternatesPath = "alternates" tmpPackedRefsPrefix = "._packed-refs" @@ -1105,10 +1108,38 @@ func (d *DotGit) Module(name string) (billy.Filesystem, error) { return d.fs.Chroot(d.fs.Join(modulePath, name)) } +func (d *DotGit) AddAlternate(remote string) error { + altpath := d.fs.Join(objectsPath, infoPath, alternatesPath) + + f, err := d.fs.OpenFile(altpath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640) + if err != nil { + return fmt.Errorf("cannot open file: %w", err) + } + defer f.Close() + + // locking in windows throws an error, based on comments + // https://github.com/go-git/go-git/pull/860#issuecomment-1751823044 + // do not lock on windows platform. + if runtime.GOOS != "windows" { + if err = f.Lock(); err != nil { + return fmt.Errorf("cannot lock file: %w", err) + } + defer f.Unlock() + } + + line := path.Join(remote, objectsPath) + "\n" + _, err = io.WriteString(f, line) + if err != nil { + return fmt.Errorf("error writing 'alternates' file: %w", err) + } + + return nil +} + // Alternates returns DotGit(s) based off paths in objects/info/alternates if // available. This can be used to checks if it's a shared repository. func (d *DotGit) Alternates() ([]*DotGit, error) { - altpath := d.fs.Join("objects", "info", "alternates") + altpath := d.fs.Join(objectsPath, infoPath, alternatesPath) f, err := d.fs.Open(altpath) if err != nil { return nil, err diff --git a/storage/filesystem/storage.go b/storage/filesystem/storage.go index 7e7a2c50f..2069d3a6f 100644 --- a/storage/filesystem/storage.go +++ b/storage/filesystem/storage.go @@ -74,3 +74,7 @@ func (s *Storage) Filesystem() billy.Filesystem { func (s *Storage) Init() error { return s.dir.Initialize() } + +func (s *Storage) AddAlternate(remote string) error { + return s.dir.AddAlternate(remote) +} diff --git a/storage/memory/storage.go b/storage/memory/storage.go index ef6a44551..79211c7c0 100644 --- a/storage/memory/storage.go +++ b/storage/memory/storage.go @@ -202,6 +202,10 @@ func (o *ObjectStorage) DeleteLooseObject(plumbing.Hash) error { return errNotSupported } +func (o *ObjectStorage) AddAlternate(remote string) error { + return errNotSupported +} + type TxObjectStorage struct { Storage *ObjectStorage Objects map[plumbing.Hash]plumbing.EncodedObject diff --git a/storage/transactional/object.go b/storage/transactional/object.go index 5d102b0e1..b43c96d3b 100644 --- a/storage/transactional/object.go +++ b/storage/transactional/object.go @@ -82,3 +82,7 @@ func (o *ObjectStorage) Commit() error { return err }) } + +func (o *ObjectStorage) AddAlternate(remote string) error { + return o.temporal.AddAlternate(remote) +} From 946bb8183643bdda90810fc48e450a008894b244 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 30 Sep 2023 11:41:23 +0100 Subject: [PATCH 163/205] plumbing: commitgraph, fix types and handle commit-graph-chains Unfortunately the original variant makes some incorrect typing assumptions about commit-graphs which make handling graph chains difficult to do correctly. This creates a new subpackage and deprecates the old one. It then adds support commit graph chains. Signed-off-by: Andrew Thornton --- go.mod | 4 +- go.sum | 16 +- plumbing/format/commitgraph/commitgraph.go | 6 + plumbing/format/commitgraph/doc.go | 113 +++--- plumbing/format/commitgraph/encoder.go | 9 + plumbing/format/commitgraph/file.go | 6 + plumbing/format/commitgraph/memory.go | 6 + plumbing/format/commitgraph/v2/chain.go | 100 ++++++ plumbing/format/commitgraph/v2/chain_test.go | 100 ++++++ plumbing/format/commitgraph/v2/chunk.go | 48 +++ plumbing/format/commitgraph/v2/commitgraph.go | 40 +++ .../format/commitgraph/v2/commitgraph_test.go | 165 +++++++++ plumbing/format/commitgraph/v2/doc.go | 106 ++++++ plumbing/format/commitgraph/v2/encoder.go | 192 ++++++++++ plumbing/format/commitgraph/v2/file.go | 338 ++++++++++++++++++ plumbing/format/commitgraph/v2/memory.go | 91 +++++ .../object/commitgraph/commitnode_graph.go | 264 +++++++------- .../object/commitgraph/commitnode_object.go | 180 +++++----- .../object/commitgraph/commitnode_test.go | 9 +- .../commitgraph/commitnode_walker_ctime.go | 210 +++++------ 20 files changed, 1607 insertions(+), 396 deletions(-) create mode 100644 plumbing/format/commitgraph/v2/chain.go create mode 100644 plumbing/format/commitgraph/v2/chain_test.go create mode 100644 plumbing/format/commitgraph/v2/chunk.go create mode 100644 plumbing/format/commitgraph/v2/commitgraph.go create mode 100644 plumbing/format/commitgraph/v2/commitgraph_test.go create mode 100644 plumbing/format/commitgraph/v2/doc.go create mode 100644 plumbing/format/commitgraph/v2/encoder.go create mode 100644 plumbing/format/commitgraph/v2/file.go create mode 100644 plumbing/format/commitgraph/v2/memory.go diff --git a/go.mod b/go.mod index 8de45b2be..9df2020f1 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 github.com/go-git/go-billy/v5 v5.5.0 - github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f + github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231007200033-41cf6f1b6389 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/go-cmp v0.5.9 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 @@ -25,7 +25,7 @@ require ( github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.13.0 golang.org/x/net v0.15.0 - golang.org/x/sys v0.12.0 + golang.org/x/sys v0.13.0 golang.org/x/text v0.13.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) diff --git a/go.sum b/go.sum index 8deb90eaa..7c62b1094 100644 --- a/go.sum +++ b/go.sum @@ -30,11 +30,10 @@ 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.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231007200033-41cf6f1b6389 h1:AlfdJ8f+G+4a4fXeHmAlKfyR3Yup4sVGCXlh+e+TrE8= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231007200033-41cf6f1b6389/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -55,7 +54,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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= @@ -76,8 +74,7 @@ github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2Iqp 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= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -109,7 +106,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 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= 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= @@ -124,8 +120,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.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= @@ -151,13 +147,11 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58 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= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/plumbing/format/commitgraph/commitgraph.go b/plumbing/format/commitgraph/commitgraph.go index 3d59323f3..e772d2636 100644 --- a/plumbing/format/commitgraph/commitgraph.go +++ b/plumbing/format/commitgraph/commitgraph.go @@ -8,6 +8,9 @@ import ( // CommitData is a reduced representation of Commit as presented in the commit graph // file. It is merely useful as an optimization for walking the commit graphs. +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. type CommitData struct { // TreeHash is the hash of the root tree of the commit. TreeHash plumbing.Hash @@ -24,6 +27,9 @@ type CommitData struct { // Index represents a representation of commit graph that allows indexed // access to the nodes using commit object hash +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. type Index interface { // GetIndexByHash gets the index in the commit graph from commit hash, if available GetIndexByHash(h plumbing.Hash) (int, error) diff --git a/plumbing/format/commitgraph/doc.go b/plumbing/format/commitgraph/doc.go index 41cd8b1e3..c320e1811 100644 --- a/plumbing/format/commitgraph/doc.go +++ b/plumbing/format/commitgraph/doc.go @@ -1,23 +1,26 @@ // Package commitgraph implements encoding and decoding of commit-graph files. // +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. +// // Git commit graph format // ======================= // // The Git commit graph stores a list of commit OIDs and some associated // metadata, including: // -// - The generation number of the commit. Commits with no parents have -// generation number 1; commits with parents have generation number -// one more than the maximum generation number of its parents. We -// reserve zero as special, and can be used to mark a generation -// number invalid or as "not computed". +// - The generation number of the commit. Commits with no parents have +// generation number 1; commits with parents have generation number +// one more than the maximum generation number of its parents. We +// reserve zero as special, and can be used to mark a generation +// number invalid or as "not computed". // // - The root tree OID. // // - The commit date. // -// - The parents of the commit, stored using positional references within -// the graph file. +// - The parents of the commit, stored using positional references within +// the graph file. // // These positional references are stored as unsigned 32-bit integers // corresponding to the array position within the list of commit OIDs. Due @@ -35,68 +38,68 @@ // // HEADER: // -// 4-byte signature: -// The signature is: {'C', 'G', 'P', 'H'} +// 4-byte signature: +// The signature is: {'C', 'G', 'P', 'H'} // -// 1-byte version number: -// Currently, the only valid version is 1. +// 1-byte version number: +// Currently, the only valid version is 1. // -// 1-byte Hash Version (1 = SHA-1) -// We infer the hash length (H) from this value. +// 1-byte Hash Version (1 = SHA-1) +// We infer the hash length (H) from this value. // -// 1-byte number (C) of "chunks" +// 1-byte number (C) of "chunks" // -// 1-byte (reserved for later use) -// Current clients should ignore this value. +// 1-byte (reserved for later use) +// Current clients should ignore this value. // // CHUNK LOOKUP: // -// (C + 1) * 12 bytes listing the table of contents for the chunks: -// First 4 bytes describe the chunk id. Value 0 is a terminating label. -// Other 8 bytes provide the byte-offset in current file for chunk to -// start. (Chunks are ordered contiguously in the file, so you can infer -// the length using the next chunk position if necessary.) Each chunk -// ID appears at most once. +// (C + 1) * 12 bytes listing the table of contents for the chunks: +// First 4 bytes describe the chunk id. Value 0 is a terminating label. +// Other 8 bytes provide the byte-offset in current file for chunk to +// start. (Chunks are ordered contiguously in the file, so you can infer +// the length using the next chunk position if necessary.) Each chunk +// ID appears at most once. // -// The remaining data in the body is described one chunk at a time, and -// these chunks may be given in any order. Chunks are required unless -// otherwise specified. +// The remaining data in the body is described one chunk at a time, and +// these chunks may be given in any order. Chunks are required unless +// otherwise specified. // // CHUNK DATA: // -// OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes) -// The ith entry, F[i], stores the number of OIDs with first -// byte at most i. Thus F[255] stores the total -// number of commits (N). -// -// OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes) -// The OIDs for all commits in the graph, sorted in ascending order. -// -// Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes) -// * The first H bytes are for the OID of the root tree. -// * The next 8 bytes are for the positions of the first two parents -// of the ith commit. Stores value 0x7000000 if no parent in that -// position. If there are more than two parents, the second value -// has its most-significant bit on and the other bits store an array -// position into the Extra Edge List chunk. -// * The next 8 bytes store the generation number of the commit and -// the commit time in seconds since EPOCH. The generation number -// uses the higher 30 bits of the first 4 bytes, while the commit -// time uses the 32 bits of the second 4 bytes, along with the lowest -// 2 bits of the lowest byte, storing the 33rd and 34th bit of the -// commit time. -// -// Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] -// This list of 4-byte values store the second through nth parents for -// all octopus merges. The second parent value in the commit data stores -// an array position within this list along with the most-significant bit -// on. Starting at that array position, iterate through this list of commit -// positions for the parents until reaching a value with the most-significant -// bit on. The other bits correspond to the position of the last parent. +// OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes) +// The ith entry, F[i], stores the number of OIDs with first +// byte at most i. Thus F[255] stores the total +// number of commits (N). +// +// OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes) +// The OIDs for all commits in the graph, sorted in ascending order. +// +// Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes) +// * The first H bytes are for the OID of the root tree. +// * The next 8 bytes are for the positions of the first two parents +// of the ith commit. Stores value 0x7000000 if no parent in that +// position. If there are more than two parents, the second value +// has its most-significant bit on and the other bits store an array +// position into the Extra Edge List chunk. +// * The next 8 bytes store the generation number of the commit and +// the commit time in seconds since EPOCH. The generation number +// uses the higher 30 bits of the first 4 bytes, while the commit +// time uses the 32 bits of the second 4 bytes, along with the lowest +// 2 bits of the lowest byte, storing the 33rd and 34th bit of the +// commit time. +// +// Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] +// This list of 4-byte values store the second through nth parents for +// all octopus merges. The second parent value in the commit data stores +// an array position within this list along with the most-significant bit +// on. Starting at that array position, iterate through this list of commit +// positions for the parents until reaching a value with the most-significant +// bit on. The other bits correspond to the position of the last parent. // // TRAILER: // -// H-byte HASH-checksum of all of the above. +// H-byte HASH-checksum of all of the above. // // Source: // https://raw.githubusercontent.com/git/git/master/Documentation/technical/commit-graph-format.txt diff --git a/plumbing/format/commitgraph/encoder.go b/plumbing/format/commitgraph/encoder.go index 674f52e7c..317635384 100644 --- a/plumbing/format/commitgraph/encoder.go +++ b/plumbing/format/commitgraph/encoder.go @@ -10,12 +10,18 @@ import ( ) // Encoder writes MemoryIndex structs to an output stream. +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. type Encoder struct { io.Writer hash hash.Hash } // NewEncoder returns a new stream encoder that writes to w. +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. func NewEncoder(w io.Writer) *Encoder { h := hash.New(hash.CryptoType) mw := io.MultiWriter(w, h) @@ -23,6 +29,9 @@ func NewEncoder(w io.Writer) *Encoder { } // Encode writes an index into the commit-graph file +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. func (e *Encoder) Encode(idx Index) error { // Get all the hashes in the input index hashes := idx.Hashes() diff --git a/plumbing/format/commitgraph/file.go b/plumbing/format/commitgraph/file.go index 17c1c5d11..ef8fb3496 100644 --- a/plumbing/format/commitgraph/file.go +++ b/plumbing/format/commitgraph/file.go @@ -13,6 +13,9 @@ import ( "github.com/go-git/go-git/v5/utils/binary" ) +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. + var ( // ErrUnsupportedVersion is returned by OpenFileIndex when the commit graph // file version is not supported. @@ -51,6 +54,9 @@ type fileIndex struct { // OpenFileIndex opens a serialized commit graph file in the format described at // https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. func OpenFileIndex(reader io.ReaderAt) (Index, error) { fi := &fileIndex{reader: reader} diff --git a/plumbing/format/commitgraph/memory.go b/plumbing/format/commitgraph/memory.go index b24ce36d9..06415e515 100644 --- a/plumbing/format/commitgraph/memory.go +++ b/plumbing/format/commitgraph/memory.go @@ -6,12 +6,18 @@ import ( // MemoryIndex provides a way to build the commit-graph in memory // for later encoding to file. +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. type MemoryIndex struct { commitData []*CommitData indexMap map[plumbing.Hash]int } // NewMemoryIndex creates in-memory commit graph representation +// +// Deprecated: This package uses the wrong types for Generation and Index in CommitData. +// Use the v2 package instead. func NewMemoryIndex() *MemoryIndex { return &MemoryIndex{ indexMap: make(map[plumbing.Hash]int), diff --git a/plumbing/format/commitgraph/v2/chain.go b/plumbing/format/commitgraph/v2/chain.go new file mode 100644 index 000000000..8da60d01b --- /dev/null +++ b/plumbing/format/commitgraph/v2/chain.go @@ -0,0 +1,100 @@ +package v2 + +import ( + "bufio" + "io" + "path" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-git/v5/plumbing" +) + +// OpenChainFile reads a commit chain file and returns a slice of the hashes within it +// +// Commit-Graph chains are described at https://git-scm.com/docs/commit-graph +// and are new line separated list of graph file hashes, oldest to newest. +// +// This function simply reads the file and returns the hashes as a slice. +func OpenChainFile(r io.Reader) ([]string, error) { + if r == nil { + return nil, io.ErrUnexpectedEOF + } + bufRd := bufio.NewReader(r) + chain := make([]string, 0, 8) + for { + line, err := bufRd.ReadSlice('\n') + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + hashStr := string(line[:len(line)-1]) + if !plumbing.IsHash(hashStr) { + return nil, ErrMalformedCommitGraphFile + } + chain = append(chain, hashStr) + } + return chain, nil +} + +// OpenChainOrFileIndex expects a billy.Filesystem representing a .git directory. +// It will first attempt to read a commit-graph index file, before trying to read a +// commit-graph chain file and its index files. If neither are present, an error is returned. +// Otherwise an Index will be returned. +// +// See: https://git-scm.com/docs/commit-graph +func OpenChainOrFileIndex(fs billy.Filesystem) (Index, error) { + file, err := fs.Open(path.Join("objects", "info", "commit-graph")) + if err != nil { + // try to open a chain file + return OpenChainIndex(fs) + } + + index, err := OpenFileIndex(file) + if err != nil { + // Ignore any file closing errors and return the error from OpenFileIndex instead + _ = file.Close() + return nil, err + } + return index, nil +} + +// OpenChainIndex expects a billy.Filesystem representing a .git directory. +// It will read a commit-graph chain file and return a coalesced index. +// If the chain file or a graph in that chain is not present, an error is returned. +// +// See: https://git-scm.com/docs/commit-graph +func OpenChainIndex(fs billy.Filesystem) (Index, error) { + chainFile, err := fs.Open(path.Join("objects", "info", "commit-graphs", "commit-graph-chain")) + if err != nil { + return nil, err + } + + chain, err := OpenChainFile(chainFile) + _ = chainFile.Close() + if err != nil { + return nil, err + } + + var index Index + for _, hash := range chain { + + file, err := fs.Open(path.Join("objects", "info", "commit-graphs", "graph-"+hash+".graph")) + if err != nil { + // Ignore all other file closing errors and return the error from opening the last file in the graph + _ = index.Close() + return nil, err + } + + index, err = OpenFileIndexWithParent(file, index) + if err != nil { + // Ignore file closing errors and return the error from OpenFileIndex instead + _ = index.Close() + return nil, err + } + } + + return index, nil +} diff --git a/plumbing/format/commitgraph/v2/chain_test.go b/plumbing/format/commitgraph/v2/chain_test.go new file mode 100644 index 000000000..32ffd69e1 --- /dev/null +++ b/plumbing/format/commitgraph/v2/chain_test.go @@ -0,0 +1,100 @@ +package v2_test + +import ( + "bytes" + "crypto" + "strings" + + commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" + "github.com/go-git/go-git/v5/plumbing/hash" + + . "gopkg.in/check.v1" +) + +func (s *CommitgraphSuite) TestOpenChainFile(c *C) { + sha1Data := []string{ + "c336d16298a017486c4164c40f8acb28afe64e84", + "31eae7b619d166c366bf5df4991f04ba8cebea0a", + "b977a025ca21e3b5ca123d8093bd7917694f6da7", + "d2a38b4a5965d529566566640519d03d2bd10f6c", + "35b585759cbf29f8ec428ef89da20705d59f99ec", + "c2bbf9fe8009b22d0f390f3c8c3f13937067590f", + "fc9f0643b21cfe571046e27e0c4565f3a1ee96c8", + "c088fd6a7e1a38e9d5a9815265cb575bb08d08ff", + "5fddbeb678bd2c36c5e5c891ab8f2b143ced5baf", + "5d7303c49ac984a9fec60523f2d5297682e16646", + } + + sha256Data := []string{ + "b9efda7160f2647e0974ca623f8a8f8e25fb6944f1b8f78f4db1bf07932de8eb", + "7095c59f8bf46e12c21d2d9da344cfe383fae18d26f3ae4d4ab7b71e3d0ddfae", + "25a395cb62f7656294e40a001ee19fefcdf3013d265dfcf4b744cd2549891dec", + "7fbd564813a82227507d9dd70f1fd21fc1f180223cd3f42e0c3090c9a8b6a7d0", + "aa95db1db2df91bd7200a892dd1c03bc2704c4793400d016b3ca08c148b0f7c1", + "2176988184b570565dc33823a02f474ad59f667a0e971c86063a7fea64776a87", + "d0afc0e64171140eb7902110f807a1beaa38a603d4312fd4bd14a5db2784ba62", + "2822136f60bfc58bbd9d624cc19fbef9f0fc0efe2a61729242e1e5f9b77fa3d0", + "6f207b5c43463af96bc38c43b0bf45275fa327e656a8bba8e7fc55c5ab6870d8", + "6cf33782619b6ff0af9c081e46323f423f8b49bf3d043887c0549bef47d60f55", + "60ea0753d2d4e828983528294be3f57e2a3ba37df4f59e3236133c9e2b17afc5", + "6b3c9f4ba5092e0807774097953ec6e9f58e8371d775bd8738a0fa98d728ba3d", + "c97cab8564054e30515dbe67dda4e14638aabf17b3f042d18dc8461cd098b362", + "9f7ece76fd2c9dae08e75176347efffc1446ad74af66004dd34680edb205dfb5", + "23e7a7e481b00571b63c2a7d0432f9733dd85d18a9841a3d7b96743100da5824", + "e684b1253fa8eb6572f35bab2fd3b6efecabf8472ede43497cd9c171973cc341", + "8b9f04080b0c40f7ad2a6bb5e5296cd6c06e730dffce87a0375ae7bd0f85f86e", + "384a745f3b14edc89526a98b96b3247b2b548541c755aadee7664352ed7f12ae", + "b68c8a82cd5b839917e1058570a0408819b81d16dbab81db118cc8dfc3def044", + "fbaf04f1a401335be57e172f4326102c658d857fde6cf2bc987520d11fc99770", + "57acf2aa5ac736337b120c951536c8a2b2cb23a4f0f198e86f3433370fa63105", + "dd7fcba4c13b6ced0b6190cdb5861adcd08446a92d67f7ec0f02f9533e09bbb0", + "744ef481c9b13ebd3b6e43d7e9ba25f7c7a5c8e453e6f0d50f5d71aae1591689", + "2c573142f1edd52b64dcd42a9c3b0ca5c9c615f757d80d25bfb02ff3eb2257e2", + "ea65cc58ef8520cd0335de4318a0d3b3a1ac257b7e9f82e12483fa3bce6cc0cd", + "1dfa626ff1523b82e21a4c29476edcdc9a89842f3c7181f63a28cd4f46cc9923", + "aa1153e71af836121e6f6cc716cf64880c19221d8dc367ff42359de1b8ef30e9", + "a7c6ec6f6569e22d2fa6e8281639d27c59b633ea00ad8ef27a43171cc985fbda", + "627b706d63d2cfd5a388deeaa76655ef09146fe492ee17cb0043578cef9c2800", + "d40eaf091ef8357b734d1047a552436eaf057d99a0c6f2068b097c324099d360", + "87f0ef81641da4fd3438dcaae4819f0c92a0ade54e262b21f9ded4575ff3f234", + "3a00a29e08d29454b5197662f70ccab5699b0ce8c85af7fbf511b8915d97cfd0", + } + + goodShas := sha1Data + badShas := sha256Data + if hash.CryptoType == crypto.SHA256 { + goodShas = sha256Data + badShas = sha1Data + } + chainData := strings.Join(goodShas, "\n") + "\n" + + chainReader := strings.NewReader(chainData) + + chain, err := commitgraph.OpenChainFile(chainReader) + c.Assert(err, IsNil) + c.Assert(goodShas, DeepEquals, chain) + + // Test with bad shas + chainData = strings.Join(badShas, "\n") + "\n" + + chainReader = strings.NewReader(chainData) + + chain, err = commitgraph.OpenChainFile(chainReader) + c.Assert(err, Equals, commitgraph.ErrMalformedCommitGraphFile) + c.Assert(chain, IsNil) + + // Test with empty file + emptyChainReader := bytes.NewReader(nil) + + chain, err = commitgraph.OpenChainFile(emptyChainReader) + c.Assert(err, IsNil) + c.Assert(chain, DeepEquals, []string{}) + + // Test with file containing only newlines + newlineChainData := []byte("\n\n\n") + newlineChainReader := bytes.NewReader(newlineChainData) + + chain, err = commitgraph.OpenChainFile(newlineChainReader) + c.Assert(err, Equals, commitgraph.ErrMalformedCommitGraphFile) + c.Assert(chain, IsNil) +} diff --git a/plumbing/format/commitgraph/v2/chunk.go b/plumbing/format/commitgraph/v2/chunk.go new file mode 100644 index 000000000..ab2432072 --- /dev/null +++ b/plumbing/format/commitgraph/v2/chunk.go @@ -0,0 +1,48 @@ +package v2 + +import "bytes" + +const ( + chunkSigLen = 4 // Length of a chunk signature + chunkSigOffset = 4 // Offset of each chunk signature in chunkSignatures +) + +// chunkSignatures contains the coalesced byte signatures for each chunk type. +// The order of the signatures must match the order of the ChunkType constants. +// (When adding new chunk types you must avoid introducing ambiguity, and you may need to add padding separators to this list or reorder these signatures.) +// (i.e. it would not be possible to add a new chunk type with the signature "IDFO" without some reordering or the addition of separators.) +var chunkSignatures = []byte("OIDFOIDLCDATGDA2GDO2EDGEBIDXBDATBASE\000\000\000\000") + +// ChunkType represents the type of a chunk in the commit graph file. +type ChunkType int + +const ( + OIDFanoutChunk ChunkType = iota // "OIDF" + OIDLookupChunk // "OIDL" + CommitDataChunk // "CDAT" + GenerationDataChunk // "GDA2" + GenerationDataOverflowChunk // "GDO2" + ExtraEdgeListChunk // "EDGE" + BloomFilterIndexChunk // "BIDX" + BloomFilterDataChunk // "BDAT" + BaseGraphsListChunk // "BASE" + ZeroChunk // "\000\000\000\000" +) + +// Signature returns the byte signature for the chunk type. +func (ct ChunkType) Signature() []byte { + if ct >= BaseGraphsListChunk || ct < 0 { // not a valid chunk type just return ZeroChunk + return chunkSignatures[ZeroChunk*chunkSigOffset : ZeroChunk*chunkSigOffset+chunkSigLen] + } + + return chunkSignatures[ct*chunkSigOffset : ct*chunkSigOffset+chunkSigLen] +} + +// ChunkTypeFromBytes returns the chunk type for the given byte signature. +func ChunkTypeFromBytes(b []byte) (ChunkType, bool) { + idx := bytes.Index(chunkSignatures, b) + if idx == -1 || idx%chunkSigOffset != 0 { // not found, or not aligned at chunkSigOffset + return -1, false + } + return ChunkType(idx / chunkSigOffset), true +} diff --git a/plumbing/format/commitgraph/v2/commitgraph.go b/plumbing/format/commitgraph/v2/commitgraph.go new file mode 100644 index 000000000..7c67b6395 --- /dev/null +++ b/plumbing/format/commitgraph/v2/commitgraph.go @@ -0,0 +1,40 @@ +package v2 + +import ( + "io" + "time" + + "github.com/go-git/go-git/v5/plumbing" +) + +// CommitData is a reduced representation of Commit as presented in the commit graph +// file. It is merely useful as an optimization for walking the commit graphs. +type CommitData struct { + // TreeHash is the hash of the root tree of the commit. + TreeHash plumbing.Hash + // ParentIndexes are the indexes of the parent commits of the commit. + ParentIndexes []uint32 + // ParentHashes are the hashes of the parent commits of the commit. + ParentHashes []plumbing.Hash + // Generation number is the pre-computed generation in the commit graph + // or zero if not available. + Generation uint64 + // When is the timestamp of the commit. + When time.Time +} + +// Index represents a representation of commit graph that allows indexed +// access to the nodes using commit object hash +type Index interface { + // GetIndexByHash gets the index in the commit graph from commit hash, if available + GetIndexByHash(h plumbing.Hash) (uint32, error) + // GetHashByIndex gets the hash given an index in the commit graph + GetHashByIndex(i uint32) (plumbing.Hash, error) + // GetNodeByIndex gets the commit node from the commit graph using index + // obtained from child node, if available + GetCommitDataByIndex(i uint32) (*CommitData, error) + // Hashes returns all the hashes that are available in the index + Hashes() []plumbing.Hash + + io.Closer +} diff --git a/plumbing/format/commitgraph/v2/commitgraph_test.go b/plumbing/format/commitgraph/v2/commitgraph_test.go new file mode 100644 index 000000000..69fdcd9fd --- /dev/null +++ b/plumbing/format/commitgraph/v2/commitgraph_test.go @@ -0,0 +1,165 @@ +package v2_test + +import ( + "os" + "testing" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/util" + "github.com/go-git/go-git/v5/plumbing" + commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" + + fixtures "github.com/go-git/go-git-fixtures/v4" + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type CommitgraphSuite struct { + fixtures.Suite +} + +var _ = Suite(&CommitgraphSuite{}) + +func testReadIndex(c *C, fs billy.Filesystem, path string) commitgraph.Index { + reader, err := fs.Open(path) + c.Assert(err, IsNil) + index, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + c.Assert(index, NotNil) + return index +} + +func testDecodeHelper(c *C, index commitgraph.Index) { + // Root commit + nodeIndex, err := index.GetIndexByHash(plumbing.NewHash("347c91919944a68e9413581a1bc15519550a3afe")) + c.Assert(err, IsNil) + commitData, err := index.GetCommitDataByIndex(nodeIndex) + c.Assert(err, IsNil) + c.Assert(len(commitData.ParentIndexes), Equals, 0) + c.Assert(len(commitData.ParentHashes), Equals, 0) + + // Regular commit + nodeIndex, err = index.GetIndexByHash(plumbing.NewHash("e713b52d7e13807e87a002e812041f248db3f643")) + c.Assert(err, IsNil) + commitData, err = index.GetCommitDataByIndex(nodeIndex) + c.Assert(err, IsNil) + c.Assert(len(commitData.ParentIndexes), Equals, 1) + c.Assert(len(commitData.ParentHashes), Equals, 1) + c.Assert(commitData.ParentHashes[0].String(), Equals, "347c91919944a68e9413581a1bc15519550a3afe") + + // Merge commit + nodeIndex, err = index.GetIndexByHash(plumbing.NewHash("b29328491a0682c259bcce28741eac71f3499f7d")) + c.Assert(err, IsNil) + commitData, err = index.GetCommitDataByIndex(nodeIndex) + c.Assert(err, IsNil) + c.Assert(len(commitData.ParentIndexes), Equals, 2) + c.Assert(len(commitData.ParentHashes), Equals, 2) + c.Assert(commitData.ParentHashes[0].String(), Equals, "e713b52d7e13807e87a002e812041f248db3f643") + c.Assert(commitData.ParentHashes[1].String(), Equals, "03d2c021ff68954cf3ef0a36825e194a4b98f981") + + // Octopus merge commit + nodeIndex, err = index.GetIndexByHash(plumbing.NewHash("6f6c5d2be7852c782be1dd13e36496dd7ad39560")) + c.Assert(err, IsNil) + commitData, err = index.GetCommitDataByIndex(nodeIndex) + c.Assert(err, IsNil) + c.Assert(len(commitData.ParentIndexes), Equals, 3) + c.Assert(len(commitData.ParentHashes), Equals, 3) + c.Assert(commitData.ParentHashes[0].String(), Equals, "ce275064ad67d51e99f026084e20827901a8361c") + c.Assert(commitData.ParentHashes[1].String(), Equals, "bb13916df33ed23004c3ce9ed3b8487528e655c1") + c.Assert(commitData.ParentHashes[2].String(), Equals, "a45273fe2d63300e1962a9e26a6b15c276cd7082") + + // Check all hashes + hashes := index.Hashes() + c.Assert(len(hashes), Equals, 11) + c.Assert(hashes[0].String(), Equals, "03d2c021ff68954cf3ef0a36825e194a4b98f981") + c.Assert(hashes[10].String(), Equals, "e713b52d7e13807e87a002e812041f248db3f643") +} + +func (s *CommitgraphSuite) TestDecode(c *C) { + fixtures.ByTag("commit-graph").Test(c, func(f *fixtures.Fixture) { + dotgit := f.DotGit() + index := testReadIndex(c, dotgit, dotgit.Join("objects", "info", "commit-graph")) + defer index.Close() + testDecodeHelper(c, index) + }) +} + +func (s *CommitgraphSuite) TestDecodeChain(c *C) { + fixtures.ByTag("commit-graph").Test(c, func(f *fixtures.Fixture) { + dotgit := f.DotGit() + index, err := commitgraph.OpenChainOrFileIndex(dotgit) + c.Assert(err, IsNil) + defer index.Close() + testDecodeHelper(c, index) + }) + + fixtures.ByTag("commit-graph-chain").Test(c, func(f *fixtures.Fixture) { + dotgit := f.DotGit() + index, err := commitgraph.OpenChainOrFileIndex(dotgit) + c.Assert(err, IsNil) + defer index.Close() + testDecodeHelper(c, index) + }) +} + +func (s *CommitgraphSuite) TestReencode(c *C) { + fixtures.ByTag("commit-graph").Test(c, func(f *fixtures.Fixture) { + dotgit := f.DotGit() + + reader, err := dotgit.Open(dotgit.Join("objects", "info", "commit-graph")) + c.Assert(err, IsNil) + defer reader.Close() + index, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + defer index.Close() + + writer, err := util.TempFile(dotgit, "", "commit-graph") + c.Assert(err, IsNil) + tmpName := writer.Name() + defer os.Remove(tmpName) + + encoder := commitgraph.NewEncoder(writer) + err = encoder.Encode(index) + c.Assert(err, IsNil) + writer.Close() + + tmpIndex := testReadIndex(c, dotgit, tmpName) + defer tmpIndex.Close() + testDecodeHelper(c, tmpIndex) + }) +} + +func (s *CommitgraphSuite) TestReencodeInMemory(c *C) { + fixtures.ByTag("commit-graph").Test(c, func(f *fixtures.Fixture) { + dotgit := f.DotGit() + + reader, err := dotgit.Open(dotgit.Join("objects", "info", "commit-graph")) + c.Assert(err, IsNil) + index, err := commitgraph.OpenFileIndex(reader) + c.Assert(err, IsNil) + + memoryIndex := commitgraph.NewMemoryIndex() + defer memoryIndex.Close() + for i, hash := range index.Hashes() { + commitData, err := index.GetCommitDataByIndex(uint32(i)) + c.Assert(err, IsNil) + memoryIndex.Add(hash, commitData) + } + index.Close() + + writer, err := util.TempFile(dotgit, "", "commit-graph") + c.Assert(err, IsNil) + tmpName := writer.Name() + defer os.Remove(tmpName) + + encoder := commitgraph.NewEncoder(writer) + err = encoder.Encode(memoryIndex) + c.Assert(err, IsNil) + writer.Close() + + tmpIndex := testReadIndex(c, dotgit, tmpName) + defer tmpIndex.Close() + testDecodeHelper(c, tmpIndex) + }) +} diff --git a/plumbing/format/commitgraph/v2/doc.go b/plumbing/format/commitgraph/v2/doc.go new file mode 100644 index 000000000..157621dc2 --- /dev/null +++ b/plumbing/format/commitgraph/v2/doc.go @@ -0,0 +1,106 @@ +// Package v2 implements encoding and decoding of commit-graph files. +// +// This package was created to work around the issues of the incorrect types in +// the commitgraph package. +// +// Git commit graph format +// ======================= +// +// The Git commit graph stores a list of commit OIDs and some associated +// metadata, including: +// +// - The generation number of the commit. Commits with no parents have +// generation number 1; commits with parents have generation number +// one more than the maximum generation number of its parents. We +// reserve zero as special, and can be used to mark a generation +// number invalid or as "not computed". +// +// - The root tree OID. +// +// - The commit date. +// +// - The parents of the commit, stored using positional references within +// the graph file. +// +// These positional references are stored as unsigned 32-bit integers +// corresponding to the array position within the list of commit OIDs. Due +// to some special constants we use to track parents, we can store at most +// (1 << 30) + (1 << 29) + (1 << 28) - 1 (around 1.8 billion) commits. +// +// == Commit graph files have the following format: +// +// In order to allow extensions that add extra data to the graph, we organize +// the body into "chunks" and provide a binary lookup table at the beginning +// of the body. The header includes certain values, such as number of chunks +// and hash type. +// +// All 4-byte numbers are in network order. +// +// HEADER: +// +// 4-byte signature: +// The signature is: {'C', 'G', 'P', 'H'} +// +// 1-byte version number: +// Currently, the only valid version is 1. +// +// 1-byte Hash Version (1 = SHA-1) +// We infer the hash length (H) from this value. +// +// 1-byte number (C) of "chunks" +// +// 1-byte (reserved for later use) +// Current clients should ignore this value. +// +// CHUNK LOOKUP: +// +// (C + 1) * 12 bytes listing the table of contents for the chunks: +// First 4 bytes describe the chunk id. Value 0 is a terminating label. +// Other 8 bytes provide the byte-offset in current file for chunk to +// start. (Chunks are ordered contiguously in the file, so you can infer +// the length using the next chunk position if necessary.) Each chunk +// ID appears at most once. +// +// The remaining data in the body is described one chunk at a time, and +// these chunks may be given in any order. Chunks are required unless +// otherwise specified. +// +// CHUNK DATA: +// +// OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes) +// The ith entry, F[i], stores the number of OIDs with first +// byte at most i. Thus F[255] stores the total +// number of commits (N). +// +// OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes) +// The OIDs for all commits in the graph, sorted in ascending order. +// +// Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes) +// * The first H bytes are for the OID of the root tree. +// * The next 8 bytes are for the positions of the first two parents +// of the ith commit. Stores value 0x7000000 if no parent in that +// position. If there are more than two parents, the second value +// has its most-significant bit on and the other bits store an array +// position into the Extra Edge List chunk. +// * The next 8 bytes store the generation number of the commit and +// the commit time in seconds since EPOCH. The generation number +// uses the higher 30 bits of the first 4 bytes, while the commit +// time uses the 32 bits of the second 4 bytes, along with the lowest +// 2 bits of the lowest byte, storing the 33rd and 34th bit of the +// commit time. +// +// Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] +// This list of 4-byte values store the second through nth parents for +// all octopus merges. The second parent value in the commit data stores +// an array position within this list along with the most-significant bit +// on. Starting at that array position, iterate through this list of commit +// positions for the parents until reaching a value with the most-significant +// bit on. The other bits correspond to the position of the last parent. +// +// TRAILER: +// +// H-byte HASH-checksum of all of the above. +// +// Source: +// https://raw.githubusercontent.com/git/git/master/Documentation/technical/commit-graph-format.txt +package v2 diff --git a/plumbing/format/commitgraph/v2/encoder.go b/plumbing/format/commitgraph/v2/encoder.go new file mode 100644 index 000000000..d1e41f892 --- /dev/null +++ b/plumbing/format/commitgraph/v2/encoder.go @@ -0,0 +1,192 @@ +package v2 + +import ( + "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" +) + +// Encoder writes MemoryIndex structs to an output stream. +type Encoder struct { + io.Writer + hash hash.Hash +} + +// NewEncoder returns a new stream encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + h := hash.New(hash.CryptoType) + mw := io.MultiWriter(w, h) + return &Encoder{mw, h} +} + +// Encode writes an index into the commit-graph file +func (e *Encoder) Encode(idx Index) error { + // Get all the hashes in the input index + hashes := idx.Hashes() + + // Sort the inout and prepare helper structures we'll need for encoding + hashToIndex, fanout, extraEdgesCount := e.prepare(idx, hashes) + + chunkSignatures := [][]byte{OIDFanoutChunk.Signature(), OIDLookupChunk.Signature(), CommitDataChunk.Signature()} + chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * hash.Size, uint64(len(hashes)) * (hash.Size + commitDataSize)} + if extraEdgesCount > 0 { + chunkSignatures = append(chunkSignatures, ExtraEdgeListChunk.Signature()) + chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*4) + } + + if err := e.encodeFileHeader(len(chunkSignatures)); err != nil { + return err + } + if err := e.encodeChunkHeaders(chunkSignatures, chunkSizes); err != nil { + return err + } + if err := e.encodeFanout(fanout); err != nil { + return err + } + if err := e.encodeOidLookup(hashes); err != nil { + return err + } + if extraEdges, err := e.encodeCommitData(hashes, hashToIndex, idx); err == nil { + if err = e.encodeExtraEdges(extraEdges); err != nil { + return err + } + } else { + return err + } + + return e.encodeChecksum() +} + +func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[plumbing.Hash]uint32, fanout []uint32, extraEdgesCount uint32) { + // Sort the hashes and build our index + plumbing.HashesSort(hashes) + hashToIndex = make(map[plumbing.Hash]uint32) + fanout = make([]uint32, 256) + for i, hash := range hashes { + hashToIndex[hash] = uint32(i) + fanout[hash[0]]++ + } + + // Convert the fanout to cumulative values + for i := 1; i <= 0xff; i++ { + fanout[i] += fanout[i-1] + } + + // Find out if we will need extra edge table + for i := 0; i < len(hashes); i++ { + v, _ := idx.GetCommitDataByIndex(uint32(i)) + if len(v.ParentHashes) > 2 { + extraEdgesCount += uint32(len(v.ParentHashes) - 1) + break + } + } + + return +} + +func (e *Encoder) encodeFileHeader(chunkCount int) (err error) { + if _, err = e.Write(commitFileSignature); err == nil { + version := byte(1) + if hash.CryptoType == crypto.SHA256 { + version = byte(2) + } + _, err = e.Write([]byte{1, version, byte(chunkCount), 0}) + } + return +} + +func (e *Encoder) encodeChunkHeaders(chunkSignatures [][]byte, chunkSizes []uint64) (err error) { + // 8 bytes of file header, 12 bytes for each chunk header and 12 byte for terminator + offset := uint64(8 + len(chunkSignatures)*12 + 12) + for i, signature := range chunkSignatures { + if _, err = e.Write(signature); err == nil { + err = binary.WriteUint64(e, offset) + } + if err != nil { + return + } + offset += chunkSizes[i] + } + if _, err = e.Write(ZeroChunk.Signature()); err == nil { + err = binary.WriteUint64(e, offset) + } + return +} + +func (e *Encoder) encodeFanout(fanout []uint32) (err error) { + for i := 0; i <= 0xff; i++ { + if err = binary.WriteUint32(e, fanout[i]); err != nil { + return + } + } + return +} + +func (e *Encoder) encodeOidLookup(hashes []plumbing.Hash) (err error) { + for _, hash := range hashes { + if _, err = e.Write(hash[:]); err != nil { + return err + } + } + return +} + +func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumbing.Hash]uint32, idx Index) (extraEdges []uint32, err error) { + for _, hash := range hashes { + origIndex, _ := idx.GetIndexByHash(hash) + commitData, _ := idx.GetCommitDataByIndex(origIndex) + if _, err = e.Write(commitData.TreeHash[:]); err != nil { + return + } + + var parent1, parent2 uint32 + if len(commitData.ParentHashes) == 0 { + parent1 = parentNone + parent2 = parentNone + } else if len(commitData.ParentHashes) == 1 { + parent1 = hashToIndex[commitData.ParentHashes[0]] + parent2 = parentNone + } else if len(commitData.ParentHashes) == 2 { + parent1 = hashToIndex[commitData.ParentHashes[0]] + parent2 = hashToIndex[commitData.ParentHashes[1]] + } else if len(commitData.ParentHashes) > 2 { + parent1 = hashToIndex[commitData.ParentHashes[0]] + parent2 = uint32(len(extraEdges)) | parentOctopusUsed + for _, parentHash := range commitData.ParentHashes[1:] { + extraEdges = append(extraEdges, hashToIndex[parentHash]) + } + extraEdges[len(extraEdges)-1] |= parentLast + } + + if err = binary.WriteUint32(e, parent1); err == nil { + err = binary.WriteUint32(e, parent2) + } + if err != nil { + return + } + + unixTime := uint64(commitData.When.Unix()) + unixTime |= uint64(commitData.Generation) << 34 + if err = binary.WriteUint64(e, unixTime); err != nil { + return + } + } + return +} + +func (e *Encoder) encodeExtraEdges(extraEdges []uint32) (err error) { + for _, parent := range extraEdges { + if err = binary.WriteUint32(e, parent); err != nil { + return + } + } + return +} + +func (e *Encoder) encodeChecksum() error { + _, err := e.Write(e.hash.Sum(nil)[:hash.Size]) + return err +} diff --git a/plumbing/format/commitgraph/v2/file.go b/plumbing/format/commitgraph/v2/file.go new file mode 100644 index 000000000..69e02508e --- /dev/null +++ b/plumbing/format/commitgraph/v2/file.go @@ -0,0 +1,338 @@ +package v2 + +import ( + "bytes" + "crypto" + encbin "encoding/binary" + "errors" + "io" + "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" +) + +var ( + // ErrUnsupportedVersion is returned by OpenFileIndex when the commit graph + // file version is not supported. + 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") + // ErrMalformedCommitGraphFile is returned by OpenFileIndex when the commit + // graph file is corrupted. + ErrMalformedCommitGraphFile = errors.New("malformed commit graph file") + + commitFileSignature = []byte{'C', 'G', 'P', 'H'} + + parentNone = uint32(0x70000000) + parentOctopusUsed = uint32(0x80000000) + parentOctopusMask = uint32(0x7fffffff) + parentLast = uint32(0x80000000) +) + +const ( + commitDataSize = 16 +) + +type fileIndex struct { + reader ReaderAtCloser + fanout [256]uint32 + offsets [9]int64 + parent Index +} + +// ReaderAtCloser is an interface that combines io.ReaderAt and io.Closer. +type ReaderAtCloser interface { + io.ReaderAt + io.Closer +} + +// OpenFileIndex opens a serialized commit graph file in the format described at +// https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt +func OpenFileIndex(reader ReaderAtCloser) (Index, error) { + return OpenFileIndexWithParent(reader, nil) +} + +// OpenFileIndexWithParent opens a serialized commit graph file in the format described at +// https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt +func OpenFileIndexWithParent(reader ReaderAtCloser, parent Index) (Index, error) { + if reader == nil { + return nil, io.ErrUnexpectedEOF + } + fi := &fileIndex{reader: reader, parent: parent} + + if err := fi.verifyFileHeader(); err != nil { + return nil, err + } + if err := fi.readChunkHeaders(); err != nil { + return nil, err + } + if err := fi.readFanout(); err != nil { + return nil, err + } + + return fi, nil +} + +// Close closes the underlying reader and the parent index if it exists. +func (fi *fileIndex) Close() (err error) { + if fi.parent != nil { + defer func() { + parentErr := fi.parent.Close() + // only report the error from the parent if there is no error from the reader + if err == nil { + err = parentErr + } + }() + } + err = fi.reader.Close() + return +} + +func (fi *fileIndex) verifyFileHeader() error { + // Verify file signature + signature := make([]byte, 4) + if _, err := fi.reader.ReadAt(signature, 0); err != nil { + return err + } + if !bytes.Equal(signature, commitFileSignature) { + return ErrMalformedCommitGraphFile + } + + // Read and verify the file header + header := make([]byte, 4) + if _, err := fi.reader.ReadAt(header, 4); err != nil { + return err + } + if header[0] != 1 { + return ErrUnsupportedVersion + } + if !(hash.CryptoType == crypto.SHA1 && header[1] == 1) && + !(hash.CryptoType == crypto.SHA256 && header[1] == 2) { + // Unknown hash type / unsupported hash type + return ErrUnsupportedHash + } + + return nil +} + +func (fi *fileIndex) readChunkHeaders() error { + chunkID := make([]byte, 4) + for i := 0; ; i++ { + chunkHeader := io.NewSectionReader(fi.reader, 8+(int64(i)*12), 12) + if _, err := io.ReadAtLeast(chunkHeader, chunkID, 4); err != nil { + return err + } + chunkOffset, err := binary.ReadUint64(chunkHeader) + if err != nil { + return err + } + + chunkType, ok := ChunkTypeFromBytes(chunkID) + if !ok { + continue + } + if chunkType == ZeroChunk || int(chunkType) >= len(fi.offsets) { + break + } + fi.offsets[chunkType] = int64(chunkOffset) + } + + if fi.offsets[OIDFanoutChunk] <= 0 || fi.offsets[OIDLookupChunk] <= 0 || fi.offsets[CommitDataChunk] <= 0 { + return ErrMalformedCommitGraphFile + } + + return nil +} + +func (fi *fileIndex) readFanout() error { + fanoutReader := io.NewSectionReader(fi.reader, fi.offsets[OIDFanoutChunk], 256*4) + for i := 0; i < 256; i++ { + fanoutValue, err := binary.ReadUint32(fanoutReader) + if err != nil { + return err + } + if fanoutValue > 0x7fffffff { + return ErrMalformedCommitGraphFile + } + fi.fanout[i] = fanoutValue + } + return nil +} + +// GetIndexByHash looks up the provided hash in the commit-graph fanout and returns the index of the commit data for the given hash. +func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (uint32, error) { + var oid plumbing.Hash + + // Find the hash in the oid lookup table + var low uint32 + if h[0] == 0 { + low = 0 + } else { + low = fi.fanout[h[0]-1] + } + high := fi.fanout[h[0]] + for low < high { + mid := (low + high) >> 1 + offset := fi.offsets[OIDLookupChunk] + int64(mid)*hash.Size + if _, err := fi.reader.ReadAt(oid[:], offset); err != nil { + return 0, err + } + cmp := bytes.Compare(h[:], oid[:]) + if cmp < 0 { + high = mid + } else if cmp == 0 { + return mid, nil + } else { + low = mid + 1 + } + } + + if fi.parent != nil { + idx, err := fi.parent.GetIndexByHash(h) + if err != nil { + return 0, err + } + return idx + fi.fanout[0xff], nil + } + + return 0, plumbing.ErrObjectNotFound +} + +// GetCommitDataByIndex returns the commit data for the given index in the commit-graph. +func (fi *fileIndex) GetCommitDataByIndex(idx uint32) (*CommitData, error) { + if idx >= fi.fanout[0xff] { + if fi.parent != nil { + data, err := fi.parent.GetCommitDataByIndex(idx - fi.fanout[0xff]) + if err != nil { + return nil, err + } + for i := range data.ParentIndexes { + data.ParentIndexes[i] += fi.fanout[0xff] + } + return data, nil + } + + return nil, plumbing.ErrObjectNotFound + } + + offset := fi.offsets[CommitDataChunk] + int64(idx)*(hash.Size+commitDataSize) + commitDataReader := io.NewSectionReader(fi.reader, offset, hash.Size+commitDataSize) + + treeHash, err := binary.ReadHash(commitDataReader) + if err != nil { + return nil, err + } + parent1, err := binary.ReadUint32(commitDataReader) + if err != nil { + return nil, err + } + parent2, err := binary.ReadUint32(commitDataReader) + if err != nil { + return nil, err + } + genAndTime, err := binary.ReadUint64(commitDataReader) + if err != nil { + return nil, err + } + + var parentIndexes []uint32 + if parent2&parentOctopusUsed == parentOctopusUsed { + // Octopus merge + parentIndexes = []uint32{parent1 & parentOctopusMask} + offset := fi.offsets[ExtraEdgeListChunk] + 4*int64(parent2&parentOctopusMask) + buf := make([]byte, 4) + for { + _, err := fi.reader.ReadAt(buf, offset) + if err != nil { + return nil, err + } + + parent := encbin.BigEndian.Uint32(buf) + offset += 4 + parentIndexes = append(parentIndexes, parent&parentOctopusMask) + if parent&parentLast == parentLast { + break + } + } + } else if parent2 != parentNone { + parentIndexes = []uint32{parent1 & parentOctopusMask, parent2 & parentOctopusMask} + } else if parent1 != parentNone { + parentIndexes = []uint32{parent1 & parentOctopusMask} + } + + parentHashes, err := fi.getHashesFromIndexes(parentIndexes) + if err != nil { + return nil, err + } + + return &CommitData{ + TreeHash: treeHash, + ParentIndexes: parentIndexes, + ParentHashes: parentHashes, + Generation: genAndTime >> 34, + When: time.Unix(int64(genAndTime&0x3FFFFFFFF), 0), + }, nil +} + +// GetHashByIndex looks up the hash for the given index in the commit-graph. +func (fi *fileIndex) GetHashByIndex(idx uint32) (found plumbing.Hash, err error) { + if idx >= fi.fanout[0xff] { + if fi.parent != nil { + return fi.parent.GetHashByIndex(idx - fi.fanout[0xff]) + } + return found, ErrMalformedCommitGraphFile + } + + offset := fi.offsets[OIDLookupChunk] + int64(idx)*hash.Size + if _, err := fi.reader.ReadAt(found[:], offset); err != nil { + return found, err + } + + return found, nil +} + +func (fi *fileIndex) getHashesFromIndexes(indexes []uint32) ([]plumbing.Hash, error) { + hashes := make([]plumbing.Hash, len(indexes)) + + for i, idx := range indexes { + if idx >= fi.fanout[0xff] { + if fi.parent != nil { + hash, err := fi.parent.GetHashByIndex(idx - fi.fanout[0xff]) + if err != nil { + return nil, err + } + hashes[i] = hash + continue + } + + return nil, ErrMalformedCommitGraphFile + } + + offset := fi.offsets[OIDLookupChunk] + int64(idx)*hash.Size + if _, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil { + return nil, err + } + } + + return hashes, nil +} + +// Hashes returns all the hashes that are available in the index. +func (fi *fileIndex) Hashes() []plumbing.Hash { + hashes := make([]plumbing.Hash, fi.fanout[0xff]) + for i := uint32(0); i < fi.fanout[0xff]; i++ { + offset := fi.offsets[OIDLookupChunk] + int64(i)*hash.Size + if n, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil || n < hash.Size { + return nil + } + } + if fi.parent != nil { + parentHashes := fi.parent.Hashes() + hashes = append(hashes, parentHashes...) + } + return hashes +} diff --git a/plumbing/format/commitgraph/v2/memory.go b/plumbing/format/commitgraph/v2/memory.go new file mode 100644 index 000000000..ab7ddfad9 --- /dev/null +++ b/plumbing/format/commitgraph/v2/memory.go @@ -0,0 +1,91 @@ +package v2 + +import ( + "github.com/go-git/go-git/v5/plumbing" +) + +// MemoryIndex provides a way to build the commit-graph in memory +// for later encoding to file. +type MemoryIndex struct { + commitData []commitData + indexMap map[plumbing.Hash]uint32 +} + +type commitData struct { + Hash plumbing.Hash + *CommitData +} + +// NewMemoryIndex creates in-memory commit graph representation +func NewMemoryIndex() *MemoryIndex { + return &MemoryIndex{ + indexMap: make(map[plumbing.Hash]uint32), + } +} + +// GetIndexByHash gets the index in the commit graph from commit hash, if available +func (mi *MemoryIndex) GetIndexByHash(h plumbing.Hash) (uint32, error) { + i, ok := mi.indexMap[h] + if ok { + return i, nil + } + + return 0, plumbing.ErrObjectNotFound +} + +// GetHashByIndex gets the hash given an index in the commit graph +func (mi *MemoryIndex) GetHashByIndex(i uint32) (plumbing.Hash, error) { + if i >= uint32(len(mi.commitData)) { + return plumbing.ZeroHash, plumbing.ErrObjectNotFound + } + + return mi.commitData[i].Hash, nil +} + +// GetCommitDataByIndex gets the commit node from the commit graph using index +// obtained from child node, if available +func (mi *MemoryIndex) GetCommitDataByIndex(i uint32) (*CommitData, error) { + if i >= uint32(len(mi.commitData)) { + return nil, plumbing.ErrObjectNotFound + } + + commitData := mi.commitData[i] + + // Map parent hashes to parent indexes + if commitData.ParentIndexes == nil { + parentIndexes := make([]uint32, len(commitData.ParentHashes)) + for i, parentHash := range commitData.ParentHashes { + var err error + if parentIndexes[i], err = mi.GetIndexByHash(parentHash); err != nil { + return nil, err + } + } + commitData.ParentIndexes = parentIndexes + } + + return commitData.CommitData, nil +} + +// Hashes returns all the hashes that are available in the index +func (mi *MemoryIndex) Hashes() []plumbing.Hash { + hashes := make([]plumbing.Hash, 0, len(mi.indexMap)) + for k := range mi.indexMap { + hashes = append(hashes, k) + } + return hashes +} + +// Add adds new node to the memory index +func (mi *MemoryIndex) Add(hash plumbing.Hash, data *CommitData) { + // The parent indexes are calculated lazily in GetNodeByIndex + // which allows adding nodes out of order as long as all parents + // are eventually resolved + data.ParentIndexes = nil + mi.indexMap[hash] = uint32(len(mi.commitData)) + mi.commitData = append(mi.commitData, commitData{Hash: hash, CommitData: data}) +} + +// Close closes the index +func (mi *MemoryIndex) Close() error { + return nil +} diff --git a/plumbing/object/commitgraph/commitnode_graph.go b/plumbing/object/commitgraph/commitnode_graph.go index 8e5d4e34a..252b5181e 100644 --- a/plumbing/object/commitgraph/commitnode_graph.go +++ b/plumbing/object/commitgraph/commitnode_graph.go @@ -1,131 +1,133 @@ -package commitgraph - -import ( - "fmt" - "time" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/format/commitgraph" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/plumbing/storer" -) - -// graphCommitNode is a reduced representation of Commit as presented in the commit -// graph file (commitgraph.Node). It is merely useful as an optimization for walking -// the commit graphs. -// -// graphCommitNode implements the CommitNode interface. -type graphCommitNode struct { - // Hash for the Commit object - hash plumbing.Hash - // Index of the node in the commit graph file - index int - - commitData *commitgraph.CommitData - gci *graphCommitNodeIndex -} - -// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit -// graph files and the object store. -// -// graphCommitNodeIndex implements the CommitNodeIndex interface -type graphCommitNodeIndex struct { - commitGraph commitgraph.Index - s storer.EncodedObjectStorer -} - -// NewGraphCommitNodeIndex returns CommitNodeIndex implementation that uses commit-graph -// files as backing storage and falls back to object storage when necessary -func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { - return &graphCommitNodeIndex{commitGraph, s} -} - -func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { - // Check the commit graph first - parentIndex, err := gci.commitGraph.GetIndexByHash(hash) - if err == nil { - parent, err := gci.commitGraph.GetCommitDataByIndex(parentIndex) - if err != nil { - return nil, err - } - - return &graphCommitNode{ - hash: hash, - index: parentIndex, - commitData: parent, - gci: gci, - }, nil - } - - // Fallback to loading full commit object - commit, err := object.GetCommit(gci.s, hash) - if err != nil { - return nil, err - } - - return &objectCommitNode{ - nodeIndex: gci, - commit: commit, - }, nil -} - -func (c *graphCommitNode) ID() plumbing.Hash { - return c.hash -} - -func (c *graphCommitNode) Tree() (*object.Tree, error) { - return object.GetTree(c.gci.s, c.commitData.TreeHash) -} - -func (c *graphCommitNode) CommitTime() time.Time { - return c.commitData.When -} - -func (c *graphCommitNode) NumParents() int { - return len(c.commitData.ParentIndexes) -} - -func (c *graphCommitNode) ParentNodes() CommitNodeIter { - return newParentgraphCommitNodeIter(c) -} - -func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { - if i < 0 || i >= len(c.commitData.ParentIndexes) { - return nil, object.ErrParentNotFound - } - - parent, err := c.gci.commitGraph.GetCommitDataByIndex(c.commitData.ParentIndexes[i]) - if err != nil { - return nil, err - } - - return &graphCommitNode{ - hash: c.commitData.ParentHashes[i], - index: c.commitData.ParentIndexes[i], - commitData: parent, - gci: c.gci, - }, nil -} - -func (c *graphCommitNode) ParentHashes() []plumbing.Hash { - return c.commitData.ParentHashes -} - -func (c *graphCommitNode) Generation() uint64 { - // If the commit-graph file was generated with older Git version that - // set the generation to zero for every commit the generation assumption - // is still valid. It is just less useful. - return uint64(c.commitData.Generation) -} - -func (c *graphCommitNode) Commit() (*object.Commit, error) { - return object.GetCommit(c.gci.s, c.hash) -} - -func (c *graphCommitNode) String() string { - return fmt.Sprintf( - "%s %s\nDate: %s", - plumbing.CommitObject, c.ID(), - c.CommitTime().Format(object.DateFormat), - ) -} +package commitgraph + +import ( + "fmt" + "time" + + "github.com/go-git/go-git/v5/plumbing" + commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/storer" +) + +// graphCommitNode is a reduced representation of Commit as presented in the commit +// graph file (commitgraph.Node). It is merely useful as an optimization for walking +// the commit graphs. +// +// graphCommitNode implements the CommitNode interface. +type graphCommitNode struct { + // Hash for the Commit object + hash plumbing.Hash + // Index of the node in the commit graph file + index uint32 + + commitData *commitgraph.CommitData + gci *graphCommitNodeIndex +} + +// graphCommitNodeIndex is an index that can load CommitNode objects from both the commit +// graph files and the object store. +// +// graphCommitNodeIndex implements the CommitNodeIndex interface +type graphCommitNodeIndex struct { + commitGraph commitgraph.Index + s storer.EncodedObjectStorer +} + +// NewGraphCommitNodeIndex returns CommitNodeIndex implementation that uses commit-graph +// files as backing storage and falls back to object storage when necessary +func NewGraphCommitNodeIndex(commitGraph commitgraph.Index, s storer.EncodedObjectStorer) CommitNodeIndex { + return &graphCommitNodeIndex{commitGraph, s} +} + +func (gci *graphCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + if gci.commitGraph != nil { + // Check the commit graph first + parentIndex, err := gci.commitGraph.GetIndexByHash(hash) + if err == nil { + parent, err := gci.commitGraph.GetCommitDataByIndex(parentIndex) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: hash, + index: parentIndex, + commitData: parent, + gci: gci, + }, nil + } + } + + // Fallback to loading full commit object + commit, err := object.GetCommit(gci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{ + nodeIndex: gci, + commit: commit, + }, nil +} + +func (c *graphCommitNode) ID() plumbing.Hash { + return c.hash +} + +func (c *graphCommitNode) Tree() (*object.Tree, error) { + return object.GetTree(c.gci.s, c.commitData.TreeHash) +} + +func (c *graphCommitNode) CommitTime() time.Time { + return c.commitData.When +} + +func (c *graphCommitNode) NumParents() int { + return len(c.commitData.ParentIndexes) +} + +func (c *graphCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) +} + +func (c *graphCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.commitData.ParentIndexes) { + return nil, object.ErrParentNotFound + } + + parent, err := c.gci.commitGraph.GetCommitDataByIndex(c.commitData.ParentIndexes[i]) + if err != nil { + return nil, err + } + + return &graphCommitNode{ + hash: c.commitData.ParentHashes[i], + index: c.commitData.ParentIndexes[i], + commitData: parent, + gci: c.gci, + }, nil +} + +func (c *graphCommitNode) ParentHashes() []plumbing.Hash { + return c.commitData.ParentHashes +} + +func (c *graphCommitNode) Generation() uint64 { + // If the commit-graph file was generated with older Git version that + // set the generation to zero for every commit the generation assumption + // is still valid. It is just less useful. + return c.commitData.Generation +} + +func (c *graphCommitNode) Commit() (*object.Commit, error) { + return object.GetCommit(c.gci.s, c.hash) +} + +func (c *graphCommitNode) String() string { + return fmt.Sprintf( + "%s %s\nDate: %s", + plumbing.CommitObject, c.ID(), + c.CommitTime().Format(object.DateFormat), + ) +} diff --git a/plumbing/object/commitgraph/commitnode_object.go b/plumbing/object/commitgraph/commitnode_object.go index bdf8cb74a..1bd37e3e0 100644 --- a/plumbing/object/commitgraph/commitnode_object.go +++ b/plumbing/object/commitgraph/commitnode_object.go @@ -1,90 +1,90 @@ -package commitgraph - -import ( - "math" - "time" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/plumbing/storer" -) - -// objectCommitNode is a representation of Commit as presented in the GIT object format. -// -// objectCommitNode implements the CommitNode interface. -type objectCommitNode struct { - nodeIndex CommitNodeIndex - commit *object.Commit -} - -// NewObjectCommitNodeIndex returns CommitNodeIndex implementation that uses -// only object storage to load the nodes -func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { - return &objectCommitNodeIndex{s} -} - -func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { - commit, err := object.GetCommit(oci.s, hash) - if err != nil { - return nil, err - } - - return &objectCommitNode{ - nodeIndex: oci, - commit: commit, - }, nil -} - -// objectCommitNodeIndex is an index that can load CommitNode objects only from the -// object store. -// -// objectCommitNodeIndex implements the CommitNodeIndex interface -type objectCommitNodeIndex struct { - s storer.EncodedObjectStorer -} - -func (c *objectCommitNode) CommitTime() time.Time { - return c.commit.Committer.When -} - -func (c *objectCommitNode) ID() plumbing.Hash { - return c.commit.ID() -} - -func (c *objectCommitNode) Tree() (*object.Tree, error) { - return c.commit.Tree() -} - -func (c *objectCommitNode) NumParents() int { - return c.commit.NumParents() -} - -func (c *objectCommitNode) ParentNodes() CommitNodeIter { - return newParentgraphCommitNodeIter(c) -} - -func (c *objectCommitNode) ParentNode(i int) (CommitNode, error) { - if i < 0 || i >= len(c.commit.ParentHashes) { - return nil, object.ErrParentNotFound - } - - // Note: It's necessary to go through CommitNodeIndex here to ensure - // that if the commit-graph file covers only part of the history we - // start using it when that part is reached. - return c.nodeIndex.Get(c.commit.ParentHashes[i]) -} - -func (c *objectCommitNode) ParentHashes() []plumbing.Hash { - return c.commit.ParentHashes -} - -func (c *objectCommitNode) Generation() uint64 { - // Commit nodes representing objects outside of the commit graph can never - // be reached by objects from the commit-graph thus we return the highest - // possible value. - return math.MaxUint64 -} - -func (c *objectCommitNode) Commit() (*object.Commit, error) { - return c.commit, nil -} +package commitgraph + +import ( + "math" + "time" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/storer" +) + +// objectCommitNode is a representation of Commit as presented in the GIT object format. +// +// objectCommitNode implements the CommitNode interface. +type objectCommitNode struct { + nodeIndex CommitNodeIndex + commit *object.Commit +} + +// NewObjectCommitNodeIndex returns CommitNodeIndex implementation that uses +// only object storage to load the nodes +func NewObjectCommitNodeIndex(s storer.EncodedObjectStorer) CommitNodeIndex { + return &objectCommitNodeIndex{s} +} + +func (oci *objectCommitNodeIndex) Get(hash plumbing.Hash) (CommitNode, error) { + commit, err := object.GetCommit(oci.s, hash) + if err != nil { + return nil, err + } + + return &objectCommitNode{ + nodeIndex: oci, + commit: commit, + }, nil +} + +// objectCommitNodeIndex is an index that can load CommitNode objects only from the +// object store. +// +// objectCommitNodeIndex implements the CommitNodeIndex interface +type objectCommitNodeIndex struct { + s storer.EncodedObjectStorer +} + +func (c *objectCommitNode) CommitTime() time.Time { + return c.commit.Committer.When +} + +func (c *objectCommitNode) ID() plumbing.Hash { + return c.commit.ID() +} + +func (c *objectCommitNode) Tree() (*object.Tree, error) { + return c.commit.Tree() +} + +func (c *objectCommitNode) NumParents() int { + return c.commit.NumParents() +} + +func (c *objectCommitNode) ParentNodes() CommitNodeIter { + return newParentgraphCommitNodeIter(c) +} + +func (c *objectCommitNode) ParentNode(i int) (CommitNode, error) { + if i < 0 || i >= len(c.commit.ParentHashes) { + return nil, object.ErrParentNotFound + } + + // Note: It's necessary to go through CommitNodeIndex here to ensure + // that if the commit-graph file covers only part of the history we + // start using it when that part is reached. + return c.nodeIndex.Get(c.commit.ParentHashes[i]) +} + +func (c *objectCommitNode) ParentHashes() []plumbing.Hash { + return c.commit.ParentHashes +} + +func (c *objectCommitNode) Generation() uint64 { + // Commit nodes representing objects outside of the commit graph can never + // be reached by objects from the commit-graph thus we return the highest + // possible value. + return math.MaxUint64 +} + +func (c *objectCommitNode) Commit() (*object.Commit, error) { + return c.commit, nil +} diff --git a/plumbing/object/commitgraph/commitnode_test.go b/plumbing/object/commitgraph/commitnode_test.go index 91fb21117..441ff6f0a 100644 --- a/plumbing/object/commitgraph/commitnode_test.go +++ b/plumbing/object/commitgraph/commitnode_test.go @@ -6,7 +6,7 @@ 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/commitgraph" + commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" "github.com/go-git/go-git/v5/plumbing/format/packfile" "github.com/go-git/go-git/v5/storage/filesystem" @@ -115,6 +115,7 @@ func (s *CommitNodeSuite) TestCommitGraph(c *C) { defer reader.Close() index, err := commitgraph.OpenFileIndex(reader) c.Assert(err, IsNil) + defer index.Close() nodeIndex := NewGraphCommitNodeIndex(index, storer) testWalker(c, nodeIndex) @@ -132,10 +133,14 @@ func (s *CommitNodeSuite) TestMixedGraph(c *C) { defer reader.Close() fileIndex, err := commitgraph.OpenFileIndex(reader) c.Assert(err, IsNil) + defer fileIndex.Close() + memoryIndex := commitgraph.NewMemoryIndex() + defer memoryIndex.Close() + for i, hash := range fileIndex.Hashes() { if hash.String() != "b9d69064b190e7aedccf84731ca1d917871f8a1c" { - node, err := fileIndex.GetCommitDataByIndex(i) + node, err := fileIndex.GetCommitDataByIndex(uint32(i)) c.Assert(err, IsNil) memoryIndex.Add(hash, node) } diff --git a/plumbing/object/commitgraph/commitnode_walker_ctime.go b/plumbing/object/commitgraph/commitnode_walker_ctime.go index 281f10bdf..c26873ce5 100644 --- a/plumbing/object/commitgraph/commitnode_walker_ctime.go +++ b/plumbing/object/commitgraph/commitnode_walker_ctime.go @@ -1,105 +1,105 @@ -package commitgraph - -import ( - "io" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/storer" - - "github.com/emirpasic/gods/trees/binaryheap" -) - -type commitNodeIteratorByCTime struct { - heap *binaryheap.Heap - seenExternal map[plumbing.Hash]bool - seen map[plumbing.Hash]bool -} - -// NewCommitNodeIterCTime returns a CommitNodeIter that walks the commit history, -// starting at the given commit and visiting its parents while preserving Committer Time order. -// this appears to be the closest order to `git log` -// The given callback will be called for each visited commit. Each commit will -// be visited only once. If the callback returns an error, walking will stop -// and will return the error. Other errors might be returned if the history -// cannot be traversed (e.g. missing objects). Ignore allows to skip some -// commits from being iterated. -func NewCommitNodeIterCTime( - c CommitNode, - seenExternal map[plumbing.Hash]bool, - ignore []plumbing.Hash, -) CommitNodeIter { - seen := make(map[plumbing.Hash]bool) - for _, h := range ignore { - seen[h] = true - } - - heap := binaryheap.NewWith(func(a, b interface{}) int { - if a.(CommitNode).CommitTime().Before(b.(CommitNode).CommitTime()) { - return 1 - } - return -1 - }) - - heap.Push(c) - - return &commitNodeIteratorByCTime{ - heap: heap, - seenExternal: seenExternal, - seen: seen, - } -} - -func (w *commitNodeIteratorByCTime) Next() (CommitNode, error) { - var c CommitNode - for { - cIn, ok := w.heap.Pop() - if !ok { - return nil, io.EOF - } - c = cIn.(CommitNode) - cID := c.ID() - - if w.seen[cID] || w.seenExternal[cID] { - continue - } - - w.seen[cID] = true - - for i, h := range c.ParentHashes() { - if w.seen[h] || w.seenExternal[h] { - continue - } - pc, err := c.ParentNode(i) - if err != nil { - return nil, err - } - w.heap.Push(pc) - } - - return c, nil - } -} - -func (w *commitNodeIteratorByCTime) ForEach(cb func(CommitNode) error) error { - for { - c, err := w.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - err = cb(c) - if err == storer.ErrStop { - break - } - if err != nil { - return err - } - } - - return nil -} - -func (w *commitNodeIteratorByCTime) Close() {} +package commitgraph + +import ( + "io" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/storer" + + "github.com/emirpasic/gods/trees/binaryheap" +) + +type commitNodeIteratorByCTime struct { + heap *binaryheap.Heap + seenExternal map[plumbing.Hash]bool + seen map[plumbing.Hash]bool +} + +// NewCommitNodeIterCTime returns a CommitNodeIter that walks the commit history, +// starting at the given commit and visiting its parents while preserving Committer Time order. +// this appears to be the closest order to `git log` +// The given callback will be called for each visited commit. Each commit will +// be visited only once. If the callback returns an error, walking will stop +// and will return the error. Other errors might be returned if the history +// cannot be traversed (e.g. missing objects). Ignore allows to skip some +// commits from being iterated. +func NewCommitNodeIterCTime( + c CommitNode, + seenExternal map[plumbing.Hash]bool, + ignore []plumbing.Hash, +) CommitNodeIter { + seen := make(map[plumbing.Hash]bool) + for _, h := range ignore { + seen[h] = true + } + + heap := binaryheap.NewWith(func(a, b interface{}) int { + if a.(CommitNode).CommitTime().Before(b.(CommitNode).CommitTime()) { + return 1 + } + return -1 + }) + + heap.Push(c) + + return &commitNodeIteratorByCTime{ + heap: heap, + seenExternal: seenExternal, + seen: seen, + } +} + +func (w *commitNodeIteratorByCTime) Next() (CommitNode, error) { + var c CommitNode + for { + cIn, ok := w.heap.Pop() + if !ok { + return nil, io.EOF + } + c = cIn.(CommitNode) + cID := c.ID() + + if w.seen[cID] || w.seenExternal[cID] { + continue + } + + w.seen[cID] = true + + for i, h := range c.ParentHashes() { + if w.seen[h] || w.seenExternal[h] { + continue + } + pc, err := c.ParentNode(i) + if err != nil { + return nil, err + } + w.heap.Push(pc) + } + + return c, nil + } +} + +func (w *commitNodeIteratorByCTime) ForEach(cb func(CommitNode) error) error { + for { + c, err := w.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if err == storer.ErrStop { + break + } + if err != nil { + return err + } + } + + return nil +} + +func (w *commitNodeIteratorByCTime) Close() {} From 1a73661645a39169763648825f4964d0794bef26 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Mon, 9 Oct 2023 22:49:36 +0100 Subject: [PATCH 164/205] build: Add github workflow to check commit message format Adds automation to confirm the commit messages align with the contributing guidelines. Signed-off-by: Paulo Gomes --- .github/workflows/pr-validation.yml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/pr-validation.yml diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 000000000..04e704654 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,30 @@ +name: 'PR Validation' + +on: + pull_request: + types: + - opened + - edited + - reopened + - synchronize + +permissions: + contents: read + +jobs: + check-commit-message: + name: Check Commit Messages + runs-on: ubuntu-latest + steps: + - name: Check Package Prefix + uses: gsactions/commit-message-checker@v2 + with: + pattern: '^(\*|plumbing|utils|config|_examples|internal|storage|cli|build): .+' + error: | + Commit message(s) does not align with contribution acceptance criteria. + + Refer to https://github.com/go-git/go-git/blob/master/CONTRIBUTING.md#format-of-the-commit-message for more information. + excludeDescription: 'true' + excludeTitle: 'true' + checkAllCommitMessages: 'true' + accessToken: ${{ secrets.GITHUB_TOKEN }} From 129b709887b4528ced42c8d74f4c2609800a8942 Mon Sep 17 00:00:00 2001 From: Max Jonas Werner Date: Sat, 7 Oct 2023 00:02:01 +0200 Subject: [PATCH 165/205] plumbing: transport/common, Improve handling of remote errors Instead of simply returning the first line that the remote returned, go-git now actively searches all of stderr for lines that may contain a more actionable error message and returns that. In addition, this change adds a case to map the GitLab-specific error message to an ErrRepositoryNotFound error. Signed-off-by: Max Jonas Werner --- plumbing/transport/internal/common/common.go | 25 ++++++-- .../transport/internal/common/common_test.go | 57 +++++++++++++++++++ plumbing/transport/internal/common/mocks.go | 46 +++++++++++++++ 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 plumbing/transport/internal/common/mocks.go diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index 5fdf4250d..6574116b1 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "regexp" "strings" "time" @@ -28,6 +29,10 @@ const ( var ( ErrTimeoutExceeded = errors.New("timeout exceeded") + // stdErrSkipPattern is used for skipping lines from a command's stderr output. + // Any line matching this pattern will be skipped from further + // processing and not be returned to calling code. + stdErrSkipPattern = regexp.MustCompile("^remote:( =*){0,1}$") ) // Commander creates Command instances. This is the main entry point for @@ -149,10 +154,17 @@ func (c *client) listenFirstError(r io.Reader) chan string { errLine := make(chan string, 1) go func() { s := bufio.NewScanner(r) - if s.Scan() { - errLine <- s.Text() - } else { - close(errLine) + for { + if s.Scan() { + line := s.Text() + if !stdErrSkipPattern.MatchString(line) { + errLine <- line + break + } + } else { + close(errLine) + break + } } _, _ = io.Copy(io.Discard, r) @@ -393,6 +405,7 @@ var ( gitProtocolNoSuchErr = "ERR no such repository" gitProtocolAccessDeniedErr = "ERR access denied" gogsAccessDeniedErr = "Gogs: Repository does not exist or you do not have access" + gitlabRepoNotFoundErr = "remote: ERROR: The project you were looking for could not be found" ) func isRepoNotFoundError(s string) bool { @@ -424,6 +437,10 @@ func isRepoNotFoundError(s string) bool { return true } + if strings.HasPrefix(s, gitlabRepoNotFoundErr) { + return true + } + return false } diff --git a/plumbing/transport/internal/common/common_test.go b/plumbing/transport/internal/common/common_test.go index affa78706..f6f2f67d2 100644 --- a/plumbing/transport/internal/common/common_test.go +++ b/plumbing/transport/internal/common/common_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/go-git/go-git/v5/plumbing/transport" . "gopkg.in/check.v1" ) @@ -77,6 +78,14 @@ func (s *CommonSuite) TestIsRepoNotFoundErrorForGogsAccessDenied(c *C) { c.Assert(isRepoNotFound, Equals, true) } +func (s *CommonSuite) TestIsRepoNotFoundErrorForGitlab(c *C) { + msg := fmt.Sprintf("%s : some error stuf", gitlabRepoNotFoundErr) + + isRepoNotFound := isRepoNotFoundError(msg) + + c.Assert(isRepoNotFound, Equals, true) +} + func (s *CommonSuite) TestCheckNotFoundError(c *C) { firstErrLine := make(chan string, 1) @@ -90,3 +99,51 @@ func (s *CommonSuite) TestCheckNotFoundError(c *C) { c.Assert(err, IsNil) } + +func TestAdvertisedReferencesWithRemoteError(t *testing.T) { + tests := []struct { + name string + stderr string + wantErr error + }{ + { + name: "unknown error", + stderr: "something", + wantErr: fmt.Errorf("unknown error: something"), + }, + { + name: "GitLab: repository not found", + stderr: `remote: +remote: ======================================================================== +remote: +remote: ERROR: The project you were looking for could not be found or you don't have permission to view it. + +remote: +remote: ======================================================================== +remote:`, + wantErr: transport.ErrRepositoryNotFound, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := NewClient(MockCommander{stderr: tt.stderr}) + sess, err := client.NewUploadPackSession(nil, nil) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + _, err = sess.AdvertisedReferences() + + if tt.wantErr != nil { + if tt.wantErr != err { + if tt.wantErr.Error() != err.Error() { + t.Fatalf("expected a different error: got '%s', expected '%s'", err, tt.wantErr) + } + } + } else if err != nil { + t.Fatalf("unexpected error: %s", err) + } + }) + } +} diff --git a/plumbing/transport/internal/common/mocks.go b/plumbing/transport/internal/common/mocks.go new file mode 100644 index 000000000..bc18b27e8 --- /dev/null +++ b/plumbing/transport/internal/common/mocks.go @@ -0,0 +1,46 @@ +package common + +import ( + "bytes" + "io" + + gogitioutil "github.com/go-git/go-git/v5/utils/ioutil" + + "github.com/go-git/go-git/v5/plumbing/transport" +) + +type MockCommand struct { + stdin bytes.Buffer + stdout bytes.Buffer + stderr bytes.Buffer +} + +func (c MockCommand) StderrPipe() (io.Reader, error) { + return &c.stderr, nil +} + +func (c MockCommand) StdinPipe() (io.WriteCloser, error) { + return gogitioutil.WriteNopCloser(&c.stdin), nil +} + +func (c MockCommand) StdoutPipe() (io.Reader, error) { + return &c.stdout, nil +} + +func (c MockCommand) Start() error { + return nil +} + +func (c MockCommand) Close() error { + panic("not implemented") +} + +type MockCommander struct { + stderr string +} + +func (c MockCommander) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod) (Command, error) { + return &MockCommand{ + stderr: *bytes.NewBufferString(c.stderr), + }, nil +} From 69b88d9bda44ebfe1d56a7624b956d9e20818c0e Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sun, 8 Oct 2023 15:49:51 +0100 Subject: [PATCH 166/205] plumbing: commitgraph, Add generation v2 support This PR adds in support for generation v2 support and a couple of new walkers to match --date-order etc options on log. This PR also fixes a bug in the chain code and adds more tests. Signed-off-by: Andrew Thornton --- go.mod | 2 +- go.sum | 4 +- plumbing/format/commitgraph/v2/chunk.go | 7 +- plumbing/format/commitgraph/v2/commitgraph.go | 17 ++ .../format/commitgraph/v2/commitgraph_test.go | 35 ++++ plumbing/format/commitgraph/v2/encoder.go | 84 ++++++-- plumbing/format/commitgraph/v2/file.go | 144 ++++++++++---- plumbing/format/commitgraph/v2/memory.go | 22 ++- plumbing/object/commitgraph/commitnode.go | 4 + .../object/commitgraph/commitnode_graph.go | 7 + .../object/commitgraph/commitnode_object.go | 7 + .../commitnode_walker_author_order.go | 61 ++++++ .../commitgraph/commitnode_walker_ctime.go | 3 +- .../commitnode_walker_date_order.go | 41 ++++ .../commitgraph/commitnode_walker_helper.go | 164 +++++++++++++++ .../commitgraph/commitnode_walker_test.go | 187 ++++++++++++++++++ .../commitnode_walker_topo_order.go | 161 +++++++++++++++ 17 files changed, 892 insertions(+), 58 deletions(-) create mode 100644 plumbing/object/commitgraph/commitnode_walker_author_order.go create mode 100644 plumbing/object/commitgraph/commitnode_walker_date_order.go create mode 100644 plumbing/object/commitgraph/commitnode_walker_helper.go create mode 100644 plumbing/object/commitgraph/commitnode_walker_test.go create mode 100644 plumbing/object/commitgraph/commitnode_walker_topo_order.go diff --git a/go.mod b/go.mod index 9df2020f1..6b7bbdb37 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/gliderlabs/ssh v0.3.5 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 github.com/go-git/go-billy/v5 v5.5.0 - github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231007200033-41cf6f1b6389 + github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/go-cmp v0.5.9 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 diff --git a/go.sum b/go.sum index 7c62b1094..347da85ef 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231007200033-41cf6f1b6389 h1:AlfdJ8f+G+4a4fXeHmAlKfyR3Yup4sVGCXlh+e+TrE8= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231007200033-41cf6f1b6389/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= diff --git a/plumbing/format/commitgraph/v2/chunk.go b/plumbing/format/commitgraph/v2/chunk.go index ab2432072..11f4d3163 100644 --- a/plumbing/format/commitgraph/v2/chunk.go +++ b/plumbing/format/commitgraph/v2/chunk.go @@ -3,7 +3,7 @@ package v2 import "bytes" const ( - chunkSigLen = 4 // Length of a chunk signature + szChunkSig = 4 // Length of a chunk signature chunkSigOffset = 4 // Offset of each chunk signature in chunkSignatures ) @@ -28,14 +28,15 @@ const ( BaseGraphsListChunk // "BASE" ZeroChunk // "\000\000\000\000" ) +const lenChunks = int(ZeroChunk) // ZeroChunk is not a valid chunk type, but it is used to determine the length of the chunk type list. // Signature returns the byte signature for the chunk type. func (ct ChunkType) Signature() []byte { if ct >= BaseGraphsListChunk || ct < 0 { // not a valid chunk type just return ZeroChunk - return chunkSignatures[ZeroChunk*chunkSigOffset : ZeroChunk*chunkSigOffset+chunkSigLen] + return chunkSignatures[ZeroChunk*chunkSigOffset : ZeroChunk*chunkSigOffset+szChunkSig] } - return chunkSignatures[ct*chunkSigOffset : ct*chunkSigOffset+chunkSigLen] + return chunkSignatures[ct*chunkSigOffset : ct*chunkSigOffset+szChunkSig] } // ChunkTypeFromBytes returns the chunk type for the given byte signature. diff --git a/plumbing/format/commitgraph/v2/commitgraph.go b/plumbing/format/commitgraph/v2/commitgraph.go index 7c67b6395..9c89cd9b4 100644 --- a/plumbing/format/commitgraph/v2/commitgraph.go +++ b/plumbing/format/commitgraph/v2/commitgraph.go @@ -2,6 +2,7 @@ package v2 import ( "io" + "math" "time" "github.com/go-git/go-git/v5/plumbing" @@ -19,10 +20,22 @@ type CommitData struct { // Generation number is the pre-computed generation in the commit graph // or zero if not available. Generation uint64 + // GenerationV2 stores the corrected commit date for the commits + // It combines the contents of the GDA2 and GDO2 sections of the commit-graph + // with the commit time portion of the CDAT section. + GenerationV2 uint64 // When is the timestamp of the commit. When time.Time } +// GenerationV2Data returns the corrected commit date for the commits +func (c *CommitData) GenerationV2Data() uint64 { + if c.GenerationV2 == 0 || c.GenerationV2 == math.MaxUint64 { + return 0 + } + return c.GenerationV2 - uint64(c.When.Unix()) +} + // Index represents a representation of commit graph that allows indexed // access to the nodes using commit object hash type Index interface { @@ -35,6 +48,10 @@ type Index interface { GetCommitDataByIndex(i uint32) (*CommitData, error) // Hashes returns all the hashes that are available in the index Hashes() []plumbing.Hash + // HasGenerationV2 returns true if the commit graph has the corrected commit date data + HasGenerationV2() bool + // MaximumNumberOfHashes returns the maximum number of hashes within the index + MaximumNumberOfHashes() uint32 io.Closer } diff --git a/plumbing/format/commitgraph/v2/commitgraph_test.go b/plumbing/format/commitgraph/v2/commitgraph_test.go index 69fdcd9fd..127840567 100644 --- a/plumbing/format/commitgraph/v2/commitgraph_test.go +++ b/plumbing/format/commitgraph/v2/commitgraph_test.go @@ -7,7 +7,11 @@ import ( "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/cache" commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" + "github.com/go-git/go-git/v5/plumbing/format/packfile" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/filesystem" fixtures "github.com/go-git/go-git-fixtures/v4" . "gopkg.in/check.v1" @@ -76,6 +80,37 @@ func testDecodeHelper(c *C, index commitgraph.Index) { c.Assert(hashes[10].String(), Equals, "e713b52d7e13807e87a002e812041f248db3f643") } +func (s *CommitgraphSuite) TestDecodeMultiChain(c *C) { + fixtures.ByTag("commit-graph-chain-2").Test(c, func(f *fixtures.Fixture) { + dotgit := f.DotGit() + index, err := commitgraph.OpenChainOrFileIndex(dotgit) + c.Assert(err, IsNil) + defer index.Close() + storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + p := f.Packfile() + defer p.Close() + packfile.UpdateObjectStorage(storer, p) + + for idx, hash := range index.Hashes() { + idx2, err := index.GetIndexByHash(hash) + c.Assert(err, IsNil) + c.Assert(idx2, Equals, uint32(idx)) + hash2, err := index.GetHashByIndex(idx2) + c.Assert(err, IsNil) + c.Assert(hash2.String(), Equals, hash.String()) + + commitData, err := index.GetCommitDataByIndex(uint32(idx)) + c.Assert(err, IsNil) + commit, err := object.GetCommit(storer, hash) + c.Assert(err, IsNil) + + for i, parent := range commit.ParentHashes { + c.Assert(hash.String()+":"+parent.String(), Equals, hash.String()+":"+commitData.ParentHashes[i].String()) + } + } + }) +} + func (s *CommitgraphSuite) TestDecode(c *C) { fixtures.ByTag("commit-graph").Test(c, func(f *fixtures.Fixture) { dotgit := f.DotGit() diff --git a/plumbing/format/commitgraph/v2/encoder.go b/plumbing/format/commitgraph/v2/encoder.go index d1e41f892..b79bc77f7 100644 --- a/plumbing/format/commitgraph/v2/encoder.go +++ b/plumbing/format/commitgraph/v2/encoder.go @@ -3,6 +3,7 @@ package v2 import ( "crypto" "io" + "math" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/hash" @@ -28,13 +29,21 @@ func (e *Encoder) Encode(idx Index) error { hashes := idx.Hashes() // Sort the inout and prepare helper structures we'll need for encoding - hashToIndex, fanout, extraEdgesCount := e.prepare(idx, hashes) + hashToIndex, fanout, extraEdgesCount, generationV2OverflowCount := e.prepare(idx, hashes) chunkSignatures := [][]byte{OIDFanoutChunk.Signature(), OIDLookupChunk.Signature(), CommitDataChunk.Signature()} - chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * hash.Size, uint64(len(hashes)) * (hash.Size + commitDataSize)} + chunkSizes := []uint64{szUint32 * lenFanout, uint64(len(hashes)) * hash.Size, uint64(len(hashes)) * (hash.Size + szCommitData)} if extraEdgesCount > 0 { chunkSignatures = append(chunkSignatures, ExtraEdgeListChunk.Signature()) - chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*4) + chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*szUint32) + } + if idx.HasGenerationV2() { + chunkSignatures = append(chunkSignatures, GenerationDataChunk.Signature()) + chunkSizes = append(chunkSizes, uint64(len(hashes))*szUint32) + if generationV2OverflowCount > 0 { + chunkSignatures = append(chunkSignatures, GenerationDataOverflowChunk.Signature()) + chunkSizes = append(chunkSizes, uint64(generationV2OverflowCount)*szUint64) + } } if err := e.encodeFileHeader(len(chunkSignatures)); err != nil { @@ -49,38 +58,52 @@ func (e *Encoder) Encode(idx Index) error { if err := e.encodeOidLookup(hashes); err != nil { return err } - if extraEdges, err := e.encodeCommitData(hashes, hashToIndex, idx); err == nil { - if err = e.encodeExtraEdges(extraEdges); err != nil { + + extraEdges, generationV2Data, err := e.encodeCommitData(hashes, hashToIndex, idx) + if err != nil { + return err + } + if err = e.encodeExtraEdges(extraEdges); err != nil { + return err + } + if idx.HasGenerationV2() { + overflows, err := e.encodeGenerationV2Data(generationV2Data) + if err != nil { + return err + } + if err = e.encodeGenerationV2Overflow(overflows); err != nil { return err } - } else { - return err } return e.encodeChecksum() } -func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[plumbing.Hash]uint32, fanout []uint32, extraEdgesCount uint32) { +func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[plumbing.Hash]uint32, fanout []uint32, extraEdgesCount uint32, generationV2OverflowCount uint32) { // Sort the hashes and build our index plumbing.HashesSort(hashes) hashToIndex = make(map[plumbing.Hash]uint32) - fanout = make([]uint32, 256) + fanout = make([]uint32, lenFanout) for i, hash := range hashes { hashToIndex[hash] = uint32(i) fanout[hash[0]]++ } // Convert the fanout to cumulative values - for i := 1; i <= 0xff; i++ { + for i := 1; i < lenFanout; i++ { fanout[i] += fanout[i-1] } + hasGenerationV2 := idx.HasGenerationV2() + // Find out if we will need extra edge table for i := 0; i < len(hashes); i++ { v, _ := idx.GetCommitDataByIndex(uint32(i)) if len(v.ParentHashes) > 2 { extraEdgesCount += uint32(len(v.ParentHashes) - 1) - break + } + if hasGenerationV2 && v.GenerationV2Data() > math.MaxUint32 { + generationV2OverflowCount++ } } @@ -100,7 +123,7 @@ func (e *Encoder) encodeFileHeader(chunkCount int) (err error) { func (e *Encoder) encodeChunkHeaders(chunkSignatures [][]byte, chunkSizes []uint64) (err error) { // 8 bytes of file header, 12 bytes for each chunk header and 12 byte for terminator - offset := uint64(8 + len(chunkSignatures)*12 + 12) + offset := uint64(szSignature + szHeader + (len(chunkSignatures)+1)*(szChunkSig+szUint64)) for i, signature := range chunkSignatures { if _, err = e.Write(signature); err == nil { err = binary.WriteUint64(e, offset) @@ -134,7 +157,10 @@ func (e *Encoder) encodeOidLookup(hashes []plumbing.Hash) (err error) { return } -func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumbing.Hash]uint32, idx Index) (extraEdges []uint32, err error) { +func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumbing.Hash]uint32, idx Index) (extraEdges []uint32, generationV2Data []uint64, err error) { + if idx.HasGenerationV2() { + generationV2Data = make([]uint64, 0, len(hashes)) + } for _, hash := range hashes { origIndex, _ := idx.GetIndexByHash(hash) commitData, _ := idx.GetCommitDataByIndex(origIndex) @@ -173,6 +199,9 @@ func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumb if err = binary.WriteUint64(e, unixTime); err != nil { return } + if generationV2Data != nil { + generationV2Data = append(generationV2Data, commitData.GenerationV2Data()) + } } return } @@ -186,6 +215,35 @@ func (e *Encoder) encodeExtraEdges(extraEdges []uint32) (err error) { return } +func (e *Encoder) encodeGenerationV2Data(generationV2Data []uint64) (overflows []uint64, err error) { + head := 0 + for _, data := range generationV2Data { + if data >= 0x80000000 { + // overflow + if err = binary.WriteUint32(e, uint32(head)|0x80000000); err != nil { + return nil, err + } + generationV2Data[head] = data + head++ + continue + } + if err = binary.WriteUint32(e, uint32(data)); err != nil { + return nil, err + } + } + + return generationV2Data[:head], nil +} + +func (e *Encoder) encodeGenerationV2Overflow(overflows []uint64) (err error) { + for _, overflow := range overflows { + if err = binary.WriteUint64(e, overflow); err != nil { + return + } + } + return +} + func (e *Encoder) encodeChecksum() error { _, err := e.Write(e.hash.Sum(nil)[:hash.Size]) return err diff --git a/plumbing/format/commitgraph/v2/file.go b/plumbing/format/commitgraph/v2/file.go index 69e02508e..c5f61e4de 100644 --- a/plumbing/format/commitgraph/v2/file.go +++ b/plumbing/format/commitgraph/v2/file.go @@ -34,14 +34,23 @@ var ( ) const ( - commitDataSize = 16 + szUint32 = 4 + szUint64 = 8 + + szSignature = 4 + szHeader = 4 + szCommitData = 2*szUint32 + szUint64 + + lenFanout = 256 ) type fileIndex struct { - reader ReaderAtCloser - fanout [256]uint32 - offsets [9]int64 - parent Index + reader ReaderAtCloser + fanout [lenFanout]uint32 + offsets [lenChunks]int64 + parent Index + hasGenerationV2 bool + minimumNumberOfHashes uint32 } // ReaderAtCloser is an interface that combines io.ReaderAt and io.Closer. @@ -74,6 +83,15 @@ func OpenFileIndexWithParent(reader ReaderAtCloser, parent Index) (Index, error) return nil, err } + fi.hasGenerationV2 = fi.offsets[GenerationDataChunk] > 0 + if fi.parent != nil { + fi.hasGenerationV2 = fi.hasGenerationV2 && fi.parent.HasGenerationV2() + } + + if fi.parent != nil { + fi.minimumNumberOfHashes = fi.parent.MaximumNumberOfHashes() + } + return fi, nil } @@ -94,7 +112,7 @@ func (fi *fileIndex) Close() (err error) { func (fi *fileIndex) verifyFileHeader() error { // Verify file signature - signature := make([]byte, 4) + signature := make([]byte, szSignature) if _, err := fi.reader.ReadAt(signature, 0); err != nil { return err } @@ -103,8 +121,8 @@ func (fi *fileIndex) verifyFileHeader() error { } // Read and verify the file header - header := make([]byte, 4) - if _, err := fi.reader.ReadAt(header, 4); err != nil { + header := make([]byte, szHeader) + if _, err := fi.reader.ReadAt(header, szHeader); err != nil { return err } if header[0] != 1 { @@ -120,10 +138,11 @@ func (fi *fileIndex) verifyFileHeader() error { } func (fi *fileIndex) readChunkHeaders() error { - chunkID := make([]byte, 4) + // The chunk table is a list of 4-byte chunk signatures and uint64 offsets into the file + chunkID := make([]byte, szChunkSig) for i := 0; ; i++ { - chunkHeader := io.NewSectionReader(fi.reader, 8+(int64(i)*12), 12) - if _, err := io.ReadAtLeast(chunkHeader, chunkID, 4); err != nil { + chunkHeader := io.NewSectionReader(fi.reader, szSignature+szHeader+(int64(i)*(szChunkSig+szUint64)), szChunkSig+szUint64) + if _, err := io.ReadAtLeast(chunkHeader, chunkID, szChunkSig); err != nil { return err } chunkOffset, err := binary.ReadUint64(chunkHeader) @@ -149,7 +168,9 @@ func (fi *fileIndex) readChunkHeaders() error { } func (fi *fileIndex) readFanout() error { - fanoutReader := io.NewSectionReader(fi.reader, fi.offsets[OIDFanoutChunk], 256*4) + // The Fanout table is a 256 entry table of the number (as uint32) of OIDs with first byte at most i. + // Thus F[255] stores the total number of commits (N) + fanoutReader := io.NewSectionReader(fi.reader, fi.offsets[OIDFanoutChunk], lenFanout*szUint32) for i := 0; i < 256; i++ { fanoutValue, err := binary.ReadUint32(fanoutReader) if err != nil { @@ -185,7 +206,7 @@ func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (uint32, error) { if cmp < 0 { high = mid } else if cmp == 0 { - return mid, nil + return mid + fi.minimumNumberOfHashes, nil } else { low = mid + 1 } @@ -196,7 +217,7 @@ func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (uint32, error) { if err != nil { return 0, err } - return idx + fi.fanout[0xff], nil + return idx, nil } return 0, plumbing.ErrObjectNotFound @@ -204,23 +225,24 @@ func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (uint32, error) { // GetCommitDataByIndex returns the commit data for the given index in the commit-graph. func (fi *fileIndex) GetCommitDataByIndex(idx uint32) (*CommitData, error) { - if idx >= fi.fanout[0xff] { + if idx < fi.minimumNumberOfHashes { if fi.parent != nil { - data, err := fi.parent.GetCommitDataByIndex(idx - fi.fanout[0xff]) + data, err := fi.parent.GetCommitDataByIndex(idx) if err != nil { return nil, err } - for i := range data.ParentIndexes { - data.ParentIndexes[i] += fi.fanout[0xff] - } return data, nil } return nil, plumbing.ErrObjectNotFound } + idx -= fi.minimumNumberOfHashes + if idx >= fi.fanout[0xff] { + return nil, plumbing.ErrObjectNotFound + } - offset := fi.offsets[CommitDataChunk] + int64(idx)*(hash.Size+commitDataSize) - commitDataReader := io.NewSectionReader(fi.reader, offset, hash.Size+commitDataSize) + offset := fi.offsets[CommitDataChunk] + int64(idx)*(hash.Size+szCommitData) + commitDataReader := io.NewSectionReader(fi.reader, offset, hash.Size+szCommitData) treeHash, err := binary.ReadHash(commitDataReader) if err != nil { @@ -241,10 +263,11 @@ func (fi *fileIndex) GetCommitDataByIndex(idx uint32) (*CommitData, error) { var parentIndexes []uint32 if parent2&parentOctopusUsed == parentOctopusUsed { - // Octopus merge + // Octopus merge - Look-up the extra parents from the extra edge list + // The extra edge list is a list of uint32s, each of which is an index into the Commit Data table, terminated by a index with the most significant bit on. parentIndexes = []uint32{parent1 & parentOctopusMask} - offset := fi.offsets[ExtraEdgeListChunk] + 4*int64(parent2&parentOctopusMask) - buf := make([]byte, 4) + offset := fi.offsets[ExtraEdgeListChunk] + szUint32*int64(parent2&parentOctopusMask) + buf := make([]byte, szUint32) for { _, err := fi.reader.ReadAt(buf, offset) if err != nil { @@ -252,7 +275,7 @@ func (fi *fileIndex) GetCommitDataByIndex(idx uint32) (*CommitData, error) { } parent := encbin.BigEndian.Uint32(buf) - offset += 4 + offset += szUint32 parentIndexes = append(parentIndexes, parent&parentOctopusMask) if parent&parentLast == parentLast { break @@ -269,23 +292,57 @@ func (fi *fileIndex) GetCommitDataByIndex(idx uint32) (*CommitData, error) { return nil, err } + generationV2 := uint64(0) + + if fi.hasGenerationV2 { + // set the GenerationV2 result to the commit time + generationV2 = uint64(genAndTime & 0x3FFFFFFFF) + + // Next read the generation (offset) data from the generation data chunk + offset := fi.offsets[GenerationDataChunk] + int64(idx)*szUint32 + buf := make([]byte, szUint32) + if _, err := fi.reader.ReadAt(buf, offset); err != nil { + return nil, err + } + genV2Data := encbin.BigEndian.Uint32(buf) + + // check if the data is an overflow that needs to be looked up in the overflow chunk + if genV2Data&0x80000000 > 0 { + // Overflow + offset := fi.offsets[GenerationDataOverflowChunk] + int64(genV2Data&0x7fffffff)*szUint64 + buf := make([]byte, 8) + if _, err := fi.reader.ReadAt(buf, offset); err != nil { + return nil, err + } + + generationV2 += encbin.BigEndian.Uint64(buf) + } else { + generationV2 += uint64(genV2Data) + } + } + return &CommitData{ TreeHash: treeHash, ParentIndexes: parentIndexes, ParentHashes: parentHashes, Generation: genAndTime >> 34, + GenerationV2: generationV2, When: time.Unix(int64(genAndTime&0x3FFFFFFFF), 0), }, nil } // GetHashByIndex looks up the hash for the given index in the commit-graph. func (fi *fileIndex) GetHashByIndex(idx uint32) (found plumbing.Hash, err error) { - if idx >= fi.fanout[0xff] { + if idx < fi.minimumNumberOfHashes { if fi.parent != nil { - return fi.parent.GetHashByIndex(idx - fi.fanout[0xff]) + return fi.parent.GetHashByIndex(idx) } return found, ErrMalformedCommitGraphFile } + idx -= fi.minimumNumberOfHashes + if idx >= fi.fanout[0xff] { + return found, ErrMalformedCommitGraphFile + } offset := fi.offsets[OIDLookupChunk] + int64(idx)*hash.Size if _, err := fi.reader.ReadAt(found[:], offset); err != nil { @@ -299,9 +356,9 @@ func (fi *fileIndex) getHashesFromIndexes(indexes []uint32) ([]plumbing.Hash, er hashes := make([]plumbing.Hash, len(indexes)) for i, idx := range indexes { - if idx >= fi.fanout[0xff] { + if idx < fi.minimumNumberOfHashes { if fi.parent != nil { - hash, err := fi.parent.GetHashByIndex(idx - fi.fanout[0xff]) + hash, err := fi.parent.GetHashByIndex(idx) if err != nil { return nil, err } @@ -312,6 +369,11 @@ func (fi *fileIndex) getHashesFromIndexes(indexes []uint32) ([]plumbing.Hash, er return nil, ErrMalformedCommitGraphFile } + idx -= fi.minimumNumberOfHashes + if idx >= fi.fanout[0xff] { + return nil, ErrMalformedCommitGraphFile + } + offset := fi.offsets[OIDLookupChunk] + int64(idx)*hash.Size if _, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil { return nil, err @@ -323,16 +385,28 @@ func (fi *fileIndex) getHashesFromIndexes(indexes []uint32) ([]plumbing.Hash, er // Hashes returns all the hashes that are available in the index. func (fi *fileIndex) Hashes() []plumbing.Hash { - hashes := make([]plumbing.Hash, fi.fanout[0xff]) + hashes := make([]plumbing.Hash, fi.fanout[0xff]+fi.minimumNumberOfHashes) + for i := uint32(0); i < fi.minimumNumberOfHashes; i++ { + hash, err := fi.parent.GetHashByIndex(i) + if err != nil { + return nil + } + hashes[i] = hash + } + for i := uint32(0); i < fi.fanout[0xff]; i++ { offset := fi.offsets[OIDLookupChunk] + int64(i)*hash.Size - if n, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil || n < hash.Size { + if n, err := fi.reader.ReadAt(hashes[i+fi.minimumNumberOfHashes][:], offset); err != nil || n < hash.Size { return nil } } - if fi.parent != nil { - parentHashes := fi.parent.Hashes() - hashes = append(hashes, parentHashes...) - } return hashes } + +func (fi *fileIndex) HasGenerationV2() bool { + return fi.hasGenerationV2 +} + +func (fi *fileIndex) MaximumNumberOfHashes() uint32 { + return fi.minimumNumberOfHashes + fi.fanout[0xff] +} diff --git a/plumbing/format/commitgraph/v2/memory.go b/plumbing/format/commitgraph/v2/memory.go index ab7ddfad9..8de0c5f08 100644 --- a/plumbing/format/commitgraph/v2/memory.go +++ b/plumbing/format/commitgraph/v2/memory.go @@ -1,14 +1,17 @@ package v2 import ( + "math" + "github.com/go-git/go-git/v5/plumbing" ) // MemoryIndex provides a way to build the commit-graph in memory // for later encoding to file. type MemoryIndex struct { - commitData []commitData - indexMap map[plumbing.Hash]uint32 + commitData []commitData + indexMap map[plumbing.Hash]uint32 + hasGenerationV2 bool } type commitData struct { @@ -19,7 +22,8 @@ type commitData struct { // NewMemoryIndex creates in-memory commit graph representation func NewMemoryIndex() *MemoryIndex { return &MemoryIndex{ - indexMap: make(map[plumbing.Hash]uint32), + indexMap: make(map[plumbing.Hash]uint32), + hasGenerationV2: true, } } @@ -83,9 +87,21 @@ func (mi *MemoryIndex) Add(hash plumbing.Hash, data *CommitData) { data.ParentIndexes = nil mi.indexMap[hash] = uint32(len(mi.commitData)) mi.commitData = append(mi.commitData, commitData{Hash: hash, CommitData: data}) + if data.GenerationV2 == math.MaxUint64 { // if GenerationV2 is not available reset it to zero + data.GenerationV2 = 0 + } + mi.hasGenerationV2 = mi.hasGenerationV2 && data.GenerationV2 != 0 +} + +func (mi *MemoryIndex) HasGenerationV2() bool { + return mi.hasGenerationV2 } // Close closes the index func (mi *MemoryIndex) Close() error { return nil } + +func (mi *MemoryIndex) MaximumNumberOfHashes() uint32 { + return uint32(len(mi.indexMap)) +} diff --git a/plumbing/object/commitgraph/commitnode.go b/plumbing/object/commitgraph/commitnode.go index d92c9064f..47227d434 100644 --- a/plumbing/object/commitgraph/commitnode.go +++ b/plumbing/object/commitgraph/commitnode.go @@ -29,6 +29,10 @@ type CommitNode interface { // Generation returns the generation of the commit for reachability analysis. // Objects with newer generation are not reachable from objects of older generation. Generation() uint64 + // GenerationV2 stores the corrected commit date for the commits + // It combines the contents of the GDA2 and GDO2 sections of the commit-graph + // with the commit time portion of the CDAT section. + GenerationV2() uint64 // Commit returns the full commit object from the node Commit() (*object.Commit, error) } diff --git a/plumbing/object/commitgraph/commitnode_graph.go b/plumbing/object/commitgraph/commitnode_graph.go index 252b5181e..0f51e3be9 100644 --- a/plumbing/object/commitgraph/commitnode_graph.go +++ b/plumbing/object/commitgraph/commitnode_graph.go @@ -120,6 +120,13 @@ func (c *graphCommitNode) Generation() uint64 { return c.commitData.Generation } +func (c *graphCommitNode) GenerationV2() uint64 { + // If the commit-graph file was generated with older Git version that + // set the generation to zero for every commit the generation assumption + // is still valid. It is just less useful. + return c.commitData.GenerationV2 +} + func (c *graphCommitNode) Commit() (*object.Commit, error) { return object.GetCommit(c.gci.s, c.hash) } diff --git a/plumbing/object/commitgraph/commitnode_object.go b/plumbing/object/commitgraph/commitnode_object.go index 1bd37e3e0..7256bed2f 100644 --- a/plumbing/object/commitgraph/commitnode_object.go +++ b/plumbing/object/commitgraph/commitnode_object.go @@ -85,6 +85,13 @@ func (c *objectCommitNode) Generation() uint64 { return math.MaxUint64 } +func (c *objectCommitNode) GenerationV2() uint64 { + // Commit nodes representing objects outside of the commit graph can never + // be reached by objects from the commit-graph thus we return the highest + // possible value. + return math.MaxUint64 +} + func (c *objectCommitNode) Commit() (*object.Commit, error) { return c.commit, nil } diff --git a/plumbing/object/commitgraph/commitnode_walker_author_order.go b/plumbing/object/commitgraph/commitnode_walker_author_order.go new file mode 100644 index 000000000..f5b23cc51 --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_walker_author_order.go @@ -0,0 +1,61 @@ +package commitgraph + +import ( + "github.com/go-git/go-git/v5/plumbing" + + "github.com/emirpasic/gods/trees/binaryheap" +) + +// NewCommitNodeIterAuthorDateOrder returns a CommitNodeIter that walks the commit history, +// starting at the given commit and visiting its parents in Author Time order but with the +// constraint that no parent is emitted before its children are emitted. +// +// This matches `git log --author-order` +// +// This ordering requires that commit objects need to be loaded into memory - thus this +// ordering is likely to be slower than other orderings. +func NewCommitNodeIterAuthorDateOrder(c CommitNode, + seenExternal map[plumbing.Hash]bool, + ignore []plumbing.Hash, +) CommitNodeIter { + seen := make(map[plumbing.Hash]struct{}) + for _, h := range ignore { + seen[h] = struct{}{} + } + for h, ext := range seenExternal { + if ext { + seen[h] = struct{}{} + } + } + inCounts := make(map[plumbing.Hash]int) + + exploreHeap := &commitNodeHeap{binaryheap.NewWith(generationAndDateOrderComparator)} + exploreHeap.Push(c) + + visitHeap := &commitNodeHeap{binaryheap.NewWith(func(left, right interface{}) int { + leftCommit, err := left.(CommitNode).Commit() + if err != nil { + return -1 + } + rightCommit, err := right.(CommitNode).Commit() + if err != nil { + return -1 + } + + switch { + case rightCommit.Author.When.Before(leftCommit.Author.When): + return -1 + case leftCommit.Author.When.Before(rightCommit.Author.When): + return 1 + } + return 0 + })} + visitHeap.Push(c) + + return &commitNodeIteratorTopological{ + exploreStack: exploreHeap, + visitStack: visitHeap, + inCounts: inCounts, + ignore: seen, + } +} diff --git a/plumbing/object/commitgraph/commitnode_walker_ctime.go b/plumbing/object/commitgraph/commitnode_walker_ctime.go index c26873ce5..3ab9e6e87 100644 --- a/plumbing/object/commitgraph/commitnode_walker_ctime.go +++ b/plumbing/object/commitgraph/commitnode_walker_ctime.go @@ -17,7 +17,8 @@ type commitNodeIteratorByCTime struct { // NewCommitNodeIterCTime returns a CommitNodeIter that walks the commit history, // starting at the given commit and visiting its parents while preserving Committer Time order. -// this appears to be the closest order to `git log` +// this is close in order to `git log` but does not guarantee topological order and will +// order things incorrectly occasionally. // The given callback will be called for each visited commit. Each commit will // be visited only once. If the callback returns an error, walking will stop // and will return the error. Other errors might be returned if the history diff --git a/plumbing/object/commitgraph/commitnode_walker_date_order.go b/plumbing/object/commitgraph/commitnode_walker_date_order.go new file mode 100644 index 000000000..659a4fa44 --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_walker_date_order.go @@ -0,0 +1,41 @@ +package commitgraph + +import ( + "github.com/go-git/go-git/v5/plumbing" + + "github.com/emirpasic/gods/trees/binaryheap" +) + +// NewCommitNodeIterDateOrder returns a CommitNodeIter that walks the commit history, +// starting at the given commit and visiting its parents in Committer Time and Generation order, +// but with the constraint that no parent is emitted before its children are emitted. +// +// This matches `git log --date-order` +func NewCommitNodeIterDateOrder(c CommitNode, + seenExternal map[plumbing.Hash]bool, + ignore []plumbing.Hash, +) CommitNodeIter { + seen := make(map[plumbing.Hash]struct{}) + for _, h := range ignore { + seen[h] = struct{}{} + } + for h, ext := range seenExternal { + if ext { + seen[h] = struct{}{} + } + } + inCounts := make(map[plumbing.Hash]int) + + exploreHeap := &commitNodeHeap{binaryheap.NewWith(generationAndDateOrderComparator)} + exploreHeap.Push(c) + + visitHeap := &commitNodeHeap{binaryheap.NewWith(generationAndDateOrderComparator)} + visitHeap.Push(c) + + return &commitNodeIteratorTopological{ + exploreStack: exploreHeap, + visitStack: visitHeap, + inCounts: inCounts, + ignore: seen, + } +} diff --git a/plumbing/object/commitgraph/commitnode_walker_helper.go b/plumbing/object/commitgraph/commitnode_walker_helper.go new file mode 100644 index 000000000..c54f6caae --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_walker_helper.go @@ -0,0 +1,164 @@ +package commitgraph + +import ( + "math" + + "github.com/go-git/go-git/v5/plumbing" + + "github.com/emirpasic/gods/trees/binaryheap" +) + +// commitNodeStackable represents a common interface between heaps and stacks +type commitNodeStackable interface { + Push(c CommitNode) + Pop() (CommitNode, bool) + Peek() (CommitNode, bool) + Size() int +} + +// commitNodeLifo is a stack implementation using an underlying slice +type commitNodeLifo struct { + l []CommitNode +} + +// Push pushes a new CommitNode to the stack +func (l *commitNodeLifo) Push(c CommitNode) { + l.l = append(l.l, c) +} + +// Pop pops the most recently added CommitNode from the stack +func (l *commitNodeLifo) Pop() (CommitNode, bool) { + if len(l.l) == 0 { + return nil, false + } + c := l.l[len(l.l)-1] + l.l = l.l[:len(l.l)-1] + return c, true +} + +// Peek returns the most recently added CommitNode from the stack without removing it +func (l *commitNodeLifo) Peek() (CommitNode, bool) { + if len(l.l) == 0 { + return nil, false + } + return l.l[len(l.l)-1], true +} + +// Size returns the number of CommitNodes in the stack +func (l *commitNodeLifo) Size() int { + return len(l.l) +} + +// commitNodeHeap is a stack implementation using an underlying binary heap +type commitNodeHeap struct { + *binaryheap.Heap +} + +// Push pushes a new CommitNode to the heap +func (h *commitNodeHeap) Push(c CommitNode) { + h.Heap.Push(c) +} + +// Pop removes top element on heap and returns it, or nil if heap is empty. +// Second return parameter is true, unless the heap was empty and there was nothing to pop. +func (h *commitNodeHeap) Pop() (CommitNode, bool) { + c, ok := h.Heap.Pop() + if !ok { + return nil, false + } + return c.(CommitNode), true +} + +// Peek returns top element on the heap without removing it, or nil if heap is empty. +// Second return parameter is true, unless the heap was empty and there was nothing to peek. +func (h *commitNodeHeap) Peek() (CommitNode, bool) { + c, ok := h.Heap.Peek() + if !ok { + return nil, false + } + return c.(CommitNode), true +} + +// Size returns number of elements within the heap. +func (h *commitNodeHeap) Size() int { + return h.Heap.Size() +} + +// generationAndDateOrderComparator compares two CommitNode objects based on their generation and commit time. +// If the left CommitNode object is in a higher generation or is newer than the right one, it returns a -1. +// If the left CommitNode object is in a lower generation or is older than the right one, it returns a 1. +// If the two CommitNode objects have the same commit time and generation, it returns 0. +func generationAndDateOrderComparator(left, right interface{}) int { + leftCommit := left.(CommitNode) + rightCommit := right.(CommitNode) + + // if GenerationV2 is MaxUint64, then the node is not in the graph + if leftCommit.GenerationV2() == math.MaxUint64 { + if rightCommit.GenerationV2() == math.MaxUint64 { + switch { + case rightCommit.CommitTime().Before(leftCommit.CommitTime()): + return -1 + case leftCommit.CommitTime().Before(rightCommit.CommitTime()): + return 1 + } + return 0 + } + // left is not in the graph, but right is, so it is newer than the right + return -1 + } + + if rightCommit.GenerationV2() == math.MaxInt64 { + // the right is not in the graph, therefore the left is before the right + return 1 + } + + if leftCommit.GenerationV2() == 0 || rightCommit.GenerationV2() == 0 { + // We need to assess generation and date + if leftCommit.Generation() < rightCommit.Generation() { + return 1 + } + if leftCommit.Generation() > rightCommit.Generation() { + return -1 + } + switch { + case rightCommit.CommitTime().Before(leftCommit.CommitTime()): + return -1 + case leftCommit.CommitTime().Before(rightCommit.CommitTime()): + return 1 + } + return 0 + } + + if leftCommit.GenerationV2() < rightCommit.GenerationV2() { + return 1 + } + if leftCommit.GenerationV2() > rightCommit.GenerationV2() { + return -1 + } + + return 0 +} + +// composeIgnores composes the ignore list with the provided seenExternal list +func composeIgnores(ignore []plumbing.Hash, seenExternal map[plumbing.Hash]bool) map[plumbing.Hash]struct{} { + if len(ignore) == 0 { + seen := make(map[plumbing.Hash]struct{}) + for h, ext := range seenExternal { + if ext { + seen[h] = struct{}{} + } + } + return seen + } + + seen := make(map[plumbing.Hash]struct{}) + for _, h := range ignore { + seen[h] = struct{}{} + } + for h, ext := range seenExternal { + if ext { + seen[h] = struct{}{} + } + } + return seen +} diff --git a/plumbing/object/commitgraph/commitnode_walker_test.go b/plumbing/object/commitgraph/commitnode_walker_test.go new file mode 100644 index 000000000..1e09c0be5 --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_walker_test.go @@ -0,0 +1,187 @@ +package commitgraph + +import ( + "strings" + + "github.com/go-git/go-git/v5/plumbing" + commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" + + fixtures "github.com/go-git/go-git-fixtures/v4" + . "gopkg.in/check.v1" +) + +func (s *CommitNodeSuite) TestCommitNodeIter(c *C) { + f := fixtures.ByTag("commit-graph-chain-2").One() + + storer := unpackRepository(f) + + index, err := commitgraph.OpenChainOrFileIndex(storer.Filesystem()) + c.Assert(err, IsNil) + + nodeIndex := NewGraphCommitNodeIndex(index, storer) + + head, err := nodeIndex.Get(plumbing.NewHash("ec6f456c0e8c7058a29611429965aa05c190b54b")) + c.Assert(err, IsNil) + + testTopoOrder(c, head) + testDateOrder(c, head) + testAuthorDateOrder(c, head) +} + +func testTopoOrder(c *C, head CommitNode) { + iter := NewCommitNodeIterTopoOrder( + head, + nil, + nil, + ) + + var commits []string + iter.ForEach(func(c CommitNode) error { + commits = append(commits, c.ID().String()) + return nil + }) + c.Assert(commits, DeepEquals, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b +d82f291cde9987322c8a0c81a325e1ba6159684c +3048d280d2d5b258d9e582a226ff4bbed34fd5c9 +27aa8cdd2431068606741a589383c02c149ea625 +fa058d42fa3bc53f39108a56dad67157169b2191 +6c629843a1750a27c9af01ed2985f362f619c47a +d10a0e7c1f340a6cfc14540a5f8c508ce7e2eabf +d0a18ccd8eea3bdabc76d6dc5420af1ea30aae9f +cf2874632223220e0445abf0a7806dc772c0b37a +758ac33217f092bfcded4ad4774954ac054c9609 +214e1dca024fb6da5ed65564d2de734df5dc2127 +70923099e61fa33f0bc5256d2f938fa44c4df10e +bcaa1ac5644b16f1febb72f31e204720b7bb8934 +e1d8866ffa78fa16d2f39b0ba5344a7269ee5371 +2275fa7d0c75d20103f90b0e1616937d5a9fc5e6 +bdd9a92789d4a86b20a8d3df462df373f41acf23 +b359f11ea09e642695edcd114b463da4395b10c1 +6f43e8933ba3c04072d5d104acc6118aac3e52ee +ccafe8bd5f9dbfb8b98b0da03ced29608dcfdeec +939814f341fdd5d35e81a3845a33c4fedb19d2d2 +5f5ad88bf2babe506f927d64d2b7a1e1493dc2ae +a2014124ca3b3f9ff28fbab0a83ce3c71bf4622e +77906b653c3eb8a1cd5bd7254e161c00c6086d83 +465cba710284204f9851854587c2887c247222db +b9471b13256703d3f5eb88b280b4a16ce325ec1b +62925030859646daeeaf5a4d386a0c41e00dda8a +5f56aea0ca8b74215a5b982bca32236e1e28c76b +23148841baa5dbce48f6adcb7ddf83dcd97debb3 +c336d16298a017486c4164c40f8acb28afe64e84 +31eae7b619d166c366bf5df4991f04ba8cebea0a +d2a38b4a5965d529566566640519d03d2bd10f6c +b977a025ca21e3b5ca123d8093bd7917694f6da7 +35b585759cbf29f8ec428ef89da20705d59f99ec +c2bbf9fe8009b22d0f390f3c8c3f13937067590f +fc9f0643b21cfe571046e27e0c4565f3a1ee96c8 +c088fd6a7e1a38e9d5a9815265cb575bb08d08ff +5fddbeb678bd2c36c5e5c891ab8f2b143ced5baf +5d7303c49ac984a9fec60523f2d5297682e16646`, "\n")) +} + +func testDateOrder(c *C, head CommitNode) { + iter := NewCommitNodeIterDateOrder( + head, + nil, + nil, + ) + + var commits []string + iter.ForEach(func(c CommitNode) error { + commits = append(commits, c.ID().String()) + return nil + }) + + c.Assert(commits, DeepEquals, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b +3048d280d2d5b258d9e582a226ff4bbed34fd5c9 +d82f291cde9987322c8a0c81a325e1ba6159684c +27aa8cdd2431068606741a589383c02c149ea625 +fa058d42fa3bc53f39108a56dad67157169b2191 +d0a18ccd8eea3bdabc76d6dc5420af1ea30aae9f +6c629843a1750a27c9af01ed2985f362f619c47a +cf2874632223220e0445abf0a7806dc772c0b37a +d10a0e7c1f340a6cfc14540a5f8c508ce7e2eabf +758ac33217f092bfcded4ad4774954ac054c9609 +214e1dca024fb6da5ed65564d2de734df5dc2127 +70923099e61fa33f0bc5256d2f938fa44c4df10e +bcaa1ac5644b16f1febb72f31e204720b7bb8934 +e1d8866ffa78fa16d2f39b0ba5344a7269ee5371 +2275fa7d0c75d20103f90b0e1616937d5a9fc5e6 +bdd9a92789d4a86b20a8d3df462df373f41acf23 +b359f11ea09e642695edcd114b463da4395b10c1 +6f43e8933ba3c04072d5d104acc6118aac3e52ee +ccafe8bd5f9dbfb8b98b0da03ced29608dcfdeec +939814f341fdd5d35e81a3845a33c4fedb19d2d2 +5f5ad88bf2babe506f927d64d2b7a1e1493dc2ae +a2014124ca3b3f9ff28fbab0a83ce3c71bf4622e +77906b653c3eb8a1cd5bd7254e161c00c6086d83 +465cba710284204f9851854587c2887c247222db +b9471b13256703d3f5eb88b280b4a16ce325ec1b +62925030859646daeeaf5a4d386a0c41e00dda8a +5f56aea0ca8b74215a5b982bca32236e1e28c76b +23148841baa5dbce48f6adcb7ddf83dcd97debb3 +c336d16298a017486c4164c40f8acb28afe64e84 +31eae7b619d166c366bf5df4991f04ba8cebea0a +b977a025ca21e3b5ca123d8093bd7917694f6da7 +d2a38b4a5965d529566566640519d03d2bd10f6c +35b585759cbf29f8ec428ef89da20705d59f99ec +c2bbf9fe8009b22d0f390f3c8c3f13937067590f +fc9f0643b21cfe571046e27e0c4565f3a1ee96c8 +c088fd6a7e1a38e9d5a9815265cb575bb08d08ff +5fddbeb678bd2c36c5e5c891ab8f2b143ced5baf +5d7303c49ac984a9fec60523f2d5297682e16646`, "\n")) +} + +func testAuthorDateOrder(c *C, head CommitNode) { + iter := NewCommitNodeIterAuthorDateOrder( + head, + nil, + nil, + ) + + var commits []string + iter.ForEach(func(c CommitNode) error { + commits = append(commits, c.ID().String()) + return nil + }) + + c.Assert(commits, DeepEquals, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b +3048d280d2d5b258d9e582a226ff4bbed34fd5c9 +d82f291cde9987322c8a0c81a325e1ba6159684c +27aa8cdd2431068606741a589383c02c149ea625 +fa058d42fa3bc53f39108a56dad67157169b2191 +d0a18ccd8eea3bdabc76d6dc5420af1ea30aae9f +6c629843a1750a27c9af01ed2985f362f619c47a +cf2874632223220e0445abf0a7806dc772c0b37a +d10a0e7c1f340a6cfc14540a5f8c508ce7e2eabf +758ac33217f092bfcded4ad4774954ac054c9609 +214e1dca024fb6da5ed65564d2de734df5dc2127 +70923099e61fa33f0bc5256d2f938fa44c4df10e +bcaa1ac5644b16f1febb72f31e204720b7bb8934 +e1d8866ffa78fa16d2f39b0ba5344a7269ee5371 +2275fa7d0c75d20103f90b0e1616937d5a9fc5e6 +bdd9a92789d4a86b20a8d3df462df373f41acf23 +b359f11ea09e642695edcd114b463da4395b10c1 +6f43e8933ba3c04072d5d104acc6118aac3e52ee +ccafe8bd5f9dbfb8b98b0da03ced29608dcfdeec +939814f341fdd5d35e81a3845a33c4fedb19d2d2 +5f5ad88bf2babe506f927d64d2b7a1e1493dc2ae +a2014124ca3b3f9ff28fbab0a83ce3c71bf4622e +77906b653c3eb8a1cd5bd7254e161c00c6086d83 +465cba710284204f9851854587c2887c247222db +b9471b13256703d3f5eb88b280b4a16ce325ec1b +5f56aea0ca8b74215a5b982bca32236e1e28c76b +62925030859646daeeaf5a4d386a0c41e00dda8a +23148841baa5dbce48f6adcb7ddf83dcd97debb3 +c336d16298a017486c4164c40f8acb28afe64e84 +31eae7b619d166c366bf5df4991f04ba8cebea0a +b977a025ca21e3b5ca123d8093bd7917694f6da7 +d2a38b4a5965d529566566640519d03d2bd10f6c +35b585759cbf29f8ec428ef89da20705d59f99ec +c2bbf9fe8009b22d0f390f3c8c3f13937067590f +fc9f0643b21cfe571046e27e0c4565f3a1ee96c8 +c088fd6a7e1a38e9d5a9815265cb575bb08d08ff +5fddbeb678bd2c36c5e5c891ab8f2b143ced5baf +5d7303c49ac984a9fec60523f2d5297682e16646`, "\n")) +} diff --git a/plumbing/object/commitgraph/commitnode_walker_topo_order.go b/plumbing/object/commitgraph/commitnode_walker_topo_order.go new file mode 100644 index 000000000..29f4bb72e --- /dev/null +++ b/plumbing/object/commitgraph/commitnode_walker_topo_order.go @@ -0,0 +1,161 @@ +package commitgraph + +import ( + "io" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/storer" + + "github.com/emirpasic/gods/trees/binaryheap" +) + +type commitNodeIteratorTopological struct { + exploreStack commitNodeStackable + visitStack commitNodeStackable + inCounts map[plumbing.Hash]int + + ignore map[plumbing.Hash]struct{} +} + +// NewCommitNodeIterTopoOrder returns a CommitNodeIter that walks the commit history, +// starting at the given commit and visiting its parents in a topological order but +// with the constraint that no parent is emitted before its children are emitted. +// +// This matches `git log --topo-order` +func NewCommitNodeIterTopoOrder(c CommitNode, + seenExternal map[plumbing.Hash]bool, + ignore []plumbing.Hash, +) CommitNodeIter { + seen := composeIgnores(ignore, seenExternal) + inCounts := make(map[plumbing.Hash]int) + + heap := &commitNodeHeap{binaryheap.NewWith(generationAndDateOrderComparator)} + heap.Push(c) + + lifo := &commitNodeLifo{make([]CommitNode, 0, 8)} + lifo.Push(c) + + return &commitNodeIteratorTopological{ + exploreStack: heap, + visitStack: lifo, + inCounts: inCounts, + ignore: seen, + } +} + +func (iter *commitNodeIteratorTopological) Next() (CommitNode, error) { + var next CommitNode + for { + var ok bool + next, ok = iter.visitStack.Pop() + if !ok { + return nil, io.EOF + } + + if iter.inCounts[next.ID()] == 0 { + break + } + } + + minimumLevel, generationV2 := next.GenerationV2(), true + if minimumLevel == 0 { + minimumLevel, generationV2 = next.Generation(), false + } + + parents := make([]CommitNode, 0, len(next.ParentHashes())) + for i := range next.ParentHashes() { + pc, err := next.ParentNode(i) + if err != nil { + return nil, err + } + + parents = append(parents, pc) + + if generationV2 { + if pc.GenerationV2() < minimumLevel { + minimumLevel = pc.GenerationV2() + } + continue + } + + if pc.Generation() < minimumLevel { + minimumLevel = pc.Generation() + } + } + + // EXPLORE + for { + toExplore, ok := iter.exploreStack.Peek() + if !ok { + break + } + + if toExplore.ID() != next.ID() && iter.exploreStack.Size() == 1 { + break + } + if generationV2 { + if toExplore.GenerationV2() < minimumLevel { + break + } + } else { + if toExplore.Generation() < minimumLevel { + break + } + } + + iter.exploreStack.Pop() + for i, h := range toExplore.ParentHashes() { + if _, has := iter.ignore[h]; has { + continue + } + iter.inCounts[h]++ + + if iter.inCounts[h] == 1 { + pc, err := toExplore.ParentNode(i) + if err != nil { + return nil, err + } + iter.exploreStack.Push(pc) + } + } + } + + // VISIT + for i, h := range next.ParentHashes() { + if _, has := iter.ignore[h]; has { + continue + } + iter.inCounts[h]-- + + if iter.inCounts[h] == 0 { + iter.visitStack.Push(parents[i]) + } + } + delete(iter.inCounts, next.ID()) + + return next, nil +} + +func (iter *commitNodeIteratorTopological) ForEach(cb func(CommitNode) error) error { + for { + obj, err := iter.Next() + if err != nil { + if err == io.EOF { + return nil + } + + return err + } + + if err := cb(obj); err != nil { + if err == storer.ErrStop { + return nil + } + + return err + } + } +} + +func (iter *commitNodeIteratorTopological) Close() { +} From 3ee0288b785e6e1070aa88c8fd5387b7afd80902 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 13 Oct 2023 22:27:48 +0100 Subject: [PATCH 167/205] build: bump golang.org/x/net from 0.15.0 to 0.17.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: Paulo Gomes --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 9df2020f1..ff859fe8d 100644 --- a/go.mod +++ b/go.mod @@ -23,8 +23,8 @@ require ( github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.2.0 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.13.0 - golang.org/x/net v0.15.0 + golang.org/x/crypto v0.14.0 + golang.org/x/net v0.17.0 golang.org/x/sys v0.13.0 golang.org/x/text v0.13.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c diff --git a/go.sum b/go.sum index 7c62b1094..c7e3c2bf4 100644 --- a/go.sum +++ b/go.sum @@ -84,8 +84,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= @@ -98,8 +98,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -128,7 +128,7 @@ golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From 136f6cc58f8469e8d5dfd1efeb6b1524215da22d Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 13 Oct 2023 22:51:43 +0100 Subject: [PATCH 168/205] build: Add dependabot configuration Configure dependabot to check for Go updates daily and GitHub actions weekly. The configuration sets the commit prefix, so that it aligns with the contributing guidelines. Signed-off-by: Paulo Gomes --- .github/dependabot.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/dependabot.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 000000000..6a7a66bfa --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "build" + + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: "build" From f4b881ce59edb35afb6a375187953db5ea7cbd64 Mon Sep 17 00:00:00 2001 From: Aditya Sirish Date: Fri, 13 Oct 2023 18:37:50 -0400 Subject: [PATCH 169/205] build: Add 'git' as valid commit msg scheme Signed-off-by: Aditya Sirish --- .github/workflows/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 04e704654..d7b115095 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -19,7 +19,7 @@ jobs: - name: Check Package Prefix uses: gsactions/commit-message-checker@v2 with: - pattern: '^(\*|plumbing|utils|config|_examples|internal|storage|cli|build): .+' + pattern: '^(\*|git|plumbing|utils|config|_examples|internal|storage|cli|build): .+' error: | Commit message(s) does not align with contribution acceptance criteria. From aca382bd2536df1533598bc1d0031cc8547b6cef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:19:50 +0000 Subject: [PATCH 170/205] build: bump github.com/google/go-cmp from 0.5.9 to 0.6.0 Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.9 to 0.6.0. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.9...v0.6.0) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 075446074..83bc402b0 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jessevdk/go-flags v1.5.0 github.com/kevinburke/ssh_config v1.2.0 diff --git a/go.sum b/go.sum index 6baf1ba2d..f2bf9c11f 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= From 06df36caa7a98d76c5555ee1ec7bc1e04ddd4a89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:30:55 +0000 Subject: [PATCH 171/205] build: bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 2 +- .github/workflows/git.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fbb867cc7..bfe9879af 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index 68776fce6..c6eff0868 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -22,7 +22,7 @@ jobs: go-version: 1.21.x - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install build dependencies run: sudo apt-get update && sudo apt-get install gettext libcurl4-openssl-dev diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74c9bdeb5..1ceee007c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure known hosts if: matrix.platform != 'ubuntu-latest' From fa818ac30356d8b124f8a0a2df296d211ce5ebb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:30:59 +0000 Subject: [PATCH 172/205] build: bump actions/setup-go from 3 to 4 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/git.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index 68776fce6..821987fd7 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: 1.21.x diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74c9bdeb5..ef9624ac4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} From 32212de859ea5df73933efd304cb8e3c076fd6a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 05:36:40 +0000 Subject: [PATCH 173/205] build: bump github.com/skeema/knownhosts from 1.2.0 to 1.2.1 Bumps [github.com/skeema/knownhosts](https://github.com/skeema/knownhosts) from 1.2.0 to 1.2.1. - [Commits](https://github.com/skeema/knownhosts/compare/v1.2.0...v1.2.1) --- updated-dependencies: - dependency-name: github.com/skeema/knownhosts dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 83bc402b0..25be1e1d4 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/kevinburke/ssh_config v1.2.0 github.com/pjbgf/sha1cd v0.3.0 github.com/sergi/go-diff v1.1.0 - github.com/skeema/knownhosts v1.2.0 + github.com/skeema/knownhosts v1.2.1 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.14.0 golang.org/x/net v0.17.0 diff --git a/go.sum b/go.sum index f2bf9c11f..9a2760a1c 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz 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.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= -github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 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= From 4862643548f115dff8d402faf610e50408458cdf Mon Sep 17 00:00:00 2001 From: Aditya Sirish Date: Thu, 12 Oct 2023 00:15:14 -0400 Subject: [PATCH 174/205] git: remote, flip branch check to not-tag check In remote.updateLocalReferenceStorage, this commit updates a check to see if the reference is for a branch (in refs/heads) to checking the reference is not a tag. This change ensures that the subsequent fast-forward only handling clauses apply to references that are not standard branches stored in refs/heads. Signed-off-by: Aditya Sirish --- remote.go | 6 +-- remote_test.go | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/remote.go b/remote.go index 2ffffe7b6..bbe275d5b 100644 --- a/remote.go +++ b/remote.go @@ -1198,9 +1198,9 @@ func (r *Remote) updateLocalReferenceStorage( old, _ := storer.ResolveReference(r.s, localName) new := plumbing.NewHashReference(localName, ref.Hash()) - // If the ref exists locally as a branch and force is not specified, - // only update if the new ref is an ancestor of the old - if old != nil && old.Name().IsBranch() && !force && !spec.IsForceUpdate() { + // If the ref exists locally as a non-tag and force is not + // specified, only update if the new ref is an ancestor of the old + if old != nil && !old.Name().IsTag() && !force && !spec.IsForceUpdate() { ff, err := isFastForward(r.s, old.Hash(), new.Hash()) if err != nil { return updated, err diff --git a/remote_test.go b/remote_test.go index e0c333294..81c60bcb8 100644 --- a/remote_test.go +++ b/remote_test.go @@ -4,16 +4,20 @@ import ( "bytes" "context" "errors" + "fmt" "io" "os" "path/filepath" "runtime" "strings" + "testing" "time" + "github.com/go-git/go-billy/v5/memfs" "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" @@ -1555,3 +1559,140 @@ func (s *RemoteSuite) TestFetchAfterShallowClone(c *C) { plumbing.NewSymbolicReference("HEAD", "refs/heads/master"), }) } + +func TestFetchFastForwardForCustomRef(t *testing.T) { + customRef := "refs/custom/branch" + // 1. Set up a remote with a URL + remoteURL := t.TempDir() + remoteRepo, err := PlainInit(remoteURL, true) + if err != nil { + t.Fatal(err) + } + + // 2. Add a commit with an empty tree to master and custom ref, also set HEAD + emptyTreeID := writeEmptyTree(t, remoteRepo) + writeCommitToRef(t, remoteRepo, "refs/heads/master", emptyTreeID, time.Now()) + writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now()) + if err := remoteRepo.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, "refs/heads/master")); err != nil { + t.Fatal(err) + } + + // 3. Clone repo, then fetch the custom ref + // Note that using custom ref in ReferenceName has an IsBranch issue + localRepo, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{ + URL: remoteURL, + }) + if err != nil { + t.Fatal(err) + } + if err := localRepo.Fetch(&FetchOptions{ + RefSpecs: []config.RefSpec{ + config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)), + }, + }); err != nil { + t.Fatal(err) + } + + // 4. Make divergent changes + remoteCommitID := writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now()) + // Consecutive calls to writeCommitToRef with time.Now() might have the same + // time value, explicitly set distinct ones to ensure the commit hashes + // differ + writeCommitToRef(t, localRepo, customRef, emptyTreeID, time.Now().Add(time.Second)) + + // 5. Try to fetch with fast-forward only mode + remote, err := localRepo.Remote(DefaultRemoteName) + if err != nil { + t.Fatal(err) + } + + err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{ + config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)), + }}) + if !errors.Is(err, ErrForceNeeded) { + t.Errorf("expected %v, got %v", ErrForceNeeded, err) + } + + // 6. Fetch with force + err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{ + config.RefSpec(fmt.Sprintf("+%s:%s", customRef, customRef)), + }}) + if err != nil { + t.Errorf("unexpected error %v", err) + } + + // 7. Assert commit ID matches + ref, err := localRepo.Reference(plumbing.ReferenceName(customRef), true) + if err != nil { + t.Fatal(err) + } + if remoteCommitID != ref.Hash() { + t.Errorf("expected %s, got %s", remoteCommitID.String(), ref.Hash().String()) + } +} + +func writeEmptyTree(t *testing.T, repo *Repository) plumbing.Hash { + t.Helper() + + obj := repo.Storer.NewEncodedObject() + obj.SetType(plumbing.TreeObject) + + tree := object.Tree{Entries: nil} + if err := tree.Encode(obj); err != nil { + t.Fatal(err) + } + + treeID, err := repo.Storer.SetEncodedObject(obj) + if err != nil { + t.Fatal(err) + } + + return treeID +} + +func writeCommitToRef(t *testing.T, repo *Repository, refName string, treeID plumbing.Hash, when time.Time) plumbing.Hash { + t.Helper() + + ref, err := repo.Reference(plumbing.ReferenceName(refName), true) + if err != nil { + if errors.Is(err, plumbing.ErrReferenceNotFound) { + if err := repo.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(refName), plumbing.ZeroHash)); err != nil { + t.Fatal(err) + } + + ref, err = repo.Reference(plumbing.ReferenceName(refName), true) + if err != nil { + t.Fatal(err) + } + } else { + t.Fatal(err) + } + } + + commit := &object.Commit{ + TreeHash: treeID, + Author: object.Signature{ + When: when, + }, + } + if !ref.Hash().IsZero() { + commit.ParentHashes = []plumbing.Hash{ref.Hash()} + } + + obj := repo.Storer.NewEncodedObject() + if err := commit.Encode(obj); err != nil { + t.Fatal(err) + } + + commitID, err := repo.Storer.SetEncodedObject(obj) + if err != nil { + t.Fatal(err) + } + + newRef := plumbing.NewHashReference(plumbing.ReferenceName(refName), commitID) + if err := repo.Storer.CheckAndSetReference(newRef, ref); err != nil { + t.Fatal(err) + } + + return commitID +} From 814abc098d033f77315d3bfb89ae5991aae10457 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Tue, 27 Jun 2023 21:33:01 +0100 Subject: [PATCH 175/205] *: Improve BenchmarkPlainClone The changes aim to make that specific benchmark more reliable for setting a baseline which can later be use to compare against future changes on the most basic feature of go-git: plain cloning repositories. --- repository_test.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/repository_test.go b/repository_test.go index f6839b6da..35a62f131 100644 --- a/repository_test.go +++ b/repository_test.go @@ -3311,20 +3311,25 @@ func BenchmarkObjects(b *testing.B) { } func BenchmarkPlainClone(b *testing.B) { - for i := 0; i < b.N; i++ { - t, err := os.MkdirTemp("", "") - if err != nil { - b.Fatal(err) - } - _, err = PlainClone(t, false, &CloneOptions{ - URL: "https://github.com/knqyf263/vuln-list", - Depth: 1, + b.StopTimer() + clone := func(b *testing.B) { + _, err := PlainClone(b.TempDir(), true, &CloneOptions{ + URL: "https://github.com/go-git/go-git.git", + Depth: 1, + Tags: NoTags, + SingleBranch: true, }) if err != nil { b.Error(err) } - b.StopTimer() - os.RemoveAll(t) - b.StartTimer() + } + + // Warm-up as the initial clone could have a higher cost which + // may skew results. + clone(b) + + b.StartTimer() + for i := 0; i < b.N; i++ { + clone(b) } } From 1c361adbc1f4b0e3d0743d11f187fb0b3ac4cb4d Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 20 Jul 2023 21:19:28 +0100 Subject: [PATCH 176/205] plumbing: Optimise memory consumption for filesystem storage Previously, as part of building the index representation, the resolveObject func would create an interim plumbing.MemoryObject, which would then be saved into storage via storage.SetEncodedObject. This meant that objects would be unnecessarily loaded into memory, to then be saved into disk. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The changes streamlines this process by: - Introducing the LazyObjectWriter interface which enables the write operation to take places directly against the filesystem-based storage. - Leverage multi-writers to process the input data once, while targeting multiple writers (e.g. hasher and storage). An additional change relates to the caching of object info children within Parser.get. The cache is now skipped when a seekable filesystem is being used. The impact of the changes can be observed when using seekable filesystem storages, especially when cloning large repositories. The stats below were captured by adapting the BenchmarkPlainClone test to clone https://github.com/torvalds/linux.git: pkg: github.com/go-git/go-git/v5 cpu: Intel(R) Core(TM) i9-10885H CPU @ 2.40GHz │ /tmp/old │ /tmp/new │ │ sec/op │ sec/op vs base │ PlainClone-16 41.68 ± 17% 48.04 ± 9% +15.27% (p=0.015 n=6) │ /tmp/old │ /tmp/new │ │ B/op │ B/op vs base │ PlainClone-16 1127.8Mi ± 7% 256.7Mi ± 50% -77.23% (p=0.002 n=6) │ /tmp/old │ /tmp/new │ │ allocs/op │ allocs/op vs base │ PlainClone-16 3.125M ± 0% 3.800M ± 0% +21.60% (p=0.002 n=6) Notice that on average the memory consumption per operation is over 75% smaller. The time per operation increased by 15%, which may actual be less on long running applications, due to the decreased GC pressure and the garbage collection costs. Signed-off-by: Paulo Gomes --- plumbing/format/packfile/parser.go | 195 ++++++++++++++++++------ plumbing/format/packfile/patch_delta.go | 102 +++++++++++++ storage/filesystem/object.go | 13 ++ 3 files changed, 260 insertions(+), 50 deletions(-) diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go index edbc0e796..62f1d13cb 100644 --- a/plumbing/format/packfile/parser.go +++ b/plumbing/format/packfile/parser.go @@ -3,6 +3,7 @@ package packfile import ( "bytes" "errors" + "fmt" "io" "github.com/go-git/go-git/v5/plumbing" @@ -174,13 +175,25 @@ func (p *Parser) init() error { return nil } +type objectHeaderWriter func(typ plumbing.ObjectType, sz int64) error + +type lazyObjectWriter interface { + // LazyWriter enables an object to be lazily written. + // It returns: + // - w: a writer to receive the object's content. + // - lwh: a func to write the object header. + // - err: any error from the initial writer creation process. + // + // Note that if the object header is not written BEFORE the writer + // is used, this will result in an invalid object. + LazyWriter() (w io.WriteCloser, lwh objectHeaderWriter, err error) +} + func (p *Parser) indexObjects() error { buf := sync.GetBytesBuffer() defer sync.PutBytesBuffer(buf) for i := uint32(0); i < p.count; i++ { - buf.Reset() - oh, err := p.scanner.NextObjectHeader() if err != nil { return err @@ -220,21 +233,60 @@ func (p *Parser) indexObjects() error { ota = newBaseObject(oh.Offset, oh.Length, t) } - buf.Grow(int(oh.Length)) - _, crc, err := p.scanner.NextObject(buf) + hasher := plumbing.NewHasher(oh.Type, oh.Length) + writers := []io.Writer{hasher} + var obj *plumbing.MemoryObject + + // Lazy writing is only available for non-delta objects. + if p.storage != nil && !delta { + // When a storage is set and supports lazy writing, + // use that instead of creating a memory object. + if low, ok := p.storage.(lazyObjectWriter); ok { + ow, lwh, err := low.LazyWriter() + if err != nil { + return err + } + + if err = lwh(oh.Type, oh.Length); err != nil { + return err + } + + defer ow.Close() + writers = append(writers, ow) + } else { + obj = new(plumbing.MemoryObject) + obj.SetSize(oh.Length) + obj.SetType(oh.Type) + + writers = append(writers, obj) + } + } + if delta && !p.scanner.IsSeekable { + buf.Reset() + buf.Grow(int(oh.Length)) + writers = append(writers, buf) + } + + mw := io.MultiWriter(writers...) + + _, crc, err := p.scanner.NextObject(mw) if err != nil { return err } + // Non delta objects needs to be added into the storage. This + // is only required when lazy writing is not supported. + if obj != nil { + if _, err := p.storage.SetEncodedObject(obj); err != nil { + return err + } + } + ota.Crc32 = crc ota.Length = oh.Length - data := buf.Bytes() if !delta { - sha1, err := getSHA1(ota.Type, data) - if err != nil { - return err - } + sha1 := hasher.Sum() // Move children of placeholder parent into actual parent, in case this // was a non-external delta reference. @@ -249,20 +301,8 @@ func (p *Parser) indexObjects() error { p.oiByHash[ota.SHA1] = ota } - if p.storage != nil && !delta { - obj := new(plumbing.MemoryObject) - obj.SetSize(oh.Length) - obj.SetType(oh.Type) - if _, err := obj.Write(data); err != nil { - return err - } - - if _, err := p.storage.SetEncodedObject(obj); err != nil { - return err - } - } - if delta && !p.scanner.IsSeekable { + data := buf.Bytes() p.deltas[oh.Offset] = make([]byte, len(data)) copy(p.deltas[oh.Offset], data) } @@ -280,23 +320,29 @@ func (p *Parser) resolveDeltas() error { for _, obj := range p.oi { buf.Reset() + buf.Grow(int(obj.Length)) err := p.get(obj, buf) if err != nil { return err } - content := buf.Bytes() if err := p.onInflatedObjectHeader(obj.Type, obj.Length, obj.Offset); err != nil { return err } - if err := p.onInflatedObjectContent(obj.SHA1, obj.Offset, obj.Crc32, content); err != nil { + if err := p.onInflatedObjectContent(obj.SHA1, obj.Offset, obj.Crc32, nil); err != nil { return err } if !obj.IsDelta() && len(obj.Children) > 0 { + // Dealing with an io.ReaderAt object, means we can + // create it once and reuse across all children. + r := bytes.NewReader(buf.Bytes()) for _, child := range obj.Children { - if err := p.resolveObject(io.Discard, child, content); err != nil { + // Even though we are discarding the output, we still need to read it to + // so that the scanner can advance to the next object, and the SHA1 can be + // calculated. + if err := p.resolveObject(io.Discard, child, r); err != nil { return err } p.resolveExternalRef(child) @@ -361,13 +407,13 @@ func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { if o.DiskType.IsDelta() { b := sync.GetBytesBuffer() defer sync.PutBytesBuffer(b) + buf.Grow(int(o.Length)) err := p.get(o.Parent, b) if err != nil { return err } - base := b.Bytes() - err = p.resolveObject(buf, o, base) + err = p.resolveObject(buf, o, bytes.NewReader(b.Bytes())) if err != nil { return err } @@ -378,6 +424,13 @@ func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { } } + // If the scanner is seekable, caching this data into + // memory by offset seems wasteful. + // There is a trade-off to be considered here in terms + // of execution time vs memory consumption. + // + // TODO: improve seekable execution time, so that we can + // skip this cache. if len(o.Children) > 0 { data := make([]byte, buf.Len()) copy(data, buf.Bytes()) @@ -386,10 +439,25 @@ func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { return nil } +// resolveObject resolves an object from base, using information +// provided by o. +// +// This call has the side-effect of changing field values +// from the object info o: +// - Type: OFSDeltaObject may become the target type (e.g. Blob). +// - Size: The size may be update with the target size. +// - Hash: Zero hashes will be calculated as part of the object +// resolution. Hence why this process can't be avoided even when w +// is an io.Discard. +// +// base must be an io.ReaderAt, which is a requirement from +// patchDeltaStream. The main reason being that reversing an +// delta object may lead to going backs and forths within base, +// which is not supported by io.Reader. func (p *Parser) resolveObject( w io.Writer, o *objectInfo, - base []byte, + base io.ReaderAt, ) error { if !o.DiskType.IsDelta() { return nil @@ -400,26 +468,46 @@ func (p *Parser) resolveObject( if err != nil { return err } - data := buf.Bytes() - data, err = applyPatchBase(o, data, base) + writers := []io.Writer{w} + var obj *plumbing.MemoryObject + var lwh objectHeaderWriter + + if p.storage != nil { + if low, ok := p.storage.(lazyObjectWriter); ok { + ow, wh, err := low.LazyWriter() + if err != nil { + return err + } + lwh = wh + + defer ow.Close() + writers = append(writers, ow) + } else { + obj = new(plumbing.MemoryObject) + ow, err := obj.Writer() + if err != nil { + return err + } + + writers = append(writers, ow) + } + } + + mw := io.MultiWriter(writers...) + + err = applyPatchBase(o, base, buf, mw, lwh) if err != nil { return err } - if p.storage != nil { - obj := new(plumbing.MemoryObject) - obj.SetSize(o.Size()) + if obj != nil { obj.SetType(o.Type) - if _, err := obj.Write(data); err != nil { - return err - } - + obj.SetSize(o.Size()) // Size here is correct as it was populated by applyPatchBase. if _, err := p.storage.SetEncodedObject(obj); err != nil { return err } } - _, err = w.Write(data) return err } @@ -443,24 +531,31 @@ func (p *Parser) readData(w io.Writer, o *objectInfo) error { return nil } -func applyPatchBase(ota *objectInfo, data, base []byte) ([]byte, error) { - patched, err := PatchDelta(base, data) - if err != nil { - return nil, err +// applyPatchBase applies the patch to target. +// +// Note that ota will be updated based on the description in resolveObject. +func applyPatchBase(ota *objectInfo, base io.ReaderAt, delta io.Reader, target io.Writer, wh objectHeaderWriter) error { + if target == nil { + return fmt.Errorf("cannot apply patch against nil target") } + typ := ota.Type if ota.SHA1 == plumbing.ZeroHash { - ota.Type = ota.Parent.Type - sha1, err := getSHA1(ota.Type, patched) - if err != nil { - return nil, err - } + typ = ota.Parent.Type + } + + sz, h, err := patchDeltaWriter(target, base, delta, typ, wh) + if err != nil { + return err + } - ota.SHA1 = sha1 - ota.Length = int64(len(patched)) + if ota.SHA1 == plumbing.ZeroHash { + ota.Type = typ + ota.Length = int64(sz) + ota.SHA1 = h } - return patched, nil + return nil } func getSHA1(t plumbing.ObjectType, data []byte) (plumbing.Hash, error) { diff --git a/plumbing/format/packfile/patch_delta.go b/plumbing/format/packfile/patch_delta.go index f00562d63..67c20ff78 100644 --- a/plumbing/format/packfile/patch_delta.go +++ b/plumbing/format/packfile/patch_delta.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "errors" + "fmt" "io" "math" @@ -265,6 +266,107 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error { return nil } +func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader, + typ plumbing.ObjectType, writeHeader objectHeaderWriter) (uint, plumbing.Hash, error) { + deltaBuf := bufio.NewReaderSize(delta, 1024) + srcSz, err := decodeLEB128ByteReader(deltaBuf) + if err != nil { + if err == io.EOF { + return 0, plumbing.ZeroHash, ErrInvalidDelta + } + return 0, plumbing.ZeroHash, err + } + + if r, ok := base.(*bytes.Reader); ok && srcSz != uint(r.Size()) { + return 0, plumbing.ZeroHash, ErrInvalidDelta + } + + targetSz, err := decodeLEB128ByteReader(deltaBuf) + if err != nil { + if err == io.EOF { + return 0, plumbing.ZeroHash, ErrInvalidDelta + } + return 0, plumbing.ZeroHash, err + } + + // If header still needs to be written, caller will provide + // a LazyObjectWriterHeader. This seems to be the case when + // dealing with thin-packs. + if writeHeader != nil { + err = writeHeader(typ, int64(targetSz)) + if err != nil { + return 0, plumbing.ZeroHash, fmt.Errorf("could not lazy write header: %w", err) + } + } + + remainingTargetSz := targetSz + + hasher := plumbing.NewHasher(typ, int64(targetSz)) + mw := io.MultiWriter(dst, hasher) + + bufp := sync.GetByteSlice() + defer sync.PutByteSlice(bufp) + + sr := io.NewSectionReader(base, int64(0), int64(srcSz)) + // Keep both the io.LimitedReader types, so we can reset N. + baselr := io.LimitReader(sr, 0).(*io.LimitedReader) + deltalr := io.LimitReader(deltaBuf, 0).(*io.LimitedReader) + + for { + buf := *bufp + cmd, err := deltaBuf.ReadByte() + if err == io.EOF { + return 0, plumbing.ZeroHash, ErrInvalidDelta + } + if err != nil { + return 0, plumbing.ZeroHash, err + } + + if isCopyFromSrc(cmd) { + offset, err := decodeOffsetByteReader(cmd, deltaBuf) + if err != nil { + return 0, plumbing.ZeroHash, err + } + sz, err := decodeSizeByteReader(cmd, deltaBuf) + if err != nil { + return 0, plumbing.ZeroHash, err + } + + if invalidSize(sz, targetSz) || + invalidOffsetSize(offset, sz, srcSz) { + return 0, plumbing.ZeroHash, err + } + + if _, err := sr.Seek(int64(offset), io.SeekStart); err != nil { + return 0, plumbing.ZeroHash, err + } + baselr.N = int64(sz) + if _, err := io.CopyBuffer(mw, baselr, buf); err != nil { + return 0, plumbing.ZeroHash, err + } + remainingTargetSz -= sz + } else if isCopyFromDelta(cmd) { + sz := uint(cmd) // cmd is the size itself + if invalidSize(sz, targetSz) { + return 0, plumbing.ZeroHash, ErrInvalidDelta + } + deltalr.N = int64(sz) + if _, err := io.CopyBuffer(mw, deltalr, buf); err != nil { + return 0, plumbing.ZeroHash, err + } + + remainingTargetSz -= sz + } else { + return 0, plumbing.ZeroHash, err + } + if remainingTargetSz <= 0 { + break + } + } + + return targetSz, hasher.Sum(), nil +} + // Decodes a number encoded as an unsigned LEB128 at the start of some // binary data and returns the decoded number and the rest of the // stream. diff --git a/storage/filesystem/object.go b/storage/filesystem/object.go index 846a7b860..e812fe934 100644 --- a/storage/filesystem/object.go +++ b/storage/filesystem/object.go @@ -146,6 +146,19 @@ func (s *ObjectStorage) SetEncodedObject(o plumbing.EncodedObject) (h plumbing.H return o.Hash(), err } +// LazyWriter returns a lazy ObjectWriter that is bound to a DotGit file. +// It first write the header passing on the object type and size, so +// that the object contents can be written later, without the need to +// create a MemoryObject and buffering its entire contents into memory. +func (s *ObjectStorage) LazyWriter() (w io.WriteCloser, wh func(typ plumbing.ObjectType, sz int64) error, err error) { + ow, err := s.dir.NewObject() + if err != nil { + return nil, nil, err + } + + return ow, ow.WriteHeader, nil +} + // HasEncodedObject returns nil if the object exists, without actually // reading the object data from storage. func (s *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) { From 501e9ad99aa3d18b7711d8cea94e0e9b68883b6b Mon Sep 17 00:00:00 2001 From: Anand Francis Joseph Date: Thu, 2 Nov 2023 22:48:52 +0530 Subject: [PATCH 177/205] plumbing: transport/ssh, Fix nil pointer dereference caused when an unreachable proxy server is set. Fixes #900 Signed-off-by: Anand Francis Joseph --- plumbing/transport/ssh/common.go | 10 +++++----- plumbing/transport/ssh/common_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go index 15316038b..46fda73fa 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -168,7 +168,7 @@ func dial(network, addr string, proxyOpts transport.ProxyOptions, config *ssh.Cl defer cancel() var conn net.Conn - var err error + var dialErr error if proxyOpts.URL != "" { proxyUrl, err := proxyOpts.FullURL() @@ -186,12 +186,12 @@ func dial(network, addr string, proxyOpts transport.ProxyOptions, config *ssh.Cl return nil, fmt.Errorf("expected ssh proxy dialer to be of type %s; got %s", reflect.TypeOf(ctxDialer), reflect.TypeOf(dialer)) } - conn, err = ctxDialer.DialContext(ctx, "tcp", addr) + conn, dialErr = ctxDialer.DialContext(ctx, "tcp", addr) } else { - conn, err = proxy.Dial(ctx, network, addr) + conn, dialErr = proxy.Dial(ctx, network, addr) } - if err != nil { - return nil, err + if dialErr != nil { + return nil, dialErr } c, chans, reqs, err := ssh.NewClientConn(conn, addr, config) diff --git a/plumbing/transport/ssh/common_test.go b/plumbing/transport/ssh/common_test.go index 496e82d17..4cc2a0693 100644 --- a/plumbing/transport/ssh/common_test.go +++ b/plumbing/transport/ssh/common_test.go @@ -172,6 +172,28 @@ func (s *SuiteCommon) TestIssue70(c *C) { c.Assert(err, IsNil) } +/* +Given, an endpoint to a git server with a socks5 proxy URL, +When, the socks5 proxy server is not reachable, +Then, there should not be any panic and an error with appropriate message should be returned. +Related issue : https://github.com/go-git/go-git/pull/900 +*/ +func (s *SuiteCommon) TestInvalidSocks5Proxy(c *C) { + ep, err := transport.NewEndpoint("git@github.com:foo/bar.git") + c.Assert(err, IsNil) + ep.Proxy.URL = "socks5://127.0.0.1:1080" + + auth, err := NewPublicKeys("foo", testdata.PEMBytes["rsa"], "") + c.Assert(err, IsNil) + c.Assert(auth, NotNil) + + ps, err := DefaultClient.NewUploadPackSession(ep, auth) + //Since the proxy server is not running, we expect an error. + c.Assert(ps, IsNil) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, "socks connect .* dial tcp 127.0.0.1:1080: .*") +} + type mockSSHConfig struct { Values map[string]map[string]string } From ce0b76e7674d683db547103bc773305129a0ded4 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Sun, 29 Oct 2023 16:35:09 -0400 Subject: [PATCH 178/205] git: implement upload-server-info. Fixes #731 This adds UpdateServerInfo along with a new go-git command to generate info files to help git dumb http serve refs and their objects. This also updates the docs to reflect this. Docs: https://git-scm.com/docs/git-update-server-info Fixes: https://github.com/go-git/go-git/issues/731 --- COMPATIBILITY.md | 8 +- cli/go-git/main.go | 1 + cli/go-git/update_server_info.go | 34 +++++ internal/reference/sort.go | 14 ++ plumbing/serverinfo/serverinfo.go | 94 +++++++++++++ plumbing/serverinfo/serverinfo_test.go | 185 +++++++++++++++++++++++++ 6 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 cli/go-git/update_server_info.go create mode 100644 internal/reference/sort.go create mode 100644 plumbing/serverinfo/serverinfo.go create mode 100644 plumbing/serverinfo/serverinfo_test.go diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index bbffea522..c1f280d4d 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -109,10 +109,10 @@ compatibility status with go-git. ## Server admin -| Feature | Sub-feature | Status | Notes | Examples | -| -------------------- | ----------- | ------ | ----- | -------- | -| `daemon` | | ❌ | | | -| `update-server-info` | | ❌ | | | +| Feature | Sub-feature | Status | Notes | Examples | +| -------------------- | ----------- | ------ | ----- | ----------------------------------------- | +| `daemon` | | ❌ | | | +| `update-server-info` | | ✅ | | [cli](./cli/go-git/update_server_info.go) | ## Advanced diff --git a/cli/go-git/main.go b/cli/go-git/main.go index 97b8c3e2a..0a5ad2c26 100644 --- a/cli/go-git/main.go +++ b/cli/go-git/main.go @@ -22,6 +22,7 @@ func main() { } parser := flags.NewNamedParser(bin, flags.Default) + parser.AddCommand("update-server-info", "", "", &CmdUpdateServerInfo{}) parser.AddCommand("receive-pack", "", "", &CmdReceivePack{}) parser.AddCommand("upload-pack", "", "", &CmdUploadPack{}) parser.AddCommand("version", "Show the version information.", "", &CmdVersion{}) diff --git a/cli/go-git/update_server_info.go b/cli/go-git/update_server_info.go new file mode 100644 index 000000000..a7f3e3e39 --- /dev/null +++ b/cli/go-git/update_server_info.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "os" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/serverinfo" + "github.com/go-git/go-git/v5/storage/filesystem" +) + +// CmdUpdateServerInfo command updates the server info files in the repository. +// This is used by git http transport (dumb) to generate a list of available +// refs for the repository. See: +// https://git-scm.com/docs/git-update-server-info +type CmdUpdateServerInfo struct { + cmd +} + +// Usage returns the usage of the command. +func (CmdUpdateServerInfo) Usage() string { + return fmt.Sprintf("within a git repository run: %s", os.Args[0]) +} + +// Execute runs the command. +func (c *CmdUpdateServerInfo) Execute(args []string) error { + r, err := git.PlainOpen(".") + if err != nil { + return err + } + + fs := r.Storer.(*filesystem.Storage).Filesystem() + return serverinfo.UpdateServerInfo(r.Storer, fs) +} diff --git a/internal/reference/sort.go b/internal/reference/sort.go new file mode 100644 index 000000000..726edbdd3 --- /dev/null +++ b/internal/reference/sort.go @@ -0,0 +1,14 @@ +package reference + +import ( + "sort" + + "github.com/go-git/go-git/v5/plumbing" +) + +// Sort sorts the references by name to ensure a consistent order. +func Sort(refs []*plumbing.Reference) { + sort.Slice(refs, func(i, j int) bool { + return refs[i].Name() < refs[j].Name() + }) +} diff --git a/plumbing/serverinfo/serverinfo.go b/plumbing/serverinfo/serverinfo.go new file mode 100644 index 000000000..d7ea7ef06 --- /dev/null +++ b/plumbing/serverinfo/serverinfo.go @@ -0,0 +1,94 @@ +package serverinfo + +import ( + "fmt" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/internal/reference" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/go-git/go-git/v5/storage" +) + +// UpdateServerInfo updates the server info files in the repository. +// +// It generates a list of available refs for the repository. +// Used by git http transport (dumb), for more information refer to: +// https://git-scm.com/book/id/v2/Git-Internals-Transfer-Protocols#_the_dumb_protocol +func UpdateServerInfo(s storage.Storer, fs billy.Filesystem) error { + pos, ok := s.(storer.PackedObjectStorer) + if !ok { + return git.ErrPackedObjectsNotSupported + } + + infoRefs, err := fs.Create("info/refs") + if err != nil { + return err + } + + defer infoRefs.Close() + + refsIter, err := s.IterReferences() + if err != nil { + return err + } + + defer refsIter.Close() + + var refs []*plumbing.Reference + if err := refsIter.ForEach(func(ref *plumbing.Reference) error { + refs = append(refs, ref) + return nil + }); err != nil { + return err + } + + reference.Sort(refs) + for _, ref := range refs { + name := ref.Name() + hash := ref.Hash() + switch ref.Type() { + case plumbing.SymbolicReference: + if name == plumbing.HEAD { + continue + } + ref, err := s.Reference(ref.Target()) + if err != nil { + return err + } + + hash = ref.Hash() + fallthrough + case plumbing.HashReference: + fmt.Fprintf(infoRefs, "%s\t%s\n", hash, name) + if name.IsTag() { + tag, err := object.GetTag(s, hash) + if err == nil { + fmt.Fprintf(infoRefs, "%s\t%s^{}\n", tag.Target, name) + } + } + } + } + + infoPacks, err := fs.Create("objects/info/packs") + if err != nil { + return err + } + + defer infoPacks.Close() + + packs, err := pos.ObjectPacks() + if err != nil { + return err + } + + for _, p := range packs { + fmt.Fprintf(infoPacks, "P pack-%s.pack\n", p) + } + + fmt.Fprintln(infoPacks) + + return nil +} diff --git a/plumbing/serverinfo/serverinfo_test.go b/plumbing/serverinfo/serverinfo_test.go new file mode 100644 index 000000000..0a52ea2f9 --- /dev/null +++ b/plumbing/serverinfo/serverinfo_test.go @@ -0,0 +1,185 @@ +package serverinfo + +import ( + "io" + "strings" + "testing" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" + 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/object" + "github.com/go-git/go-git/v5/plumbing/storer" + "github.com/go-git/go-git/v5/storage" + "github.com/go-git/go-git/v5/storage/memory" + . "gopkg.in/check.v1" +) + +type ServerInfoSuite struct{} + +var _ = Suite(&ServerInfoSuite{}) + +func Test(t *testing.T) { TestingT(t) } + +func (s *ServerInfoSuite) TestUpdateServerInfoInit(c *C) { + fs := memfs.New() + st := memory.NewStorage() + r, err := git.Init(st, fs) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + err = UpdateServerInfo(st, fs) + c.Assert(err, IsNil) +} + +func assertInfoRefs(c *C, st storage.Storer, fs billy.Filesystem) { + refsFile, err := fs.Open("info/refs") + c.Assert(err, IsNil) + + defer refsFile.Close() + bts, err := io.ReadAll(refsFile) + c.Assert(err, IsNil) + + localRefs := make(map[plumbing.ReferenceName]plumbing.Hash) + for _, line := range strings.Split(string(bts), "\n") { + if line == "" { + continue + } + parts := strings.Split(line, "\t") + c.Assert(parts, HasLen, 2) + hash := plumbing.NewHash(parts[0]) + name := plumbing.ReferenceName(parts[1]) + localRefs[name] = hash + } + + refs, err := st.IterReferences() + c.Assert(err, IsNil) + + err = refs.ForEach(func(ref *plumbing.Reference) error { + name := ref.Name() + hash := ref.Hash() + switch ref.Type() { + case plumbing.SymbolicReference: + if name == plumbing.HEAD { + return nil + } + ref, err := st.Reference(ref.Target()) + c.Assert(err, IsNil) + hash = ref.Hash() + fallthrough + case plumbing.HashReference: + h, ok := localRefs[name] + c.Assert(ok, Equals, true) + c.Assert(h, Equals, hash) + if name.IsTag() { + tag, err := object.GetTag(st, hash) + if err == nil { + t, ok := localRefs[name+"^{}"] + c.Assert(ok, Equals, true) + c.Assert(t, Equals, tag.Target) + } + } + } + return nil + }) + + c.Assert(err, IsNil) +} + +func assertObjectPacks(c *C, st storage.Storer, fs billy.Filesystem) { + infoPacks, err := fs.Open("objects/info/packs") + c.Assert(err, IsNil) + + defer infoPacks.Close() + bts, err := io.ReadAll(infoPacks) + c.Assert(err, IsNil) + + pos, ok := st.(storer.PackedObjectStorer) + c.Assert(ok, Equals, true) + localPacks := make(map[string]struct{}) + packs, err := pos.ObjectPacks() + c.Assert(err, IsNil) + + for _, line := range strings.Split(string(bts), "\n") { + if line == "" { + continue + } + parts := strings.Split(line, " ") + c.Assert(parts, HasLen, 2) + pack := strings.TrimPrefix(parts[1], "pack-") + pack = strings.TrimSuffix(pack, ".pack") + localPacks[pack] = struct{}{} + } + + for _, p := range packs { + _, ok := localPacks[p.String()] + c.Assert(ok, Equals, true) + } +} + +func (s *ServerInfoSuite) TestUpdateServerInfoTags(c *C) { + fs := memfs.New() + st := memory.NewStorage() + r, err := git.Clone(st, fs, &git.CloneOptions{ + URL: fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().URL, + }) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + err = UpdateServerInfo(st, fs) + c.Assert(err, IsNil) + + assertInfoRefs(c, st, fs) + assertObjectPacks(c, st, fs) +} + +func (s *ServerInfoSuite) TestUpdateServerInfoBasic(c *C) { + fs := memfs.New() + st := memory.NewStorage() + r, err := git.Clone(st, fs, &git.CloneOptions{ + URL: fixtures.Basic().One().URL, + }) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + err = UpdateServerInfo(st, fs) + c.Assert(err, IsNil) + + assertInfoRefs(c, st, fs) + assertObjectPacks(c, st, fs) +} + +func (s *ServerInfoSuite) TestUpdateServerInfoBasicChange(c *C) { + fs := memfs.New() + st := memory.NewStorage() + r, err := git.Clone(st, fs, &git.CloneOptions{ + URL: fixtures.Basic().One().URL, + }) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + err = UpdateServerInfo(st, fs) + c.Assert(err, IsNil) + + assertInfoRefs(c, st, fs) + assertObjectPacks(c, st, fs) + + head, err := r.Head() + c.Assert(err, IsNil) + + ref := plumbing.NewHashReference("refs/heads/my-branch", head.Hash()) + err = r.Storer.SetReference(ref) + c.Assert(err, IsNil) + + _, err = r.CreateTag("test-tag", head.Hash(), &git.CreateTagOptions{ + Message: "test-tag", + }) + c.Assert(err, IsNil) + + err = UpdateServerInfo(st, fs) + + assertInfoRefs(c, st, fs) + assertObjectPacks(c, st, fs) +} From 71db7fa9023ed5e0b9b8a6b6cac81f9d3c787972 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:21:52 +0000 Subject: [PATCH 179/205] build: bump golang.org/x/sys from 0.13.0 to 0.14.0 Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.13.0 to 0.14.0. - [Commits](https://github.com/golang/sys/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 25be1e1d4..383a7cb84 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.14.0 golang.org/x/net v0.17.0 - golang.org/x/sys v0.13.0 + golang.org/x/sys v0.14.0 golang.org/x/text v0.13.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) diff --git a/go.sum b/go.sum index 9a2760a1c..010c325e5 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= From 3c3e49e73a2531e2cf89e314d1766fb7f8e0fee4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:19:42 +0000 Subject: [PATCH 180/205] build: bump golang.org/x/text from 0.13.0 to 0.14.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 383a7cb84..9be458cf4 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( golang.org/x/crypto v0.14.0 golang.org/x/net v0.17.0 golang.org/x/sys v0.14.0 - golang.org/x/text v0.13.0 + golang.org/x/text v0.14.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) diff --git a/go.sum b/go.sum index 010c325e5..b0817f8b9 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= From f7cd844ced42685ed1e6676b0598f064ea5a5bc8 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Mon, 6 Nov 2023 21:31:10 +0000 Subject: [PATCH 181/205] plumbing: format/packfile, Refactor patch delta The changes aim to simplify the code and reduce duplication. Signed-off-by: Paulo Gomes --- plumbing/format/packfile/diff_delta.go | 5 +- plumbing/format/packfile/patch_delta.go | 187 ++++++++++-------------- 2 files changed, 79 insertions(+), 113 deletions(-) diff --git a/plumbing/format/packfile/diff_delta.go b/plumbing/format/packfile/diff_delta.go index 2c7a33581..8898e5830 100644 --- a/plumbing/format/packfile/diff_delta.go +++ b/plumbing/format/packfile/diff_delta.go @@ -17,8 +17,11 @@ const ( s = 16 // https://github.com/git/git/blob/f7466e94375b3be27f229c78873f0acf8301c0a5/diff-delta.c#L428 - // Max size of a copy operation (64KB) + // Max size of a copy operation (64KB). maxCopySize = 64 * 1024 + + // Min size of a copy operation. + minCopySize = 4 ) // GetDelta returns an EncodedObject of type OFSDeltaObject. Base and Target object, diff --git a/plumbing/format/packfile/patch_delta.go b/plumbing/format/packfile/patch_delta.go index 67c20ff78..960769c7c 100644 --- a/plumbing/format/packfile/patch_delta.go +++ b/plumbing/format/packfile/patch_delta.go @@ -18,7 +18,33 @@ import ( // and https://github.com/tarruda/node-git-core/blob/master/src/js/delta.js // for details about the delta format. -const deltaSizeMin = 4 +var ( + ErrInvalidDelta = errors.New("invalid delta") + ErrDeltaCmd = errors.New("wrong delta command") +) + +const ( + payload = 0x7f // 0111 1111 + continuation = 0x80 // 1000 0000 +) + +type offset struct { + mask byte + shift uint +} + +var offsets = []offset{ + {mask: 0x01, shift: 0}, + {mask: 0x02, shift: 8}, + {mask: 0x04, shift: 16}, + {mask: 0x08, shift: 24}, +} + +var sizes = []offset{ + {mask: 0x10, shift: 0}, + {mask: 0x20, shift: 8}, + {mask: 0x40, shift: 16}, +} // ApplyDelta writes to target the result of applying the modification deltas in delta to base. func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) { @@ -59,11 +85,6 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) { return err } -var ( - ErrInvalidDelta = errors.New("invalid delta") - ErrDeltaCmd = errors.New("wrong delta command") -) - // PatchDelta returns the result of applying the modification deltas in delta to src. // An error will be returned if delta is corrupted (ErrDeltaLen) or an action command // is not copy from source or copy from delta (ErrDeltaCmd). @@ -121,7 +142,8 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo return } - if isCopyFromSrc(cmd) { + switch { + case isCopyFromSrc(cmd): offset, err := decodeOffsetByteReader(cmd, deltaBuf) if err != nil { _ = dstWr.CloseWithError(err) @@ -174,7 +196,8 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo } remainingTargetSz -= sz basePos += sz - } else if isCopyFromDelta(cmd) { + + case isCopyFromDelta(cmd): sz := uint(cmd) // cmd is the size itself if invalidSize(sz, targetSz) { _ = dstWr.CloseWithError(ErrInvalidDelta) @@ -186,10 +209,12 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo } remainingTargetSz -= sz - } else { + + default: _ = dstWr.CloseWithError(ErrDeltaCmd) return } + if remainingTargetSz <= 0 { _ = dstWr.Close() return @@ -201,7 +226,7 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo } func patchDelta(dst *bytes.Buffer, src, delta []byte) error { - if len(delta) < deltaSizeMin { + if len(delta) < minCopySize { return ErrInvalidDelta } @@ -222,7 +247,9 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error { cmd = delta[0] delta = delta[1:] - if isCopyFromSrc(cmd) { + + switch { + case isCopyFromSrc(cmd): var offset, sz uint var err error offset, delta, err = decodeOffset(cmd, delta) @@ -241,7 +268,8 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error { } dst.Write(src[offset : offset+sz]) remainingTargetSz -= sz - } else if isCopyFromDelta(cmd) { + + case isCopyFromDelta(cmd): sz := uint(cmd) // cmd is the size itself if invalidSize(sz, targetSz) { return ErrInvalidDelta @@ -254,7 +282,8 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error { dst.Write(delta[0:sz]) remainingTargetSz -= sz delta = delta[sz:] - } else { + + default: return ErrDeltaCmd } @@ -408,48 +437,24 @@ func decodeLEB128ByteReader(input io.ByteReader) (uint, error) { return num, nil } -const ( - payload = 0x7f // 0111 1111 - continuation = 0x80 // 1000 0000 -) - func isCopyFromSrc(cmd byte) bool { - return (cmd & 0x80) != 0 + return (cmd & continuation) != 0 } func isCopyFromDelta(cmd byte) bool { - return (cmd&0x80) == 0 && cmd != 0 + return (cmd&continuation) == 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 + for _, o := range offsets { + if (cmd & o.mask) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + offset |= uint(next) << o.shift } - offset |= uint(next) << 24 } return offset, nil @@ -457,33 +462,14 @@ func decodeOffsetByteReader(cmd byte, delta io.ByteReader) (uint, error) { func decodeOffset(cmd byte, delta []byte) (uint, []byte, error) { var offset uint - if (cmd & 0x01) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta - } - offset = uint(delta[0]) - delta = delta[1:] - } - if (cmd & 0x02) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta - } - offset |= uint(delta[0]) << 8 - delta = delta[1:] - } - if (cmd & 0x04) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta - } - offset |= uint(delta[0]) << 16 - delta = delta[1:] - } - if (cmd & 0x08) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta + for _, o := range offsets { + if (cmd & o.mask) != 0 { + if len(delta) == 0 { + return 0, nil, ErrInvalidDelta + } + offset |= uint(delta[0]) << o.shift + delta = delta[1:] } - offset |= uint(delta[0]) << 24 - delta = delta[1:] } return offset, delta, nil @@ -491,29 +477,18 @@ func decodeOffset(cmd byte, delta []byte) (uint, []byte, error) { 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 + for _, s := range sizes { + if (cmd & s.mask) != 0 { + next, err := delta.ReadByte() + if err != nil { + return 0, err + } + sz |= uint(next) << s.shift } - sz |= uint(next) << 16 } + if sz == 0 { - sz = 0x10000 + sz = maxCopySize } return sz, nil @@ -521,29 +496,17 @@ func decodeSizeByteReader(cmd byte, delta io.ByteReader) (uint, error) { func decodeSize(cmd byte, delta []byte) (uint, []byte, error) { var sz uint - if (cmd & 0x10) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta - } - sz = uint(delta[0]) - delta = delta[1:] - } - if (cmd & 0x20) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta - } - sz |= uint(delta[0]) << 8 - delta = delta[1:] - } - if (cmd & 0x40) != 0 { - if len(delta) == 0 { - return 0, nil, ErrInvalidDelta + for _, s := range sizes { + if (cmd & s.mask) != 0 { + if len(delta) == 0 { + return 0, nil, ErrInvalidDelta + } + sz |= uint(delta[0]) << s.shift + delta = delta[1:] } - sz |= uint(delta[0]) << 16 - delta = delta[1:] } if sz == 0 { - sz = 0x10000 + sz = maxCopySize } return sz, delta, nil From 242eac6741c4bf82c8e53d69ac0ae2ebf9c789d8 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 7 Nov 2023 12:07:14 -0500 Subject: [PATCH 182/205] cli: separate go module for cli --- cli/go-git/go.mod | 33 +++++++++++ cli/go-git/go.sum | 137 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 - go.sum | 3 - 4 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 cli/go-git/go.mod create mode 100644 cli/go-git/go.sum diff --git a/cli/go-git/go.mod b/cli/go-git/go.mod new file mode 100644 index 000000000..bde9014cc --- /dev/null +++ b/cli/go-git/go.mod @@ -0,0 +1,33 @@ +module github.com/go-git/v5/go-git/cli/go-git + +go 1.19 + +require ( + github.com/go-git/go-git/v5 v5.10.1-0.20231107163107-e54a6ae399e9 + github.com/jessevdk/go-flags v1.5.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/acomagu/bufpipe v1.0.4 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/tools v0.13.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect +) diff --git a/cli/go-git/go.sum b/cli/go-git/go.sum new file mode 100644 index 000000000..00a9140dc --- /dev/null +++ b/cli/go-git/go.sum @@ -0,0 +1,137 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +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/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git/v5 v5.10.1-0.20231107163107-e54a6ae399e9 h1:uNsBlaMgh9sjbk+uvOJSJm+ixmoJoXbeDFNBCgYqUXo= +github.com/go-git/go-git/v5 v5.10.1-0.20231107163107-e54a6ae399e9/go.mod h1:vQMZfh4ro7khF6K55zuBL6sU45QYjjppZ6Cot47a9t4= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +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 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +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.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +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-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-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-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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +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/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/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +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 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod index 9be458cf4..51fc3e834 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/go-cmp v0.6.0 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 - github.com/jessevdk/go-flags v1.5.0 github.com/kevinburke/ssh_config v1.2.0 github.com/pjbgf/sha1cd v0.3.0 github.com/sergi/go-diff v1.1.0 diff --git a/go.sum b/go.sum index b0817f8b9..a6f00e58a 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 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= @@ -108,7 +106,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From e40360644f4a6e368e916db5a930c17c47f64a78 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 7 Nov 2023 12:11:03 -0500 Subject: [PATCH 183/205] build: add cli/go-git to dependabot --- .github/dependabot.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 6a7a66bfa..403f428e4 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -13,3 +13,10 @@ updates: interval: "daily" commit-message: prefix: "build" + + - package-ecosystem: "gomod" + directory: "/cli/go-git" + schedule: + interval: "daily" + commit-message: + prefix: "build" From fbc6d7f5d37846133ca92dabf2e2115fcee06e19 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 7 Nov 2023 12:42:10 -0500 Subject: [PATCH 184/205] build: fix go-git binary build --- plumbing/transport/file/common_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plumbing/transport/file/common_test.go b/plumbing/transport/file/common_test.go index 7e033a80b..a217e9716 100644 --- a/plumbing/transport/file/common_test.go +++ b/plumbing/transport/file/common_test.go @@ -29,8 +29,8 @@ func (s *CommonSuite) SetUpSuite(c *C) { s.ReceivePackBin = filepath.Join(s.tmpDir, "git-receive-pack") s.UploadPackBin = filepath.Join(s.tmpDir, "git-upload-pack") bin := filepath.Join(s.tmpDir, "go-git") - cmd := exec.Command("go", "build", "-o", bin, - "../../../cli/go-git/...") + cmd := exec.Command("go", "build", "-o", bin) + cmd.Dir = "../../../cli/go-git" c.Assert(cmd.Run(), IsNil) c.Assert(os.Symlink(bin, s.ReceivePackBin), IsNil) c.Assert(os.Symlink(bin, s.UploadPackBin), IsNil) From a0bdfd93726140d0bccc403584f8e09aacd915ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:07:27 +0000 Subject: [PATCH 185/205] build: bump golang.org/x/crypto from 0.14.0 to 0.15.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.15.0. - [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.15.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 51fc3e834..203dd58a0 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.2.1 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.15.0 golang.org/x/net v0.17.0 golang.org/x/sys v0.14.0 golang.org/x/text v0.14.0 diff --git a/go.sum b/go.sum index a6f00e58a..6b1bc44f7 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= @@ -125,7 +125,7 @@ golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From f1485ab725f8ecbaf61ca252d9237b1132206275 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 14:10:21 +0000 Subject: [PATCH 186/205] build: bump golang.org/x/net from 0.17.0 to 0.18.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.18.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.18.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 203dd58a0..c7bfb1437 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/skeema/knownhosts v1.2.1 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.15.0 - golang.org/x/net v0.17.0 + golang.org/x/net v0.18.0 golang.org/x/sys v0.14.0 golang.org/x/text v0.14.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c diff --git a/go.sum b/go.sum index 6b1bc44f7..4b75ae7b8 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= 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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From a28a4ac965f714e7506ba26212556fd3d9817712 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 13 Nov 2023 17:53:46 -0500 Subject: [PATCH 187/205] utils: remove ioutil.Pipe and use std library io.Pipe ioutil.Pipe literally calls io.Pipe. --- go.mod | 1 - go.sum | 4 ---- plumbing/transport/file/client.go | 3 +-- plumbing/transport/server/server.go | 2 +- remote.go | 2 +- utils/ioutil/common.go | 12 +----------- utils/ioutil/pipe.go | 9 --------- utils/ioutil/pipe_js.go | 9 --------- 8 files changed, 4 insertions(+), 38 deletions(-) delete mode 100644 utils/ioutil/pipe.go delete mode 100644 utils/ioutil/pipe_js.go diff --git a/go.mod b/go.mod index c7bfb1437..18b60da9e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ go 1.19 require ( dario.cat/mergo v1.0.0 github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 - github.com/acomagu/bufpipe v1.0.4 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a github.com/emirpasic/gods v1.18.1 diff --git a/go.sum b/go.sum index 4b75ae7b8..f5af02373 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 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= @@ -50,8 +48,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= diff --git a/plumbing/transport/file/client.go b/plumbing/transport/file/client.go index 6f0a38012..38714e2ad 100644 --- a/plumbing/transport/file/client.go +++ b/plumbing/transport/file/client.go @@ -11,7 +11,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/internal/common" - "github.com/go-git/go-git/v5/utils/ioutil" "golang.org/x/sys/execabs" ) @@ -112,7 +111,7 @@ func (c *command) Start() error { func (c *command) StderrPipe() (io.Reader, error) { // Pipe returned by Command.StderrPipe has a race with Read + Command.Wait. // We use an io.Pipe and close it after the command finishes. - r, w := ioutil.Pipe() + r, w := io.Pipe() c.cmd.Stderr = w c.stderrCloser = r return r, nil diff --git a/plumbing/transport/server/server.go b/plumbing/transport/server/server.go index 11fa0c801..cf5d6f43f 100644 --- a/plumbing/transport/server/server.go +++ b/plumbing/transport/server/server.go @@ -166,7 +166,7 @@ func (s *upSession) UploadPack(ctx context.Context, req *packp.UploadPackRequest return nil, err } - pr, pw := ioutil.Pipe() + pr, pw := io.Pipe() e := packfile.NewEncoder(pw, s.storer, false) go func() { // TODO: plumb through a pack window. diff --git a/remote.go b/remote.go index bbe275d5b..2b4122e96 100644 --- a/remote.go +++ b/remote.go @@ -1387,7 +1387,7 @@ func pushHashes( allDelete bool, ) (*packp.ReportStatus, error) { - rd, wr := ioutil.Pipe() + rd, wr := io.Pipe() config, err := s.Config() if err != nil { diff --git a/utils/ioutil/common.go b/utils/ioutil/common.go index b0ace4e62..235af717b 100644 --- a/utils/ioutil/common.go +++ b/utils/ioutil/common.go @@ -195,7 +195,7 @@ func NewWriterOnError(w io.Writer, notify func(error)) io.Writer { } // NewWriteCloserOnError returns a io.WriteCloser that call the notify function -//when an unexpected (!io.EOF) error happens, after call Write function. +// when an unexpected (!io.EOF) error happens, after call Write function. func NewWriteCloserOnError(w io.WriteCloser, notify func(error)) io.WriteCloser { return NewWriteCloser(NewWriterOnError(w, notify), w) } @@ -208,13 +208,3 @@ func (r *writerOnError) Write(p []byte) (n int, err error) { return } - -type PipeReader interface { - io.ReadCloser - CloseWithError(err error) error -} - -type PipeWriter interface { - io.WriteCloser - CloseWithError(err error) error -} diff --git a/utils/ioutil/pipe.go b/utils/ioutil/pipe.go deleted file mode 100644 index f30c452fa..000000000 --- a/utils/ioutil/pipe.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !js - -package ioutil - -import "io" - -func Pipe() (PipeReader, PipeWriter) { - return io.Pipe() -} diff --git a/utils/ioutil/pipe_js.go b/utils/ioutil/pipe_js.go deleted file mode 100644 index cf102e6ef..000000000 --- a/utils/ioutil/pipe_js.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build js - -package ioutil - -import "github.com/acomagu/bufpipe" - -func Pipe() (PipeReader, PipeWriter) { - return bufpipe.New(nil) -} From a8e17350b8b1c97e252964c863fca0fac9ff5cec Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 8 Nov 2023 18:40:06 -0500 Subject: [PATCH 188/205] internal: add trace package This adds a generic tracing package to log messages to output if target is enabled. --- internal/trace/trace.go | 55 +++++++++++++++++++++ internal/trace/trace_test.go | 95 ++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 internal/trace/trace.go create mode 100644 internal/trace/trace_test.go diff --git a/internal/trace/trace.go b/internal/trace/trace.go new file mode 100644 index 000000000..3e15c5b9f --- /dev/null +++ b/internal/trace/trace.go @@ -0,0 +1,55 @@ +package trace + +import ( + "fmt" + "log" + "os" + "sync/atomic" +) + +var ( + // logger is the logger to use for tracing. + logger = newLogger() + + // current is the targets that are enabled for tracing. + current atomic.Int32 +) + +func newLogger() *log.Logger { + return log.New(os.Stderr, "", log.Ltime|log.Lmicroseconds|log.Lshortfile) +} + +// Target is a tracing target. +type Target int32 + +const ( + // General traces general operations. + General Target = 1 << iota + + // Packet traces git packets. + Packet +) + +// SetTarget sets the tracing targets. +func SetTarget(target Target) { + current.Store(int32(target)) +} + +// SetLogger sets the logger to use for tracing. +func SetLogger(l *log.Logger) { + logger = l +} + +// Print prints the given message only if the target is enabled. +func (t Target) Print(args ...interface{}) { + if int32(t)¤t.Load() != 0 { + logger.Output(2, fmt.Sprint(args...)) // nolint: errcheck + } +} + +// Printf prints the given message only if the target is enabled. +func (t Target) Printf(format string, args ...interface{}) { + if int32(t)¤t.Load() != 0 { + logger.Output(2, fmt.Sprintf(format, args...)) // nolint: errcheck + } +} diff --git a/internal/trace/trace_test.go b/internal/trace/trace_test.go new file mode 100644 index 000000000..6f8f14094 --- /dev/null +++ b/internal/trace/trace_test.go @@ -0,0 +1,95 @@ +package trace + +import ( + "bytes" + "io" + "log" + "testing" +) + +func TestMain(m *testing.M) { + defer SetLogger(newLogger()) + if code := m.Run(); code != 0 { + panic(code) + } +} + +func setUpTest(t testing.TB, buf *bytes.Buffer) { + t.Cleanup(func() { + if buf != nil { + buf.Reset() + } + SetTarget(0) + }) + w := io.Discard + if buf != nil { + w = buf + } + SetLogger(log.New(w, "", 0)) +} + +func TestEmpty(t *testing.T) { + var buf bytes.Buffer + setUpTest(t, &buf) + General.Print("test") + if buf.String() != "" { + t.Error("expected empty string") + } +} + +func TestOneTarget(t *testing.T) { + var buf bytes.Buffer + setUpTest(t, &buf) + SetTarget(General) + General.Print("test") + if buf.String() != "test\n" { + t.Error("expected 'test'") + } +} + +func TestMultipleTargets(t *testing.T) { + var buf bytes.Buffer + setUpTest(t, &buf) + SetTarget(General | Packet) + General.Print("a") + Packet.Print("b") + if buf.String() != "a\nb\n" { + t.Error("expected 'a\nb\n'") + } +} + +func TestPrintf(t *testing.T) { + var buf bytes.Buffer + setUpTest(t, &buf) + SetTarget(General) + General.Printf("a %d", 1) + if buf.String() != "a 1\n" { + t.Error("expected 'a 1\n'") + } +} + +func TestDisabledMultipleTargets(t *testing.T) { + var buf bytes.Buffer + setUpTest(t, &buf) + SetTarget(General) + General.Print("a") + Packet.Print("b") + if buf.String() != "a\n" { + t.Error("expected 'a\n'") + } +} + +func BenchmarkDisabledTarget(b *testing.B) { + setUpTest(b, nil) + for i := 0; i < b.N; i++ { + General.Print("test") + } +} + +func BenchmarkEnabledTarget(b *testing.B) { + setUpTest(b, nil) + SetTarget(General) + for i := 0; i < b.N; i++ { + General.Print("test") + } +} From c3843453da23043b4a4dcb9f7968f72c1e4ae8cc Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 15 Nov 2023 16:53:42 -0500 Subject: [PATCH 189/205] plumbing: format/pktline: trace packets --- plumbing/format/pktline/encoder.go | 4 ++++ plumbing/format/pktline/scanner.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/plumbing/format/pktline/encoder.go b/plumbing/format/pktline/encoder.go index 6d409795b..1b9b84e8a 100644 --- a/plumbing/format/pktline/encoder.go +++ b/plumbing/format/pktline/encoder.go @@ -7,6 +7,8 @@ import ( "errors" "fmt" "io" + + "github.com/go-git/go-git/v5/internal/trace" ) // An Encoder writes pkt-lines to an output stream. @@ -43,6 +45,7 @@ func NewEncoder(w io.Writer) *Encoder { // Flush encodes a flush-pkt to the output stream. func (e *Encoder) Flush() error { + defer trace.Packet.Print("packet: > 0000") _, err := e.w.Write(FlushPkt) return err } @@ -70,6 +73,7 @@ func (e *Encoder) encodeLine(p []byte) error { } n := len(p) + 4 + defer trace.Packet.Printf("packet: > %04x %s", n, p) if _, err := e.w.Write(asciiHex16(n)); err != nil { return err } diff --git a/plumbing/format/pktline/scanner.go b/plumbing/format/pktline/scanner.go index 99aab46e8..86068af79 100644 --- a/plumbing/format/pktline/scanner.go +++ b/plumbing/format/pktline/scanner.go @@ -3,6 +3,8 @@ package pktline import ( "errors" "io" + + "github.com/go-git/go-git/v5/internal/trace" ) const ( @@ -65,6 +67,7 @@ func (s *Scanner) Scan() bool { return false } s.payload = s.payload[:l] + trace.Packet.Printf("packet: < %04x %s", l, s.payload) return true } From c62aa3e780da6f85f6e1ddd7628ab30ec9d92b53 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 16 Nov 2023 11:15:10 -0500 Subject: [PATCH 190/205] utils: move trace to utils Without exposing `trace`, we can't set a target to enable tracing from out of go-git. Fixes: https://github.com/go-git/go-git/pull/916 --- plumbing/format/pktline/encoder.go | 2 +- plumbing/format/pktline/scanner.go | 2 +- {internal => utils}/trace/trace.go | 0 {internal => utils}/trace/trace_test.go | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename {internal => utils}/trace/trace.go (100%) rename {internal => utils}/trace/trace_test.go (100%) diff --git a/plumbing/format/pktline/encoder.go b/plumbing/format/pktline/encoder.go index 1b9b84e8a..b6144faf5 100644 --- a/plumbing/format/pktline/encoder.go +++ b/plumbing/format/pktline/encoder.go @@ -8,7 +8,7 @@ import ( "fmt" "io" - "github.com/go-git/go-git/v5/internal/trace" + "github.com/go-git/go-git/v5/utils/trace" ) // An Encoder writes pkt-lines to an output stream. diff --git a/plumbing/format/pktline/scanner.go b/plumbing/format/pktline/scanner.go index 86068af79..5e85ed092 100644 --- a/plumbing/format/pktline/scanner.go +++ b/plumbing/format/pktline/scanner.go @@ -4,7 +4,7 @@ import ( "errors" "io" - "github.com/go-git/go-git/v5/internal/trace" + "github.com/go-git/go-git/v5/utils/trace" ) const ( diff --git a/internal/trace/trace.go b/utils/trace/trace.go similarity index 100% rename from internal/trace/trace.go rename to utils/trace/trace.go diff --git a/internal/trace/trace_test.go b/utils/trace/trace_test.go similarity index 100% rename from internal/trace/trace_test.go rename to utils/trace/trace_test.go From 05551b70f689944ab558a2b326b2174f313d372a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 16 Nov 2023 11:28:02 -0500 Subject: [PATCH 191/205] plumbing: fix empty uploadpack request error If we have all what we asked for, finish the session and handle error. This is equivalent of git "Already up to date." message. Fixes: https://github.com/go-git/go-git/issues/328 Fixes: https://github.com/go-git/go-git/issues/638 Fixes: https://github.com/go-git/go-git/issues/157 --- plumbing/transport/internal/common/common.go | 6 ++++++ remote.go | 4 ++++ worktree_test.go | 14 ++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index 6574116b1..da1c2acce 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -245,6 +245,12 @@ func (s *session) handleAdvRefDecodeError(err error) error { // returned with the packfile content. The reader must be closed after reading. func (s *session) UploadPack(ctx context.Context, req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { if req.IsEmpty() { + // XXX: IsEmpty means haves are a subset of wants, in that case we have + // everything we asked for. Close the connection and return nil. + if err := s.finish(); err != nil { + return nil, err + } + // TODO:(v6) return nil here return nil, transport.ErrEmptyUploadPackRequest } diff --git a/remote.go b/remote.go index 2b4122e96..8ca9b102f 100644 --- a/remote.go +++ b/remote.go @@ -552,6 +552,10 @@ func (r *Remote) fetchPack(ctx context.Context, o *FetchOptions, s transport.Upl reader, err := s.UploadPack(ctx, req) if err != nil { + if errors.Is(err, transport.ErrEmptyUploadPackRequest) { + // XXX: no packfile provided, everything is up-to-date. + return nil + } return err } diff --git a/worktree_test.go b/worktree_test.go index 712695ae2..180bfb0b4 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -299,6 +299,20 @@ func (s *WorktreeSuite) TestPullAlreadyUptodate(c *C) { c.Assert(err, Equals, NoErrAlreadyUpToDate) } +func (s *WorktreeSuite) TestPullDepth(c *C) { + r, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{ + URL: fixtures.Basic().One().URL, + Depth: 1, + }) + + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + err = w.Pull(&PullOptions{}) + c.Assert(err, Equals, nil) +} + func (s *WorktreeSuite) TestCheckout(c *C) { fs := memfs.New() w := &Worktree{ From 2e14e3ab84edd9b0f54b355aeddfcadf1383ec67 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Mon, 9 Oct 2023 21:37:49 +0100 Subject: [PATCH 192/205] plumbing: transport/git, Improve tests error message When running the tests in an install in which git daemon is not installed (e.g. openSUSE), all the git transport tests will fail with a connection refused error. This was due the git server not being running in the first place. The tests will now fail with 'git daemon cannot be found'. Signed-off-by: Paulo Gomes --- plumbing/transport/git/common_test.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/plumbing/transport/git/common_test.go b/plumbing/transport/git/common_test.go index 73899198f..3cab93314 100644 --- a/plumbing/transport/git/common_test.go +++ b/plumbing/transport/git/common_test.go @@ -1,6 +1,7 @@ package git import ( + "bytes" "fmt" "net" "os" @@ -32,7 +33,12 @@ func (s *BaseSuite) SetUpTest(c *C) { See https://github.com/git-for-windows/git/issues/907`) } - var err error + cmd := exec.Command("git", "daemon", "--help") + output, err := cmd.CombinedOutput() + if err != nil && bytes.Contains(output, []byte("'daemon' is not a git command")) { + c.Fatal("git daemon cannot be found") + } + s.port, err = freePort() c.Assert(err, IsNil) @@ -85,11 +91,15 @@ func (s *BaseSuite) prepareRepository(c *C, f *fixtures.Fixture, name string) *t } func (s *BaseSuite) TearDownTest(c *C) { - _ = s.daemon.Process.Signal(os.Kill) - _ = s.daemon.Wait() + if s.daemon != nil { + _ = s.daemon.Process.Signal(os.Kill) + _ = s.daemon.Wait() + } - err := os.RemoveAll(s.base) - c.Assert(err, IsNil) + if s.base != "" { + err := os.RemoveAll(s.base) + c.Assert(err, IsNil) + } } func freePort() (int, error) { From 5349b8a45ed2712302e85b258d3789808e1bd96d Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 16 Nov 2023 12:08:26 +0100 Subject: [PATCH 193/205] utils: merkletrie, Skip loading sockets as filesystem nodes. Fixes #312 --- utils/merkletrie/filesystem/node.go | 4 ++++ utils/merkletrie/filesystem/node_test.go | 26 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/utils/merkletrie/filesystem/node.go b/utils/merkletrie/filesystem/node.go index ad169ff4a..7bba0d03e 100644 --- a/utils/merkletrie/filesystem/node.go +++ b/utils/merkletrie/filesystem/node.go @@ -103,6 +103,10 @@ func (n *node) calculateChildren() error { continue } + if file.Mode()&os.ModeSocket != 0 { + continue + } + c, err := n.newChildNode(file) if err != nil { return err diff --git a/utils/merkletrie/filesystem/node_test.go b/utils/merkletrie/filesystem/node_test.go index 159e63dcd..b76abc412 100644 --- a/utils/merkletrie/filesystem/node_test.go +++ b/utils/merkletrie/filesystem/node_test.go @@ -2,9 +2,12 @@ package filesystem import ( "bytes" + "fmt" "io" + "net" "os" "path" + "runtime" "testing" "github.com/go-git/go-git/v5/plumbing" @@ -13,6 +16,7 @@ import ( "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-billy/v5/osfs" . "gopkg.in/check.v1" ) @@ -196,6 +200,28 @@ func (s *NoderSuite) TestDiffDirectory(c *C) { c.Assert(a, Equals, merkletrie.Modify) } +func (s *NoderSuite) TestSocket(c *C) { + if runtime.GOOS == "windows" { + c.Skip("socket files do not exist on windows") + } + + td, err := os.MkdirTemp("", "socket-test") + defer os.RemoveAll(td) + c.Assert(err, IsNil) + + sock, err := net.ListenUnix("unix", &net.UnixAddr{Name: fmt.Sprintf("%s/socket", td), Net: "unix"}) + c.Assert(err, IsNil) + defer sock.Close() + + fsA := osfs.New(td) + WriteFile(fsA, "foo", []byte("foo"), 0644) + + noder := NewRootNode(fsA, nil) + childs, err := noder.Children() + c.Assert(err, IsNil) + c.Assert(childs, HasLen, 1) +} + func WriteFile(fs billy.Filesystem, filename string, data []byte, perm os.FileMode) error { f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { From e1875336041ead704df7f481f8691452ef939556 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 17 Nov 2023 15:27:49 -0500 Subject: [PATCH 194/205] plumbing: add git-proto-request type This the request used for the git transport protocol feat: nil writer --- plumbing/protocol/packp/common.go | 5 ++ plumbing/protocol/packp/gitproto.go | 115 ++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 plumbing/protocol/packp/gitproto.go diff --git a/plumbing/protocol/packp/common.go b/plumbing/protocol/packp/common.go index fef50a450..a858323e7 100644 --- a/plumbing/protocol/packp/common.go +++ b/plumbing/protocol/packp/common.go @@ -48,6 +48,11 @@ func isFlush(payload []byte) bool { return len(payload) == 0 } +var ( + // ErrNilWriter is returned when a nil writer is passed to the encoder. + ErrNilWriter = fmt.Errorf("nil writer") +) + // ErrUnexpectedData represents an unexpected data decoding a message type ErrUnexpectedData struct { Msg string diff --git a/plumbing/protocol/packp/gitproto.go b/plumbing/protocol/packp/gitproto.go new file mode 100644 index 000000000..806e44ce9 --- /dev/null +++ b/plumbing/protocol/packp/gitproto.go @@ -0,0 +1,115 @@ +package packp + +import ( + "fmt" + "io" + "strings" + + "github.com/go-git/go-git/v5/plumbing/format/pktline" +) + +var ( + // ErrInvalidGitProtoRequest is returned by Decode if the input is not a + // valid git protocol request. + ErrInvalidGitProtoRequest = fmt.Errorf("invalid git protocol request") +) + +// GitProtoRequest is a command request for the git protocol. +// It is used to send the command, endpoint, and extra parameters to the +// remote. +// See https://git-scm.com/docs/pack-protocol#_git_transport +type GitProtoRequest struct { + RequestCommand string + Pathname string + + // Optional + Host string + + // Optional + ExtraParams []string +} + +// validate validates the request. +func (g *GitProtoRequest) validate() error { + if g.RequestCommand == "" { + return fmt.Errorf("%w: empty request command", ErrInvalidGitProtoRequest) + } + + if g.Pathname == "" { + return fmt.Errorf("%w: empty pathname", ErrInvalidGitProtoRequest) + } + + return nil +} + +// Encode encodes the request into the writer. +func (g *GitProtoRequest) Encode(w io.Writer) error { + if w == nil { + return ErrNilWriter + } + + if err := g.validate(); err != nil { + return err + } + + p := pktline.NewEncoder(w) + req := fmt.Sprintf("%s %s\x00", g.RequestCommand, g.Pathname) + if host := g.Host; host != "" { + req += fmt.Sprintf("host=%s\x00", host) + } + + if len(g.ExtraParams) > 0 { + req += "\x00" + for _, param := range g.ExtraParams { + req += param + "\x00" + } + } + + if err := p.Encode([]byte(req)); err != nil { + return err + } + + return nil +} + +// Decode decodes the request from the reader. +func (g *GitProtoRequest) Decode(r io.Reader) error { + s := pktline.NewScanner(r) + if !s.Scan() { + return s.Err() + } + + line := string(s.Bytes()) + if len(line) == 0 { + return io.EOF + } + + if line[len(line)-1] != 0 { + return fmt.Errorf("%w: missing null terminator", ErrInvalidGitProtoRequest) + } + + parts := strings.SplitN(line, " ", 2) + if len(parts) != 2 { + return fmt.Errorf("%w: short request", ErrInvalidGitProtoRequest) + } + + g.RequestCommand = parts[0] + params := strings.Split(parts[1], string(null)) + if len(params) < 1 { + return fmt.Errorf("%w: missing pathname", ErrInvalidGitProtoRequest) + } + + if len(params) > 1 { + g.Host = params[1] + } + + if len(params) > 2 { + for _, param := range params[2:] { + if param != "" { + g.ExtraParams = append(g.ExtraParams, param) + } + } + } + + return nil +} From e2c6ae3333a3facd13aa52e1986a2ba2dbc56a9d Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 17 Nov 2023 15:28:25 -0500 Subject: [PATCH 195/205] plumbing: handle pktline erro-line as errors Error when encountering an error-line See https://git-scm.com/docs/pack-protocol Update plumbing/format/pktline/error.go Co-authored-by: Paulo Gomes Update plumbing/format/pktline/error.go Co-authored-by: Paulo Gomes feat: format newline --- plumbing/format/pktline/error.go | 51 ++++++++++++++++++++ plumbing/format/pktline/error_test.go | 68 +++++++++++++++++++++++++++ plumbing/format/pktline/scanner.go | 9 ++++ 3 files changed, 128 insertions(+) create mode 100644 plumbing/format/pktline/error.go create mode 100644 plumbing/format/pktline/error_test.go diff --git a/plumbing/format/pktline/error.go b/plumbing/format/pktline/error.go new file mode 100644 index 000000000..2c0e5a72a --- /dev/null +++ b/plumbing/format/pktline/error.go @@ -0,0 +1,51 @@ +package pktline + +import ( + "bytes" + "errors" + "io" + "strings" +) + +var ( + // ErrInvalidErrorLine is returned by Decode when the packet line is not an + // error line. + ErrInvalidErrorLine = errors.New("expected an error-line") + + errPrefix = []byte("ERR ") +) + +// ErrorLine is a packet line that contains an error message. +// Once this packet is sent by client or server, the data transfer process is +// terminated. +// See https://git-scm.com/docs/pack-protocol#_pkt_line_format +type ErrorLine struct { + Text string +} + +// Error implements the error interface. +func (e *ErrorLine) Error() string { + return e.Text +} + +// Encode encodes the ErrorLine into a packet line. +func (e *ErrorLine) Encode(w io.Writer) error { + p := NewEncoder(w) + return p.Encodef("%s%s\n", string(errPrefix), e.Text) +} + +// Decode decodes a packet line into an ErrorLine. +func (e *ErrorLine) Decode(r io.Reader) error { + s := NewScanner(r) + if !s.Scan() { + return s.Err() + } + + line := s.Bytes() + if !bytes.HasPrefix(line, errPrefix) { + return ErrInvalidErrorLine + } + + e.Text = strings.TrimSpace(string(line[4:])) + return nil +} diff --git a/plumbing/format/pktline/error_test.go b/plumbing/format/pktline/error_test.go new file mode 100644 index 000000000..3cffd20d1 --- /dev/null +++ b/plumbing/format/pktline/error_test.go @@ -0,0 +1,68 @@ +package pktline + +import ( + "bytes" + "errors" + "io" + "testing" +) + +func TestEncodeEmptyErrorLine(t *testing.T) { + e := &ErrorLine{} + err := e.Encode(io.Discard) + if err != nil { + t.Fatal(err) + } +} + +func TestEncodeErrorLine(t *testing.T) { + e := &ErrorLine{ + Text: "something", + } + var buf bytes.Buffer + err := e.Encode(&buf) + if err != nil { + t.Fatal(err) + } + if buf.String() != "0012ERR something\n" { + t.Fatalf("unexpected encoded error line: %q", buf.String()) + } +} + +func TestDecodeEmptyErrorLine(t *testing.T) { + var buf bytes.Buffer + e := &ErrorLine{} + err := e.Decode(&buf) + if err != nil { + t.Fatal(err) + } + if e.Text != "" { + t.Fatalf("unexpected error line: %q", e.Text) + } +} + +func TestDecodeErrorLine(t *testing.T) { + var buf bytes.Buffer + buf.WriteString("000eERR foobar") + var e *ErrorLine + err := e.Decode(&buf) + if !errors.As(err, &e) { + t.Fatalf("expected error line, got: %T: %v", err, err) + } + if e.Text != "foobar" { + t.Fatalf("unexpected error line: %q", e.Text) + } +} + +func TestDecodeErrorLineLn(t *testing.T) { + var buf bytes.Buffer + buf.WriteString("000fERR foobar\n") + var e *ErrorLine + err := e.Decode(&buf) + if !errors.As(err, &e) { + t.Fatalf("expected error line, got: %T: %v", err, err) + } + if e.Text != "foobar" { + t.Fatalf("unexpected error line: %q", e.Text) + } +} diff --git a/plumbing/format/pktline/scanner.go b/plumbing/format/pktline/scanner.go index 5e85ed092..fbb137de0 100644 --- a/plumbing/format/pktline/scanner.go +++ b/plumbing/format/pktline/scanner.go @@ -1,8 +1,10 @@ package pktline import ( + "bytes" "errors" "io" + "strings" "github.com/go-git/go-git/v5/utils/trace" ) @@ -69,6 +71,13 @@ func (s *Scanner) Scan() bool { s.payload = s.payload[:l] trace.Packet.Printf("packet: < %04x %s", l, s.payload) + if bytes.HasPrefix(s.payload, errPrefix) { + s.err = &ErrorLine{ + Text: strings.TrimSpace(string(s.payload[4:])), + } + return false + } + return true } From f46d04a18759071e45b057908050a01e061f1ddd Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 17 Nov 2023 15:29:33 -0500 Subject: [PATCH 196/205] plumbing: transport: use git-proto-request and decode error-line errors --- plumbing/protocol/packp/gitproto.go | 9 +- plumbing/protocol/packp/gitproto_test.go | 99 +++++++++++++++++++ plumbing/transport/git/common.go | 26 +++-- plumbing/transport/internal/common/common.go | 77 +++++++-------- .../transport/internal/common/common_test.go | 60 +---------- 5 files changed, 157 insertions(+), 114 deletions(-) create mode 100644 plumbing/protocol/packp/gitproto_test.go diff --git a/plumbing/protocol/packp/gitproto.go b/plumbing/protocol/packp/gitproto.go index 806e44ce9..0b7ff8f82 100644 --- a/plumbing/protocol/packp/gitproto.go +++ b/plumbing/protocol/packp/gitproto.go @@ -76,7 +76,11 @@ func (g *GitProtoRequest) Encode(w io.Writer) error { func (g *GitProtoRequest) Decode(r io.Reader) error { s := pktline.NewScanner(r) if !s.Scan() { - return s.Err() + err := s.Err() + if err == nil { + return ErrInvalidGitProtoRequest + } + return err } line := string(s.Bytes()) @@ -99,8 +103,9 @@ func (g *GitProtoRequest) Decode(r io.Reader) error { return fmt.Errorf("%w: missing pathname", ErrInvalidGitProtoRequest) } + g.Pathname = params[0] if len(params) > 1 { - g.Host = params[1] + g.Host = strings.TrimPrefix(params[1], "host=") } if len(params) > 2 { diff --git a/plumbing/protocol/packp/gitproto_test.go b/plumbing/protocol/packp/gitproto_test.go new file mode 100644 index 000000000..9cf1049fd --- /dev/null +++ b/plumbing/protocol/packp/gitproto_test.go @@ -0,0 +1,99 @@ +package packp + +import ( + "bytes" + "testing" +) + +func TestEncodeEmptyGitProtoRequest(t *testing.T) { + var buf bytes.Buffer + var p GitProtoRequest + err := p.Encode(&buf) + if err == nil { + t.Fatal("expected error") + } +} + +func TestEncodeGitProtoRequest(t *testing.T) { + var buf bytes.Buffer + p := GitProtoRequest{ + RequestCommand: "command", + Pathname: "pathname", + Host: "host", + ExtraParams: []string{"param1", "param2"}, + } + err := p.Encode(&buf) + if err != nil { + t.Fatal(err) + } + expected := "002ecommand pathname\x00host=host\x00\x00param1\x00param2\x00" + if buf.String() != expected { + t.Fatalf("expected %q, got %q", expected, buf.String()) + } +} + +func TestEncodeInvalidGitProtoRequest(t *testing.T) { + var buf bytes.Buffer + p := GitProtoRequest{ + RequestCommand: "command", + } + err := p.Encode(&buf) + if err == nil { + t.Fatal("expected error") + } +} + +func TestDecodeEmptyGitProtoRequest(t *testing.T) { + var buf bytes.Buffer + var p GitProtoRequest + err := p.Decode(&buf) + if err == nil { + t.Fatal("expected error") + } +} + +func TestDecodeGitProtoRequest(t *testing.T) { + var buf bytes.Buffer + buf.WriteString("002ecommand pathname\x00host=host\x00\x00param1\x00param2\x00") + var p GitProtoRequest + err := p.Decode(&buf) + if err != nil { + t.Fatal(err) + } + expected := GitProtoRequest{ + RequestCommand: "command", + Pathname: "pathname", + Host: "host", + ExtraParams: []string{"param1", "param2"}, + } + if p.RequestCommand != expected.RequestCommand { + t.Fatalf("expected %q, got %q", expected.RequestCommand, p.RequestCommand) + } + if p.Pathname != expected.Pathname { + t.Fatalf("expected %q, got %q", expected.Pathname, p.Pathname) + } + if p.Host != expected.Host { + t.Fatalf("expected %q, got %q", expected.Host, p.Host) + } + if len(p.ExtraParams) != len(expected.ExtraParams) { + t.Fatalf("expected %d, got %d", len(expected.ExtraParams), len(p.ExtraParams)) + } +} + +func TestDecodeInvalidGitProtoRequest(t *testing.T) { + var buf bytes.Buffer + buf.WriteString("0026command \x00host=host\x00\x00param1\x00param2") + var p GitProtoRequest + err := p.Decode(&buf) + if err == nil { + t.Fatal("expected error") + } +} + +func TestValidateEmptyGitProtoRequest(t *testing.T) { + var p GitProtoRequest + err := p.validate() + if err == nil { + t.Fatal("expected error") + } +} diff --git a/plumbing/transport/git/common.go b/plumbing/transport/git/common.go index 92fc0becc..2b878b035 100644 --- a/plumbing/transport/git/common.go +++ b/plumbing/transport/git/common.go @@ -2,12 +2,11 @@ package git import ( - "fmt" "io" "net" "strconv" - "github.com/go-git/go-git/v5/plumbing/format/pktline" + "github.com/go-git/go-git/v5/plumbing/protocol/packp" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/internal/common" "github.com/go-git/go-git/v5/utils/ioutil" @@ -42,10 +41,18 @@ type command struct { // Start executes the command sending the required message to the TCP connection func (c *command) Start() error { - cmd := endpointToCommand(c.command, c.endpoint) + req := packp.GitProtoRequest{ + RequestCommand: c.command, + Pathname: c.endpoint.Path, + } + host := c.endpoint.Host + if c.endpoint.Port != DefaultPort { + host = net.JoinHostPort(c.endpoint.Host, strconv.Itoa(c.endpoint.Port)) + } + + req.Host = host - e := pktline.NewEncoder(c.conn) - return e.Encode([]byte(cmd)) + return req.Encode(c.conn) } func (c *command) connect() error { @@ -90,15 +97,6 @@ func (c *command) StdoutPipe() (io.Reader, error) { return c.conn, nil } -func endpointToCommand(cmd string, ep *transport.Endpoint) string { - host := ep.Host - if ep.Port != DefaultPort { - host = net.JoinHostPort(ep.Host, strconv.Itoa(ep.Port)) - } - - return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path, 0, host, 0) -} - // Close closes the TCP connection and connection. func (c *command) Close() error { if !c.connected { diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index da1c2acce..9e1d02357 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -203,9 +203,22 @@ func (s *session) AdvertisedReferencesContext(ctx context.Context) (*packp.AdvRe } func (s *session) handleAdvRefDecodeError(err error) error { + var errLine *pktline.ErrorLine + if errors.As(err, &errLine) { + if isRepoNotFoundError(errLine.Text) { + return transport.ErrRepositoryNotFound + } + + return errLine + } + // If repository is not found, we get empty stdout and server writes an // error to stderr. - if err == packp.ErrEmptyInput { + if errors.Is(err, packp.ErrEmptyInput) { + // TODO:(v6): handle this error in a better way. + // Instead of checking the stderr output for a specific error message, + // define an ExitError and embed the stderr output and exit (if one + // exists) in the error struct. Just like exec.ExitError. s.finished = true if err := s.checkNotFoundError(); err != nil { return err @@ -399,59 +412,43 @@ func (s *session) checkNotFoundError() error { return transport.ErrRepositoryNotFound } + // TODO:(v6): return server error just as it is without a prefix return fmt.Errorf("unknown error: %s", line) } } -var ( - githubRepoNotFoundErr = "ERROR: Repository not found." - bitbucketRepoNotFoundErr = "conq: repository does not exist." +const ( + githubRepoNotFoundErr = "Repository not found." + bitbucketRepoNotFoundErr = "repository does not exist." localRepoNotFoundErr = "does not appear to be a git repository" - gitProtocolNotFoundErr = "ERR \n Repository not found." - gitProtocolNoSuchErr = "ERR no such repository" - gitProtocolAccessDeniedErr = "ERR access denied" - gogsAccessDeniedErr = "Gogs: Repository does not exist or you do not have access" - gitlabRepoNotFoundErr = "remote: ERROR: The project you were looking for could not be found" + gitProtocolNotFoundErr = "Repository not found." + gitProtocolNoSuchErr = "no such repository" + gitProtocolAccessDeniedErr = "access denied" + gogsAccessDeniedErr = "Repository does not exist or you do not have access" + gitlabRepoNotFoundErr = "The project you were looking for could not be found" ) func isRepoNotFoundError(s string) bool { - if strings.HasPrefix(s, githubRepoNotFoundErr) { - return true - } - - if strings.HasPrefix(s, bitbucketRepoNotFoundErr) { - return true - } - - if strings.HasSuffix(s, localRepoNotFoundErr) { - return true - } - - if strings.HasPrefix(s, gitProtocolNotFoundErr) { - return true - } - - if strings.HasPrefix(s, gitProtocolNoSuchErr) { - return true - } - - if strings.HasPrefix(s, gitProtocolAccessDeniedErr) { - return true - } - - if strings.HasPrefix(s, gogsAccessDeniedErr) { - return true - } - - if strings.HasPrefix(s, gitlabRepoNotFoundErr) { - return true + for _, err := range []string{ + githubRepoNotFoundErr, + bitbucketRepoNotFoundErr, + localRepoNotFoundErr, + gitProtocolNotFoundErr, + gitProtocolNoSuchErr, + gitProtocolAccessDeniedErr, + gogsAccessDeniedErr, + gitlabRepoNotFoundErr, + } { + if strings.Contains(s, err) { + return true + } } return false } // uploadPack implements the git-upload-pack protocol. -func uploadPack(w io.WriteCloser, r io.Reader, req *packp.UploadPackRequest) error { +func uploadPack(w io.WriteCloser, _ io.Reader, req *packp.UploadPackRequest) error { // TODO support multi_ack mode // TODO support multi_ack_detailed mode // TODO support acks for common objects diff --git a/plumbing/transport/internal/common/common_test.go b/plumbing/transport/internal/common/common_test.go index f6f2f67d2..9344bb62b 100644 --- a/plumbing/transport/internal/common/common_test.go +++ b/plumbing/transport/internal/common/common_test.go @@ -22,64 +22,8 @@ func (s *CommonSuite) TestIsRepoNotFoundErrorForUnknownSource(c *C) { c.Assert(isRepoNotFound, Equals, false) } -func (s *CommonSuite) TestIsRepoNotFoundErrorForGithub(c *C) { - msg := fmt.Sprintf("%s : some error stuf", githubRepoNotFoundErr) - - isRepoNotFound := isRepoNotFoundError(msg) - - c.Assert(isRepoNotFound, Equals, true) -} - -func (s *CommonSuite) TestIsRepoNotFoundErrorForBitBucket(c *C) { - msg := fmt.Sprintf("%s : some error stuf", bitbucketRepoNotFoundErr) - - isRepoNotFound := isRepoNotFoundError(msg) - - c.Assert(isRepoNotFound, Equals, true) -} - -func (s *CommonSuite) TestIsRepoNotFoundErrorForLocal(c *C) { - msg := fmt.Sprintf("some error stuf : %s", localRepoNotFoundErr) - - isRepoNotFound := isRepoNotFoundError(msg) - - c.Assert(isRepoNotFound, Equals, true) -} - -func (s *CommonSuite) TestIsRepoNotFoundErrorForGitProtocolNotFound(c *C) { - msg := fmt.Sprintf("%s : some error stuf", gitProtocolNotFoundErr) - - isRepoNotFound := isRepoNotFoundError(msg) - - c.Assert(isRepoNotFound, Equals, true) -} - -func (s *CommonSuite) TestIsRepoNotFoundErrorForGitProtocolNoSuch(c *C) { - msg := fmt.Sprintf("%s : some error stuf", gitProtocolNoSuchErr) - - isRepoNotFound := isRepoNotFoundError(msg) - - c.Assert(isRepoNotFound, Equals, true) -} - -func (s *CommonSuite) TestIsRepoNotFoundErrorForGitProtocolAccessDenied(c *C) { - msg := fmt.Sprintf("%s : some error stuf", gitProtocolAccessDeniedErr) - - isRepoNotFound := isRepoNotFoundError(msg) - - c.Assert(isRepoNotFound, Equals, true) -} - -func (s *CommonSuite) TestIsRepoNotFoundErrorForGogsAccessDenied(c *C) { - msg := fmt.Sprintf("%s : some error stuf", gogsAccessDeniedErr) - - isRepoNotFound := isRepoNotFoundError(msg) - - c.Assert(isRepoNotFound, Equals, true) -} - -func (s *CommonSuite) TestIsRepoNotFoundErrorForGitlab(c *C) { - msg := fmt.Sprintf("%s : some error stuf", gitlabRepoNotFoundErr) +func (s *CommonSuite) TestIsRepoNotFoundError(c *C) { + msg := "no such repository : some error stuf" isRepoNotFound := isRepoNotFoundError(msg) From 861009f70a5b68d1ee134fea7bc1d893088388bb Mon Sep 17 00:00:00 2001 From: Dan Hoizner Date: Wed, 22 Nov 2023 12:51:45 -0500 Subject: [PATCH 197/205] git: stop iterating at oldest shallow when pulling. Fixes #305 --- remote.go | 23 ++++++++++++++++++----- worktree.go | 13 +++++++++++-- worktree_test.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/remote.go b/remote.go index 8ca9b102f..0cb70bc00 100644 --- a/remote.go +++ b/remote.go @@ -1070,7 +1070,7 @@ func checkFastForwardUpdate(s storer.EncodedObjectStorer, remoteRefs storer.Refe return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String()) } - ff, err := isFastForward(s, cmd.Old, cmd.New) + ff, err := isFastForward(s, cmd.Old, cmd.New, nil) if err != nil { return err } @@ -1082,14 +1082,28 @@ func checkFastForwardUpdate(s storer.EncodedObjectStorer, remoteRefs storer.Refe return nil } -func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash) (bool, error) { +func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash, earliestShallow *plumbing.Hash) (bool, error) { c, err := object.GetCommit(s, new) if err != nil { return false, err } + parentsToIgnore := []plumbing.Hash{} + if earliestShallow != nil { + earliestCommit, err := object.GetCommit(s, *earliestShallow) + if err != nil { + return false, err + } + + parentsToIgnore = earliestCommit.ParentHashes + } + found := false - iter := object.NewCommitPreorderIter(c, nil, nil) + // stop iterating at the earlist shallow commit, ignoring its parents + // note: when pull depth is smaller than the number of new changes on the remote, this fails due to missing parents. + // as far as i can tell, without the commits in-between the shallow pull and the earliest shallow, there's no + // real way of telling whether it will be a fast-forward merge. + iter := object.NewCommitPreorderIter(c, nil, parentsToIgnore) err = iter.ForEach(func(c *object.Commit) error { if c.Hash != old { return nil @@ -1205,7 +1219,7 @@ func (r *Remote) updateLocalReferenceStorage( // If the ref exists locally as a non-tag and force is not // specified, only update if the new ref is an ancestor of the old if old != nil && !old.Name().IsTag() && !force && !spec.IsForceUpdate() { - ff, err := isFastForward(r.s, old.Hash(), new.Hash()) + ff, err := isFastForward(r.s, old.Hash(), new.Hash(), nil) if err != nil { return updated, err } @@ -1390,7 +1404,6 @@ func pushHashes( useRefDeltas bool, allDelete bool, ) (*packp.ReportStatus, error) { - rd, wr := io.Pipe() config, err := s.Config() diff --git a/worktree.go b/worktree.go index f8b854dda..974d2336a 100644 --- a/worktree.go +++ b/worktree.go @@ -95,7 +95,15 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error { head, err := w.r.Head() if err == nil { - headAheadOfRef, err := isFastForward(w.r.Storer, ref.Hash(), head.Hash()) + // if we don't have a shallows list, just ignore it + shallowList, _ := w.r.Storer.Shallow() + + var earliestShallow *plumbing.Hash + if len(shallowList) > 0 { + earliestShallow = &shallowList[0] + } + + headAheadOfRef, err := isFastForward(w.r.Storer, ref.Hash(), head.Hash(), earliestShallow) if err != nil { return err } @@ -104,7 +112,7 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error { return NoErrAlreadyUpToDate } - ff, err := isFastForward(w.r.Storer, head.Hash(), ref.Hash()) + ff, err := isFastForward(w.r.Storer, head.Hash(), ref.Hash(), earliestShallow) if err != nil { return err } @@ -188,6 +196,7 @@ func (w *Worktree) Checkout(opts *CheckoutOptions) error { return w.Reset(ro) } + func (w *Worktree) createBranch(opts *CheckoutOptions) error { _, err := w.r.Storer.Reference(opts.Branch) if err == nil { diff --git a/worktree_test.go b/worktree_test.go index 180bfb0b4..8805079d8 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -313,6 +313,42 @@ func (s *WorktreeSuite) TestPullDepth(c *C) { c.Assert(err, Equals, nil) } +func (s *WorktreeSuite) TestPullAfterShallowClone(c *C) { + tempDir, clean := s.TemporalDir() + defer clean() + remoteURL := filepath.Join(tempDir, "remote") + repoDir := filepath.Join(tempDir, "repo") + + remote, err := PlainInit(remoteURL, false) + c.Assert(err, IsNil) + c.Assert(remote, NotNil) + + _ = CommitNewFile(c, remote, "File1") + _ = CommitNewFile(c, remote, "File2") + + repo, err := PlainClone(repoDir, false, &CloneOptions{ + URL: remoteURL, + Depth: 1, + Tags: NoTags, + SingleBranch: true, + ReferenceName: "master", + }) + c.Assert(err, IsNil) + + _ = CommitNewFile(c, remote, "File3") + _ = CommitNewFile(c, remote, "File4") + + w, err := repo.Worktree() + c.Assert(err, IsNil) + + err = w.Pull(&PullOptions{ + RemoteName: DefaultRemoteName, + SingleBranch: true, + ReferenceName: plumbing.NewBranchReferenceName("master"), + }) + c.Assert(err, IsNil) +} + func (s *WorktreeSuite) TestCheckout(c *C) { fs := memfs.New() w := &Worktree{ From aebf868d2a557384539cf91476d21901ab378643 Mon Sep 17 00:00:00 2001 From: Daniel Moch Date: Fri, 24 Nov 2023 12:29:45 -0500 Subject: [PATCH 198/205] plumbing: object, enable renames in getFileStatsFromFilePatches Diff has handled renames by default since 2020. This change sets Name for the renamed file in a manner similar to diffstat. --- plumbing/object/patch.go | 4 +-- plumbing/object/patch_stats_test.go | 54 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 plumbing/object/patch_stats_test.go diff --git a/plumbing/object/patch.go b/plumbing/object/patch.go index 06bc35bbc..dd8fef447 100644 --- a/plumbing/object/patch.go +++ b/plumbing/object/patch.go @@ -317,8 +317,8 @@ func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats { // File is deleted. cs.Name = from.Path() } else if from.Path() != to.Path() { - // File is renamed. Not supported. - // cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path()) + // File is renamed. + cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path()) } else { cs.Name = from.Path() } diff --git a/plumbing/object/patch_stats_test.go b/plumbing/object/patch_stats_test.go new file mode 100644 index 000000000..f393c30c4 --- /dev/null +++ b/plumbing/object/patch_stats_test.go @@ -0,0 +1,54 @@ +package object_test + +import ( + "time" + + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-billy/v5/util" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/memory" + + fixtures "github.com/go-git/go-git-fixtures/v4" + . "gopkg.in/check.v1" +) + +type PatchStatsSuite struct { + fixtures.Suite +} + +var _ = Suite(&PatchStatsSuite{}) + +func (s *PatchStatsSuite) TestStatsWithRename(c *C) { + cm := &git.CommitOptions{ + Author: &object.Signature{Name: "Foo", Email: "foo@example.local", When: time.Now()}, + } + + fs := memfs.New() + r, err := git.Init(memory.NewStorage(), fs) + c.Assert(err, IsNil) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + util.WriteFile(fs, "foo", []byte("foo\nbar\n"), 0644) + + _, err = w.Add("foo") + c.Assert(err, IsNil) + + _, err = w.Commit("foo\n", cm) + c.Assert(err, IsNil) + + _, err = w.Move("foo", "bar") + c.Assert(err, IsNil) + + hash, err := w.Commit("rename foo to bar", cm) + c.Assert(err, IsNil) + + commit, err := r.CommitObject(hash) + c.Assert(err, IsNil) + + fileStats, err := commit.Stats() + c.Assert(err, IsNil) + c.Assert(fileStats[0].Name, Equals, "foo => bar") +} From 93b009cae97a84586b944f34006fcb178e77ed10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:51:23 +0000 Subject: [PATCH 199/205] build: bump golang.org/x/crypto from 0.15.0 to 0.16.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.16.0. - [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.16.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 18b60da9e..7bdee7b7f 100644 --- a/go.mod +++ b/go.mod @@ -21,9 +21,9 @@ require ( github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.2.1 github.com/xanzy/ssh-agent v0.3.3 - golang.org/x/crypto v0.15.0 + golang.org/x/crypto v0.16.0 golang.org/x/net v0.18.0 - golang.org/x/sys v0.14.0 + golang.org/x/sys v0.15.0 golang.org/x/text v0.14.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) diff --git a/go.sum b/go.sum index f5af02373..6f4755b5d 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= @@ -113,15 +113,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From a24037512f4edd5a591a2c8b2077519424225783 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:16:26 +0000 Subject: [PATCH 200/205] build: bump golang.org/x/net from 0.18.0 to 0.19.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.18.0 to 0.19.0. - [Commits](https://github.com/golang/net/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7bdee7b7f..9a18c7a49 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/skeema/knownhosts v1.2.1 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.16.0 - golang.org/x/net v0.18.0 + golang.org/x/net v0.19.0 golang.org/x/sys v0.15.0 golang.org/x/text v0.14.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c diff --git a/go.sum b/go.sum index 6f4755b5d..4be7ec4eb 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From d85bc9920e5d0d9f023f7462096378a71429c549 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:43:14 +0000 Subject: [PATCH 201/205] build: bump github.com/go-git/go-git/v5 in /cli/go-git Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.10.1-0.20231107163107-e54a6ae399e9 to 5.10.1. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/commits/v5.10.1) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- cli/go-git/go.mod | 7 +++---- cli/go-git/go.sum | 18 +++++++----------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/cli/go-git/go.mod b/cli/go-git/go.mod index bde9014cc..5cc0d18ad 100644 --- a/cli/go-git/go.mod +++ b/cli/go-git/go.mod @@ -3,7 +3,7 @@ module github.com/go-git/v5/go-git/cli/go-git go 1.19 require ( - github.com/go-git/go-git/v5 v5.10.1-0.20231107163107-e54a6ae399e9 + github.com/go-git/go-git/v5 v5.10.1 github.com/jessevdk/go-flags v1.5.0 ) @@ -11,7 +11,6 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/emirpasic/gods v1.18.1 // indirect @@ -24,9 +23,9 @@ require ( github.com/sergi/go-diff v1.1.0 // indirect github.com/skeema/knownhosts v1.2.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.15.0 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.18.0 // indirect golang.org/x/sys v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/cli/go-git/go.sum b/cli/go-git/go.sum index 00a9140dc..45c055264 100644 --- a/cli/go-git/go.sum +++ b/cli/go-git/go.sum @@ -5,8 +5,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -26,8 +24,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git/v5 v5.10.1-0.20231107163107-e54a6ae399e9 h1:uNsBlaMgh9sjbk+uvOJSJm+ixmoJoXbeDFNBCgYqUXo= -github.com/go-git/go-git/v5 v5.10.1-0.20231107163107-e54a6ae399e9/go.mod h1:vQMZfh4ro7khF6K55zuBL6sU45QYjjppZ6Cot47a9t4= +github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= +github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -42,8 +40,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= @@ -69,8 +65,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= @@ -82,8 +78,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= 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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -109,7 +105,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From de1d5a5978b9599ca3dacd58bbf699e4bb4cf6bd Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 28 Nov 2023 14:31:04 -0500 Subject: [PATCH 202/205] git: validate reference names Check reference names format before creating branches/tags/remotes. This should probably be in a lower level somewhere in `plumbing`. Validating the names under `plumbing.NewReference*` is not possible since these functions don't return errors. Fixes: https://github.com/go-git/go-git/issues/929 --- config/branch.go | 2 +- config/config.go | 3 +- plumbing/reference.go | 89 ++++++++++++++++++++++++++++++++++++++ plumbing/reference_test.go | 59 +++++++++++++++++++++++++ repository.go | 9 +++- repository_test.go | 37 ++++++++++++++++ worktree.go | 4 ++ worktree_test.go | 24 ++++++++++ 8 files changed, 224 insertions(+), 3 deletions(-) diff --git a/config/branch.go b/config/branch.go index 652270a28..db2cb499a 100644 --- a/config/branch.go +++ b/config/branch.go @@ -54,7 +54,7 @@ func (b *Branch) Validate() error { return errBranchInvalidRebase } - return nil + return plumbing.NewBranchReferenceName(b.Name).Validate() } func (b *Branch) marshal() *format.Subsection { diff --git a/config/config.go b/config/config.go index da425a784..6d41c15dc 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ import ( "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/internal/url" + "github.com/go-git/go-git/v5/plumbing" format "github.com/go-git/go-git/v5/plumbing/format/config" ) @@ -614,7 +615,7 @@ func (c *RemoteConfig) Validate() error { c.Fetch = []RefSpec{RefSpec(fmt.Sprintf(DefaultFetchRefSpec, c.Name))} } - return nil + return plumbing.NewRemoteHEADReferenceName(c.Name).Validate() } func (c *RemoteConfig) unmarshal(s *format.Subsection) error { diff --git a/plumbing/reference.go b/plumbing/reference.go index 5a67f69e7..ddba93029 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -3,6 +3,7 @@ package plumbing import ( "errors" "fmt" + "regexp" "strings" ) @@ -29,6 +30,9 @@ var RefRevParseRules = []string{ var ( ErrReferenceNotFound = errors.New("reference not found") + + // ErrInvalidReferenceName is returned when a reference name is invalid. + ErrInvalidReferenceName = errors.New("invalid reference name") ) // ReferenceType reference type's @@ -124,6 +128,91 @@ func (r ReferenceName) Short() string { return res } +var ( + ctrlSeqs = regexp.MustCompile(`[\000-\037\177]`) +) + +// Validate validates a reference name. +// This follows the git-check-ref-format rules. +// See https://git-scm.com/docs/git-check-ref-format +// +// It is important to note that this function does not check if the reference +// exists in the repository. +// It only checks if the reference name is valid. +// This functions does not support the --refspec-pattern, --normalize, and +// --allow-onelevel options. +// +// Git imposes the following rules on how references are named: +// +// 1. They can include slash / for hierarchical (directory) grouping, but no +// slash-separated component can begin with a dot . or end with the +// sequence .lock. +// 2. They must contain at least one /. This enforces the presence of a +// category like heads/, tags/ etc. but the actual names are not +// restricted. If the --allow-onelevel option is used, this rule is +// waived. +// 3. They cannot have two consecutive dots .. anywhere. +// 4. They cannot have ASCII control characters (i.e. bytes whose values are +// lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : +// anywhere. +// 5. They cannot have question-mark ?, asterisk *, or open bracket [ +// anywhere. See the --refspec-pattern option below for an exception to this +// rule. +// 6. They cannot begin or end with a slash / or contain multiple consecutive +// slashes (see the --normalize option below for an exception to this rule). +// 7. They cannot end with a dot .. +// 8. They cannot contain a sequence @{. +// 9. They cannot be the single character @. +// 10. They cannot contain a \. +func (r ReferenceName) Validate() error { + s := string(r) + if len(s) == 0 { + return ErrInvalidReferenceName + } + + // HEAD is a special case + if r == HEAD { + return nil + } + + // rule 7 + if strings.HasSuffix(s, ".") { + return ErrInvalidReferenceName + } + + // rule 2 + parts := strings.Split(s, "/") + if len(parts) < 2 { + return ErrInvalidReferenceName + } + + isBranch := r.IsBranch() + isTag := r.IsTag() + for _, part := range parts { + // rule 6 + if len(part) == 0 { + return ErrInvalidReferenceName + } + + if strings.HasPrefix(part, ".") || // rule 1 + strings.Contains(part, "..") || // rule 3 + ctrlSeqs.MatchString(part) || // rule 4 + strings.ContainsAny(part, "~^:?*[ \t\n") || // rule 4 & 5 + strings.Contains(part, "@{") || // rule 8 + part == "@" || // rule 9 + strings.Contains(part, "\\") || // rule 10 + strings.HasSuffix(part, ".lock") { // rule 1 + return ErrInvalidReferenceName + } + + if (isBranch || isTag) && strings.HasPrefix(part, "-") { // branches & tags can't start with - + return ErrInvalidReferenceName + } + } + + return nil +} + const ( HEAD ReferenceName = "HEAD" Master ReferenceName = "refs/heads/master" diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go index 04dfef93d..ce570752f 100644 --- a/plumbing/reference_test.go +++ b/plumbing/reference_test.go @@ -103,6 +103,65 @@ func (s *ReferenceSuite) TestIsTag(c *C) { c.Assert(r.IsTag(), Equals, true) } +func (s *ReferenceSuite) TestValidReferenceNames(c *C) { + valid := []ReferenceName{ + "refs/heads/master", + "refs/notes/commits", + "refs/remotes/origin/master", + "HEAD", + "refs/tags/v3.1.1", + "refs/pulls/1/head", + "refs/pulls/1/merge", + "refs/pulls/1/abc.123", + "refs/pulls", + "refs/-", // should this be allowed? + } + for _, v := range valid { + c.Assert(v.Validate(), IsNil) + } + + invalid := []ReferenceName{ + "refs", + "refs/", + "refs//", + "refs/heads/\\", + "refs/heads/\\foo", + "refs/heads/\\foo/bar", + "abc", + "", + "refs/heads/ ", + "refs/heads/ /", + "refs/heads/ /foo", + "refs/heads/.", + "refs/heads/..", + "refs/heads/foo..", + "refs/heads/foo.lock", + "refs/heads/foo@{bar}", + "refs/heads/foo[", + "refs/heads/foo~", + "refs/heads/foo^", + "refs/heads/foo:", + "refs/heads/foo?", + "refs/heads/foo*", + "refs/heads/foo[bar", + "refs/heads/foo\t", + "refs/heads/@", + "refs/heads/@{bar}", + "refs/heads/\n", + "refs/heads/-foo", + "refs/heads/foo..bar", + "refs/heads/-", + "refs/tags/-", + "refs/tags/-foo", + } + + for i, v := range invalid { + comment := Commentf("invalid reference name case %d: %s", i, v) + c.Assert(v.Validate(), NotNil, comment) + c.Assert(v.Validate(), ErrorMatches, "invalid reference name", comment) + } +} + func benchMarkReferenceString(r *Reference, b *testing.B) { for n := 0; n < b.N; n++ { _ = r.String() diff --git a/repository.go b/repository.go index 48988383d..1524a6913 100644 --- a/repository.go +++ b/repository.go @@ -98,6 +98,10 @@ func InitWithOptions(s storage.Storer, worktree billy.Filesystem, options InitOp options.DefaultBranch = plumbing.Master } + if err := options.DefaultBranch.Validate(); err != nil { + return nil, err + } + r := newRepository(s, worktree) _, err := r.Reference(plumbing.HEAD, false) switch err { @@ -724,7 +728,10 @@ func (r *Repository) DeleteBranch(name string) error { // CreateTag creates a tag. If opts is included, the tag is an annotated tag, // otherwise a lightweight tag is created. func (r *Repository) CreateTag(name string, hash plumbing.Hash, opts *CreateTagOptions) (*plumbing.Reference, error) { - rname := plumbing.ReferenceName(path.Join("refs", "tags", name)) + rname := plumbing.NewTagReferenceName(name) + if err := rname.Validate(); err != nil { + return nil, err + } _, err := r.Storer.Reference(rname) switch err { diff --git a/repository_test.go b/repository_test.go index 35a62f131..51df84512 100644 --- a/repository_test.go +++ b/repository_test.go @@ -75,6 +75,13 @@ func (s *RepositorySuite) TestInitWithOptions(c *C) { } +func (s *RepositorySuite) TestInitWithInvalidDefaultBranch(c *C) { + _, err := InitWithOptions(memory.NewStorage(), memfs.New(), InitOptions{ + DefaultBranch: "foo", + }) + c.Assert(err, NotNil) +} + func createCommit(c *C, r *Repository) { // Create a commit so there is a HEAD to check wt, err := r.Worktree() @@ -391,6 +398,22 @@ func (s *RepositorySuite) TestDeleteRemote(c *C) { c.Assert(alt, IsNil) } +func (s *RepositorySuite) TestEmptyCreateBranch(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.CreateBranch(&config.Branch{}) + + c.Assert(err, NotNil) +} + +func (s *RepositorySuite) TestInvalidCreateBranch(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.CreateBranch(&config.Branch{ + Name: "-foo", + }) + + c.Assert(err, NotNil) +} + func (s *RepositorySuite) TestCreateBranchAndBranch(c *C) { r, _ := Init(memory.NewStorage(), nil) testBranch := &config.Branch{ @@ -2797,6 +2820,20 @@ func (s *RepositorySuite) TestDeleteTagAnnotatedUnpacked(c *C) { c.Assert(err, Equals, plumbing.ErrObjectNotFound) } +func (s *RepositorySuite) TestInvalidTagName(c *C) { + r, err := Init(memory.NewStorage(), nil) + c.Assert(err, IsNil) + for i, name := range []string{ + "", + "foo bar", + "foo\tbar", + "foo\nbar", + } { + _, err = r.CreateTag(name, plumbing.ZeroHash, nil) + c.Assert(err, NotNil, Commentf("case %d %q", i, name)) + } +} + func (s *RepositorySuite) TestBranches(c *C) { f := fixtures.ByURL("https://github.com/git-fixtures/root-references.git").One() sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) diff --git a/worktree.go b/worktree.go index f8b854dda..51795e635 100644 --- a/worktree.go +++ b/worktree.go @@ -189,6 +189,10 @@ func (w *Worktree) Checkout(opts *CheckoutOptions) error { return w.Reset(ro) } func (w *Worktree) createBranch(opts *CheckoutOptions) error { + if err := opts.Branch.Validate(); err != nil { + return err + } + _, err := w.r.Storer.Reference(opts.Branch) if err == nil { return fmt.Errorf("a branch named %q already exists", opts.Branch) diff --git a/worktree_test.go b/worktree_test.go index 180bfb0b4..5c9a4eb3a 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -785,6 +785,30 @@ func (s *WorktreeSuite) TestCheckoutCreateMissingBranch(c *C) { c.Assert(err, Equals, ErrCreateRequiresBranch) } +func (s *WorktreeSuite) TestCheckoutCreateInvalidBranch(c *C) { + w := &Worktree{ + r: s.Repository, + Filesystem: memfs.New(), + } + + for _, name := range []plumbing.ReferenceName{ + "foo", + "-", + "-foo", + "refs/heads//", + "refs/heads/..", + "refs/heads/a..b", + "refs/heads/.", + } { + err := w.Checkout(&CheckoutOptions{ + Create: true, + Branch: name, + }) + + c.Assert(err, Equals, plumbing.ErrInvalidReferenceName) + } +} + func (s *WorktreeSuite) TestCheckoutTag(c *C) { f := fixtures.ByTag("tags").One() r := s.NewRepositoryWithEmptyWorktree(f) From 8b47ceb1aa854f3c3bfa1c347157a04324fcd51e Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sat, 2 Dec 2023 10:30:30 +0000 Subject: [PATCH 203/205] storage: filesystem, Add option to set a specific FS for alternates Introduces the option to set a FS for alternates, enabling more flexible cross FS sharing of alternates. If none is set, falls back to the current FS used for the object storage. The changes only process a given path once, and if an alternates dir is not valid, exits with error - aligning behaviour with upstream. Signed-off-by: Paulo Gomes --- go.mod | 4 + go.sum | 1 + storage/filesystem/dotgit/dotgit.go | 64 ++++++--- storage/filesystem/dotgit/dotgit_test.go | 162 +++++++++++++++++------ storage/filesystem/storage.go | 5 + worktree_test.go | 31 +++-- 6 files changed, 203 insertions(+), 64 deletions(-) diff --git a/go.mod b/go.mod index 9a18c7a49..5247d063c 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/pjbgf/sha1cd v0.3.0 github.com/sergi/go-diff v1.1.0 github.com/skeema/knownhosts v1.2.1 + github.com/stretchr/testify v1.8.4 github.com/xanzy/ssh-agent v0.3.3 golang.org/x/crypto v0.16.0 golang.org/x/net v0.19.0 @@ -33,10 +34,13 @@ require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/tools v0.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4be7ec4eb..c0593765b 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go index 3080e4acc..31c469481 100644 --- a/storage/filesystem/dotgit/dotgit.go +++ b/storage/filesystem/dotgit/dotgit.go @@ -10,18 +10,19 @@ import ( "os" "path" "path/filepath" + "reflect" "runtime" "sort" "strings" "time" - "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/storage" "github.com/go-git/go-git/v5/utils/ioutil" "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/helper/chroot" ) const ( @@ -81,6 +82,10 @@ type Options struct { // KeepDescriptors makes the file descriptors to be reused but they will // need to be manually closed calling Close(). KeepDescriptors bool + // AlternatesFS provides the billy filesystem to be used for Git Alternates. + // If none is provided, it falls back to using the underlying instance used for + // DotGit. + AlternatesFS billy.Filesystem } // The DotGit type represents a local git repository on disk. This @@ -1146,28 +1151,55 @@ func (d *DotGit) Alternates() ([]*DotGit, error) { } defer f.Close() + fs := d.options.AlternatesFS + if fs == nil { + fs = d.fs + } + var alternates []*DotGit + seen := make(map[string]struct{}) // Read alternate paths line-by-line and create DotGit objects. scanner := bufio.NewScanner(f) for scanner.Scan() { path := scanner.Text() - if !filepath.IsAbs(path) { - // For relative paths, we can perform an internal conversion to - // slash so that they work cross-platform. - slashPath := filepath.ToSlash(path) - // If the path is not absolute, it must be relative to object - // database (.git/objects/info). - // https://www.kernel.org/pub/software/scm/git/docs/gitrepository-layout.html - // Hence, derive a path relative to DotGit's root. - // "../../../reponame/.git/" -> "../../reponame/.git" - // Remove the first ../ - relpath := filepath.Join(strings.Split(slashPath, "/")[1:]...) - normalPath := filepath.FromSlash(relpath) - path = filepath.Join(d.fs.Root(), normalPath) + + // Avoid creating multiple dotgits for the same alternative path. + if _, ok := seen[path]; ok { + continue + } + + seen[path] = struct{}{} + + if filepath.IsAbs(path) { + // Handling absolute paths should be straight-forward. However, the default osfs (Chroot) + // tries to concatenate an abs path with the root path in some operations (e.g. Stat), + // which leads to unexpected errors. Therefore, make the path relative to the current FS instead. + if reflect.TypeOf(fs) == reflect.TypeOf(&chroot.ChrootHelper{}) { + path, err = filepath.Rel(fs.Root(), path) + if err != nil { + return nil, fmt.Errorf("cannot make path %q relative: %w", path, err) + } + } + } else { + // By Git conventions, relative paths should be based on the object database (.git/objects/info) + // location as per: https://www.kernel.org/pub/software/scm/git/docs/gitrepository-layout.html + // However, due to the nature of go-git and its filesystem handling via Billy, paths cannot + // cross its "chroot boundaries". Therefore, ignore any "../" and treat the path from the + // fs root. If this is not correct based on the dotgit fs, set a different one via AlternatesFS. + abs := filepath.Join(string(filepath.Separator), filepath.ToSlash(path)) + path = filepath.FromSlash(abs) + } + + // Aligns with upstream behavior: exit if target path is not a valid directory. + if fi, err := fs.Stat(path); err != nil || !fi.IsDir() { + return nil, fmt.Errorf("invalid object directory %q: %w", path, err) + } + afs, err := fs.Chroot(filepath.Dir(path)) + if err != nil { + return nil, fmt.Errorf("cannot chroot %q: %w", path, err) } - fs := osfs.New(filepath.Dir(path)) - alternates = append(alternates, New(fs)) + alternates = append(alternates, New(afs)) } if err = scanner.Err(); err != nil { diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go index 1b6c11351..2cbdb0c9f 100644 --- a/storage/filesystem/dotgit/dotgit_test.go +++ b/storage/filesystem/dotgit/dotgit_test.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "regexp" "runtime" "strings" "testing" @@ -15,6 +16,7 @@ import ( "github.com/go-git/go-billy/v5/util" fixtures "github.com/go-git/go-git-fixtures/v4" "github.com/go-git/go-git/v5/plumbing" + "github.com/stretchr/testify/assert" . "gopkg.in/check.v1" ) @@ -810,53 +812,139 @@ func (s *SuiteDotGit) TestPackRefs(c *C) { c.Assert(ref.Hash().String(), Equals, "b8d3ffab552895c19b9fcf7aa264d277cde33881") } -func (s *SuiteDotGit) TestAlternates(c *C) { - fs, clean := s.TemporalFilesystem() - defer clean() +func TestAlternatesDefault(t *testing.T) { + // Create a new dotgit object. + dotFS := osfs.New(t.TempDir()) - // Create a new dotgit object and initialize. - dir := New(fs) - err := dir.Initialize() - c.Assert(err, IsNil) + testAlternates(t, dotFS, dotFS) +} - // Create alternates file. - altpath := fs.Join("objects", "info", "alternates") - f, err := fs.Create(altpath) - c.Assert(err, IsNil) +func TestAlternatesWithFS(t *testing.T) { + // Create a new dotgit object with a specific FS for alternates. + altFS := osfs.New(t.TempDir()) + dotFS, _ := altFS.Chroot("repo2") - // Multiple alternates. - var strContent string - if runtime.GOOS == "windows" { - strContent = "C:\\Users\\username\\repo1\\.git\\objects\r\n..\\..\\..\\rep2\\.git\\objects" - } else { - strContent = "/Users/username/rep1//.git/objects\n../../../rep2//.git/objects" + testAlternates(t, dotFS, altFS) +} + +func TestAlternatesWithBoundOS(t *testing.T) { + // Create a new dotgit object with a specific FS for alternates. + altFS := osfs.New(t.TempDir(), osfs.WithBoundOS()) + dotFS, _ := altFS.Chroot("repo2") + + testAlternates(t, dotFS, altFS) +} + +func testAlternates(t *testing.T, dotFS, altFS billy.Filesystem) { + tests := []struct { + name string + in []string + inWindows []string + setup func() + wantErr bool + wantRoots []string + }{ + { + name: "no alternates", + }, + { + name: "abs path", + in: []string{filepath.Join(altFS.Root(), "./repo1/.git/objects")}, + inWindows: []string{filepath.Join(altFS.Root(), ".\\repo1\\.git\\objects")}, + setup: func() { + err := altFS.MkdirAll(filepath.Join("repo1", ".git", "objects"), 0o700) + assert.NoError(t, err) + }, + wantRoots: []string{filepath.Join("repo1", ".git")}, + }, + { + name: "rel path", + in: []string{"../../../repo3//.git/objects"}, + inWindows: []string{"..\\..\\..\\repo3\\.git\\objects"}, + setup: func() { + err := altFS.MkdirAll(filepath.Join("repo3", ".git", "objects"), 0o700) + assert.NoError(t, err) + }, + wantRoots: []string{filepath.Join("repo3", ".git")}, + }, + { + name: "invalid abs path", + in: []string{"/alt/target2"}, + inWindows: []string{"\\alt\\target2"}, + wantErr: true, + }, + { + name: "invalid rel path", + in: []string{"../../../alt/target3"}, + inWindows: []string{"..\\..\\..\\alt\\target3"}, + wantErr: true, + }, } - content := []byte(strContent) - f.Write(content) - f.Close() - dotgits, err := dir.Alternates() - c.Assert(err, IsNil) - if runtime.GOOS == "windows" { - c.Assert(dotgits[0].fs.Root(), Equals, "C:\\Users\\username\\repo1\\.git") - } else { - c.Assert(dotgits[0].fs.Root(), Equals, "/Users/username/rep1/.git") + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + dir := NewWithOptions(dotFS, Options{AlternatesFS: altFS}) + err := dir.Initialize() + assert.NoError(t, err) + + content := strings.Join(tc.in, "\n") + if runtime.GOOS == "windows" { + content = strings.Join(tc.inWindows, "\r\n") + } + + // Create alternates file. + altpath := dotFS.Join("objects", "info", "alternates") + f, err := dotFS.Create(altpath) + assert.NoError(t, err) + f.Write([]byte(content)) + f.Close() + + if tc.setup != nil { + tc.setup() + } + + dotgits, err := dir.Alternates() + if tc.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + for i, d := range dotgits { + assert.Regexp(t, "^"+regexp.QuoteMeta(altFS.Root()), d.fs.Root()) + assert.Regexp(t, regexp.QuoteMeta(tc.wantRoots[i])+"$", d.fs.Root()) + } + }) } +} - // For relative path: - // /some/absolute/path/to/dot-git -> /some/absolute/path - pathx := strings.Split(fs.Root(), string(filepath.Separator)) - pathx = pathx[:len(pathx)-2] - // Use string.Join() to avoid malformed absolutepath on windows - // C:Users\\User\\... instead of C:\\Users\\appveyor\\... . - resolvedPath := strings.Join(pathx, string(filepath.Separator)) - // Append the alternate path to the resolvedPath - expectedPath := fs.Join(string(filepath.Separator), resolvedPath, "rep2", ".git") +func TestAlternatesDupes(t *testing.T) { + dotFS := osfs.New(t.TempDir()) + dir := New(dotFS) + err := dir.Initialize() + assert.NoError(t, err) + + path := filepath.Join(dotFS.Root(), "target3") + dupes := []string{path, path, path, path, path} + + content := strings.Join(dupes, "\n") if runtime.GOOS == "windows" { - expectedPath = fs.Join(resolvedPath, "rep2", ".git") + content = strings.Join(dupes, "\r\n") } - c.Assert(dotgits[1].fs.Root(), Equals, expectedPath) + err = dotFS.MkdirAll("target3", 0o700) + assert.NoError(t, err) + + // Create alternates file. + altpath := dotFS.Join("objects", "info", "alternates") + f, err := dotFS.Create(altpath) + assert.NoError(t, err) + f.Write([]byte(content)) + f.Close() + + dotgits, err := dir.Alternates() + assert.NoError(t, err) + assert.Len(t, dotgits, 1) } type norwfs struct { diff --git a/storage/filesystem/storage.go b/storage/filesystem/storage.go index 2069d3a6f..951ea00c8 100644 --- a/storage/filesystem/storage.go +++ b/storage/filesystem/storage.go @@ -37,6 +37,10 @@ type Options struct { // 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 + // AlternatesFS provides the billy filesystem to be used for Git Alternates. + // If none is provided, it falls back to using the underlying instance used for + // DotGit. + AlternatesFS billy.Filesystem } // NewStorage returns a new Storage backed by a given `fs.Filesystem` and cache. @@ -49,6 +53,7 @@ func NewStorage(fs billy.Filesystem, cache cache.Object) *Storage { func NewStorageWithOptions(fs billy.Filesystem, cache cache.Object, ops Options) *Storage { dirOps := dotgit.Options{ ExclusiveAccess: ops.ExclusiveAccess, + AlternatesFS: ops.AlternatesFS, } dir := dotgit.NewWithOptions(fs, dirOps) diff --git a/worktree_test.go b/worktree_test.go index 3136e5988..3e057a79d 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -16,11 +16,14 @@ import ( fixtures "github.com/go-git/go-git-fixtures/v4" "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/filemode" "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/go-git/go-git/v5/plumbing/format/index" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/memory" + "github.com/stretchr/testify/assert" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/osfs" @@ -2198,34 +2201,40 @@ func (s *WorktreeSuite) TestCleanBare(c *C) { c.Assert(err, IsNil) } -func (s *WorktreeSuite) TestAlternatesRepo(c *C) { +func TestAlternatesRepo(t *testing.T) { fs := fixtures.ByTag("alternates").One().Worktree() // Open 1st repo. rep1fs, err := fs.Chroot("rep1") - c.Assert(err, IsNil) + assert.NoError(t, err) rep1, err := PlainOpen(rep1fs.Root()) - c.Assert(err, IsNil) + assert.NoError(t, err) // Open 2nd repo. rep2fs, err := fs.Chroot("rep2") - c.Assert(err, IsNil) - rep2, err := PlainOpen(rep2fs.Root()) - c.Assert(err, IsNil) + assert.NoError(t, err) + d, _ := rep2fs.Chroot(GitDirName) + storer := filesystem.NewStorageWithOptions(d, + cache.NewObjectLRUDefault(), filesystem.Options{ + AlternatesFS: fs, + }) + rep2, err := Open(storer, rep2fs) + + assert.NoError(t, err) // Get the HEAD commit from the main repo. h, err := rep1.Head() - c.Assert(err, IsNil) + assert.NoError(t, err) commit1, err := rep1.CommitObject(h.Hash()) - c.Assert(err, IsNil) + assert.NoError(t, err) // Get the HEAD commit from the shared repo. h, err = rep2.Head() - c.Assert(err, IsNil) + assert.NoError(t, err) commit2, err := rep2.CommitObject(h.Hash()) - c.Assert(err, IsNil) + assert.NoError(t, err) - c.Assert(commit1.String(), Equals, commit2.String()) + assert.Equal(t, commit1.String(), commit2.String()) } func (s *WorktreeSuite) TestGrep(c *C) { From b2c19824771bbcbb21abb51abb319c1a610aa6b3 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 8 Dec 2023 08:55:30 +0000 Subject: [PATCH 204/205] git: worktree, Align validation with upstream rules Some worktree validation rules observed upstream are not checked by go-git, leading to scenarios in which what seems to be a valid repository for go-git is not necessarily the case for the git cli. Signed-off-by: Paulo Gomes --- worktree.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++ worktree_test.go | 75 +++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/worktree.go b/worktree.go index 66cb3d29c..ad525c1a4 100644 --- a/worktree.go +++ b/worktree.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" "github.com/go-git/go-billy/v5" @@ -394,6 +395,9 @@ func (w *Worktree) resetWorktree(t *object.Tree) error { b := newIndexBuilder(idx) for _, ch := range changes { + if err := w.validChange(ch); err != nil { + return err + } if err := w.checkoutChange(ch, t, b); err != nil { return err } @@ -403,6 +407,104 @@ func (w *Worktree) resetWorktree(t *object.Tree) error { return w.r.Storer.SetIndex(idx) } +// worktreeDeny is a list of paths that are not allowed +// to be used when resetting the worktree. +var worktreeDeny = map[string]struct{}{ + // .git + GitDirName: {}, + + // For other historical reasons, file names that do not conform to the 8.3 + // format (up to eight characters for the basename, three for the file + // extension, certain characters not allowed such as `+`, etc) are associated + // with a so-called "short name", at least on the `C:` drive by default. + // Which means that `git~1/` is a valid way to refer to `.git/`. + "git~1": {}, +} + +// validPath checks whether paths are valid. +// The rules around invalid paths could differ from upstream based on how +// filesystems are managed within go-git, but they are largely the same. +// +// For upstream rules: +// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/read-cache.c#L946 +// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/path.c#L1383 +func validPath(paths ...string) error { + for _, p := range paths { + parts := strings.FieldsFunc(p, func(r rune) bool { return (r == '\\' || r == '/') }) + if _, denied := worktreeDeny[strings.ToLower(parts[0])]; denied { + return fmt.Errorf("invalid path prefix: %q", p) + } + + if runtime.GOOS == "windows" { + // Volume names are not supported, in both formats: \\ and :. + if vol := filepath.VolumeName(p); vol != "" { + return fmt.Errorf("invalid path: %q", p) + } + + if !windowsValidPath(parts[0]) { + return fmt.Errorf("invalid path: %q", p) + } + } + + for _, part := range parts { + if part == ".." { + return fmt.Errorf("invalid path %q: cannot use '..'", p) + } + } + } + return nil +} + +// windowsPathReplacer defines the chars that need to be replaced +// as part of windowsValidPath. +var windowsPathReplacer *strings.Replacer + +func init() { + windowsPathReplacer = strings.NewReplacer(" ", "", ".", "") +} + +func windowsValidPath(part string) bool { + if len(part) > 3 && strings.EqualFold(part[:4], GitDirName) { + // For historical reasons, file names that end in spaces or periods are + // automatically trimmed. Therefore, `.git . . ./` is a valid way to refer + // to `.git/`. + if windowsPathReplacer.Replace(part[4:]) == "" { + return false + } + + // For yet other historical reasons, NTFS supports so-called "Alternate Data + // Streams", i.e. metadata associated with a given file, referred to via + // `::`. There exists a default stream + // type for directories, allowing `.git/` to be accessed via + // `.git::$INDEX_ALLOCATION/`. + // + // For performance reasons, _all_ Alternate Data Streams of `.git/` are + // forbidden, not just `::$INDEX_ALLOCATION`. + if len(part) > 4 && part[4:5] == ":" { + return false + } + } + return true +} + +func (w *Worktree) validChange(ch merkletrie.Change) error { + action, err := ch.Action() + if err != nil { + return nil + } + + switch action { + case merkletrie.Delete: + return validPath(ch.From.String()) + case merkletrie.Insert: + return validPath(ch.To.String()) + case merkletrie.Modify: + return validPath(ch.From.String(), ch.To.String()) + } + + return nil +} + func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *indexBuilder) error { a, err := ch.Action() if err != nil { @@ -575,6 +677,11 @@ func (w *Worktree) checkoutFile(f *object.File) (err error) { } func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) { + // https://github.com/git/git/commit/10ecfa76491e4923988337b2e2243b05376b40de + if strings.EqualFold(f.Name, gitmodulesFile) { + return ErrGitModulesSymlink + } + from, err := f.Reader() if err != nil { return diff --git a/worktree_test.go b/worktree_test.go index 3e057a79d..50ff189fa 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "io" "os" "path/filepath" @@ -2747,3 +2748,77 @@ func (s *WorktreeSuite) TestLinkedWorktree(c *C) { c.Assert(err, Equals, ErrRepositoryIncomplete) } } + +func TestValidPath(t *testing.T) { + type testcase struct { + path string + wantErr bool + } + + tests := []testcase{ + {".git", true}, + {".git/b", true}, + {".git\\b", true}, + {"git~1", true}, + {"a/../b", true}, + {"a\\..\\b", true}, + {".gitmodules", false}, + {".gitignore", false}, + {"a..b", false}, + {".", false}, + {"a/.git", false}, + {"a\\.git", false}, + {"a/.git/b", false}, + {"a\\.git\\b", false}, + } + + if runtime.GOOS == "windows" { + tests = append(tests, []testcase{ + {"\\\\a\\b", true}, + {"C:\\a\\b", true}, + {".git . . .", true}, + {".git . . ", true}, + {".git ", true}, + {".git.", true}, + {".git::$INDEX_ALLOCATION", true}, + }...) + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("%s", tc.path), func(t *testing.T) { + err := validPath(tc.path) + if tc.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestWindowsValidPath(t *testing.T) { + tests := []struct { + path string + want bool + }{ + {".git", false}, + {".git . . .", false}, + {".git ", false}, + {".git ", false}, + {".git . .", false}, + {".git . .", false}, + {".git::$INDEX_ALLOCATION", false}, + {".git:", false}, + {"a", true}, + {"a\\b", true}, + {"a/b", true}, + {".gitm", true}, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("%s", tc.path), func(t *testing.T) { + got := windowsValidPath(tc.path) + assert.Equal(t, tc.want, got) + }) + } +} From 5bd1d8f4abcfbf1345a1e5a5ec9a96121f3746dc Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 8 Dec 2023 09:04:16 +0000 Subject: [PATCH 205/205] build: Ensure checkout is the first operation The setup-go step can be sped up by caching Go dependencies. The input for that operation is the go.sum file. Previously, the checkout operation was happening after the setup-go, which meant that go.sum was never available which effectively meant the cache was disabled. Signed-off-by: Paulo Gomes --- .github/workflows/git.yml | 6 +++--- .github/workflows/test.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index 95398087c..d46cd3b0e 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -16,14 +16,14 @@ jobs: GIT_DIST_PATH: .git-dist/${{ matrix.git[0] }} steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Go uses: actions/setup-go@v4 with: go-version: 1.21.x - - name: Checkout code - uses: actions/checkout@v4 - - name: Install build dependencies run: sudo apt-get update && sudo apt-get install gettext libcurl4-openssl-dev diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 875dda7be..378000871 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,13 +13,13 @@ jobs: runs-on: ${{ matrix.platform }} steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} - - - name: Checkout code - uses: actions/checkout@v4 - name: Configure known hosts if: matrix.platform != 'ubuntu-latest'