* Exclude vendor dirs from git CRLF normalization Should get rid of a few warnings like at the end of `lint-backend` like https://drone.gitea.io/go-gitea/gitea/23117/1/4 * make vendor Co-authored-by: John Olheiser <john.olheiser@gmail.com> Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>tags/v1.21.12.1
| @@ -1,4 +1,7 @@ | |||
| * text=auto eol=lf | |||
| /vendor/** -text -eol | |||
| /public/vendor/** -text -eol | |||
| conf/* linguist-vendored | |||
| docker/* linguist-vendored | |||
| options/* linguist-vendored | |||
| @@ -1,35 +1,35 @@ | |||
| package commitgraph | |||
| import ( | |||
| "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 []int | |||
| // 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 int | |||
| // 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) (int, error) | |||
| // GetNodeByIndex gets the commit node from the commit graph using index | |||
| // obtained from child node, if available | |||
| GetCommitDataByIndex(i int) (*CommitData, error) | |||
| // Hashes returns all the hashes that are available in the index | |||
| Hashes() []plumbing.Hash | |||
| } | |||
| package commitgraph | |||
| import ( | |||
| "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 []int | |||
| // 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 int | |||
| // 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) (int, error) | |||
| // GetNodeByIndex gets the commit node from the commit graph using index | |||
| // obtained from child node, if available | |||
| GetCommitDataByIndex(i int) (*CommitData, error) | |||
| // Hashes returns all the hashes that are available in the index | |||
| Hashes() []plumbing.Hash | |||
| } | |||
| @@ -1,188 +1,188 @@ | |||
| package commitgraph | |||
| import ( | |||
| "crypto/sha1" | |||
| "hash" | |||
| "io" | |||
| "github.com/go-git/go-git/v5/plumbing" | |||
| "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 := sha1.New() | |||
| 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{oidFanoutSignature, oidLookupSignature, commitDataSignature} | |||
| chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * 20, uint64(len(hashes)) * 36} | |||
| if extraEdgesCount > 0 { | |||
| chunkSignatures = append(chunkSignatures, extraEdgeListSignature) | |||
| 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(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 { | |||
| _, err = e.Write([]byte{1, 1, 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(lastSignature); 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)[:20]) | |||
| return err | |||
| } | |||
| package commitgraph | |||
| import ( | |||
| "crypto/sha1" | |||
| "hash" | |||
| "io" | |||
| "github.com/go-git/go-git/v5/plumbing" | |||
| "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 := sha1.New() | |||
| 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{oidFanoutSignature, oidLookupSignature, commitDataSignature} | |||
| chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * 20, uint64(len(hashes)) * 36} | |||
| if extraEdgesCount > 0 { | |||
| chunkSignatures = append(chunkSignatures, extraEdgeListSignature) | |||
| 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(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 { | |||
| _, err = e.Write([]byte{1, 1, 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(lastSignature); 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)[:20]) | |||
| return err | |||
| } | |||
| @@ -1,259 +1,259 @@ | |||
| package commitgraph | |||
| import ( | |||
| "bytes" | |||
| encbin "encoding/binary" | |||
| "errors" | |||
| "io" | |||
| "time" | |||
| "github.com/go-git/go-git/v5/plumbing" | |||
| "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'} | |||
| oidFanoutSignature = []byte{'O', 'I', 'D', 'F'} | |||
| oidLookupSignature = []byte{'O', 'I', 'D', 'L'} | |||
| commitDataSignature = []byte{'C', 'D', 'A', 'T'} | |||
| extraEdgeListSignature = []byte{'E', 'D', 'G', 'E'} | |||
| lastSignature = []byte{0, 0, 0, 0} | |||
| parentNone = uint32(0x70000000) | |||
| parentOctopusUsed = uint32(0x80000000) | |||
| parentOctopusMask = uint32(0x7fffffff) | |||
| parentLast = uint32(0x80000000) | |||
| ) | |||
| type fileIndex struct { | |||
| reader io.ReaderAt | |||
| fanout [256]int | |||
| oidFanoutOffset int64 | |||
| oidLookupOffset int64 | |||
| commitDataOffset int64 | |||
| extraEdgeListOffset int64 | |||
| } | |||
| // 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 io.ReaderAt) (Index, error) { | |||
| fi := &fileIndex{reader: reader} | |||
| 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 | |||
| } | |||
| func (fi *fileIndex) verifyFileHeader() error { | |||
| // Verify file signature | |||
| var 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 | |||
| var 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 | |||
| } | |||
| return nil | |||
| } | |||
| func (fi *fileIndex) readChunkHeaders() error { | |||
| var 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 | |||
| } | |||
| if bytes.Equal(chunkID, oidFanoutSignature) { | |||
| fi.oidFanoutOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, oidLookupSignature) { | |||
| fi.oidLookupOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, commitDataSignature) { | |||
| fi.commitDataOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, extraEdgeListSignature) { | |||
| fi.extraEdgeListOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, lastSignature) { | |||
| break | |||
| } | |||
| } | |||
| if fi.oidFanoutOffset <= 0 || fi.oidLookupOffset <= 0 || fi.commitDataOffset <= 0 { | |||
| return ErrMalformedCommitGraphFile | |||
| } | |||
| return nil | |||
| } | |||
| func (fi *fileIndex) readFanout() error { | |||
| fanoutReader := io.NewSectionReader(fi.reader, fi.oidFanoutOffset, 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] = int(fanoutValue) | |||
| } | |||
| return nil | |||
| } | |||
| func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (int, error) { | |||
| var oid plumbing.Hash | |||
| // Find the hash in the oid lookup table | |||
| var low int | |||
| 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.oidLookupOffset + int64(mid)*20 | |||
| 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 | |||
| } | |||
| } | |||
| return 0, plumbing.ErrObjectNotFound | |||
| } | |||
| func (fi *fileIndex) GetCommitDataByIndex(idx int) (*CommitData, error) { | |||
| if idx >= fi.fanout[0xff] { | |||
| return nil, plumbing.ErrObjectNotFound | |||
| } | |||
| offset := fi.commitDataOffset + int64(idx)*36 | |||
| commitDataReader := io.NewSectionReader(fi.reader, offset, 36) | |||
| 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 []int | |||
| if parent2&parentOctopusUsed == parentOctopusUsed { | |||
| // Octopus merge | |||
| parentIndexes = []int{int(parent1 & parentOctopusMask)} | |||
| offset := fi.extraEdgeListOffset + 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, int(parent&parentOctopusMask)) | |||
| if parent&parentLast == parentLast { | |||
| break | |||
| } | |||
| } | |||
| } else if parent2 != parentNone { | |||
| parentIndexes = []int{int(parent1 & parentOctopusMask), int(parent2 & parentOctopusMask)} | |||
| } else if parent1 != parentNone { | |||
| parentIndexes = []int{int(parent1 & parentOctopusMask)} | |||
| } | |||
| parentHashes, err := fi.getHashesFromIndexes(parentIndexes) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &CommitData{ | |||
| TreeHash: treeHash, | |||
| ParentIndexes: parentIndexes, | |||
| ParentHashes: parentHashes, | |||
| Generation: int(genAndTime >> 34), | |||
| When: time.Unix(int64(genAndTime&0x3FFFFFFFF), 0), | |||
| }, nil | |||
| } | |||
| func (fi *fileIndex) getHashesFromIndexes(indexes []int) ([]plumbing.Hash, error) { | |||
| hashes := make([]plumbing.Hash, len(indexes)) | |||
| for i, idx := range indexes { | |||
| if idx >= fi.fanout[0xff] { | |||
| return nil, ErrMalformedCommitGraphFile | |||
| } | |||
| offset := fi.oidLookupOffset + int64(idx)*20 | |||
| 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 := 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 { | |||
| return nil | |||
| } | |||
| } | |||
| return hashes | |||
| } | |||
| package commitgraph | |||
| import ( | |||
| "bytes" | |||
| encbin "encoding/binary" | |||
| "errors" | |||
| "io" | |||
| "time" | |||
| "github.com/go-git/go-git/v5/plumbing" | |||
| "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'} | |||
| oidFanoutSignature = []byte{'O', 'I', 'D', 'F'} | |||
| oidLookupSignature = []byte{'O', 'I', 'D', 'L'} | |||
| commitDataSignature = []byte{'C', 'D', 'A', 'T'} | |||
| extraEdgeListSignature = []byte{'E', 'D', 'G', 'E'} | |||
| lastSignature = []byte{0, 0, 0, 0} | |||
| parentNone = uint32(0x70000000) | |||
| parentOctopusUsed = uint32(0x80000000) | |||
| parentOctopusMask = uint32(0x7fffffff) | |||
| parentLast = uint32(0x80000000) | |||
| ) | |||
| type fileIndex struct { | |||
| reader io.ReaderAt | |||
| fanout [256]int | |||
| oidFanoutOffset int64 | |||
| oidLookupOffset int64 | |||
| commitDataOffset int64 | |||
| extraEdgeListOffset int64 | |||
| } | |||
| // 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 io.ReaderAt) (Index, error) { | |||
| fi := &fileIndex{reader: reader} | |||
| 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 | |||
| } | |||
| func (fi *fileIndex) verifyFileHeader() error { | |||
| // Verify file signature | |||
| var 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 | |||
| var 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 | |||
| } | |||
| return nil | |||
| } | |||
| func (fi *fileIndex) readChunkHeaders() error { | |||
| var 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 | |||
| } | |||
| if bytes.Equal(chunkID, oidFanoutSignature) { | |||
| fi.oidFanoutOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, oidLookupSignature) { | |||
| fi.oidLookupOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, commitDataSignature) { | |||
| fi.commitDataOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, extraEdgeListSignature) { | |||
| fi.extraEdgeListOffset = int64(chunkOffset) | |||
| } else if bytes.Equal(chunkID, lastSignature) { | |||
| break | |||
| } | |||
| } | |||
| if fi.oidFanoutOffset <= 0 || fi.oidLookupOffset <= 0 || fi.commitDataOffset <= 0 { | |||
| return ErrMalformedCommitGraphFile | |||
| } | |||
| return nil | |||
| } | |||
| func (fi *fileIndex) readFanout() error { | |||
| fanoutReader := io.NewSectionReader(fi.reader, fi.oidFanoutOffset, 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] = int(fanoutValue) | |||
| } | |||
| return nil | |||
| } | |||
| func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (int, error) { | |||
| var oid plumbing.Hash | |||
| // Find the hash in the oid lookup table | |||
| var low int | |||
| 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.oidLookupOffset + int64(mid)*20 | |||
| 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 | |||
| } | |||
| } | |||
| return 0, plumbing.ErrObjectNotFound | |||
| } | |||
| func (fi *fileIndex) GetCommitDataByIndex(idx int) (*CommitData, error) { | |||
| if idx >= fi.fanout[0xff] { | |||
| return nil, plumbing.ErrObjectNotFound | |||
| } | |||
| offset := fi.commitDataOffset + int64(idx)*36 | |||
| commitDataReader := io.NewSectionReader(fi.reader, offset, 36) | |||
| 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 []int | |||
| if parent2&parentOctopusUsed == parentOctopusUsed { | |||
| // Octopus merge | |||
| parentIndexes = []int{int(parent1 & parentOctopusMask)} | |||
| offset := fi.extraEdgeListOffset + 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, int(parent&parentOctopusMask)) | |||
| if parent&parentLast == parentLast { | |||
| break | |||
| } | |||
| } | |||
| } else if parent2 != parentNone { | |||
| parentIndexes = []int{int(parent1 & parentOctopusMask), int(parent2 & parentOctopusMask)} | |||
| } else if parent1 != parentNone { | |||
| parentIndexes = []int{int(parent1 & parentOctopusMask)} | |||
| } | |||
| parentHashes, err := fi.getHashesFromIndexes(parentIndexes) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &CommitData{ | |||
| TreeHash: treeHash, | |||
| ParentIndexes: parentIndexes, | |||
| ParentHashes: parentHashes, | |||
| Generation: int(genAndTime >> 34), | |||
| When: time.Unix(int64(genAndTime&0x3FFFFFFFF), 0), | |||
| }, nil | |||
| } | |||
| func (fi *fileIndex) getHashesFromIndexes(indexes []int) ([]plumbing.Hash, error) { | |||
| hashes := make([]plumbing.Hash, len(indexes)) | |||
| for i, idx := range indexes { | |||
| if idx >= fi.fanout[0xff] { | |||
| return nil, ErrMalformedCommitGraphFile | |||
| } | |||
| offset := fi.oidLookupOffset + int64(idx)*20 | |||
| 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 := 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 { | |||
| return nil | |||
| } | |||
| } | |||
| return hashes | |||
| } | |||
| @@ -1,72 +1,72 @@ | |||
| package commitgraph | |||
| 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]int | |||
| } | |||
| // NewMemoryIndex creates in-memory commit graph representation | |||
| func NewMemoryIndex() *MemoryIndex { | |||
| return &MemoryIndex{ | |||
| indexMap: make(map[plumbing.Hash]int), | |||
| } | |||
| } | |||
| // GetIndexByHash gets the index in the commit graph from commit hash, if available | |||
| func (mi *MemoryIndex) GetIndexByHash(h plumbing.Hash) (int, error) { | |||
| i, ok := mi.indexMap[h] | |||
| if ok { | |||
| return i, nil | |||
| } | |||
| return 0, plumbing.ErrObjectNotFound | |||
| } | |||
| // GetCommitDataByIndex gets the commit node from the commit graph using index | |||
| // obtained from child node, if available | |||
| func (mi *MemoryIndex) GetCommitDataByIndex(i int) (*CommitData, error) { | |||
| if i >= len(mi.commitData) { | |||
| return nil, plumbing.ErrObjectNotFound | |||
| } | |||
| commitData := mi.commitData[i] | |||
| // Map parent hashes to parent indexes | |||
| if commitData.ParentIndexes == nil { | |||
| parentIndexes := make([]int, 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, 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, commitData *CommitData) { | |||
| // The parent indexes are calculated lazily in GetNodeByIndex | |||
| // which allows adding nodes out of order as long as all parents | |||
| // are eventually resolved | |||
| commitData.ParentIndexes = nil | |||
| mi.indexMap[hash] = len(mi.commitData) | |||
| mi.commitData = append(mi.commitData, commitData) | |||
| } | |||
| package commitgraph | |||
| 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]int | |||
| } | |||
| // NewMemoryIndex creates in-memory commit graph representation | |||
| func NewMemoryIndex() *MemoryIndex { | |||
| return &MemoryIndex{ | |||
| indexMap: make(map[plumbing.Hash]int), | |||
| } | |||
| } | |||
| // GetIndexByHash gets the index in the commit graph from commit hash, if available | |||
| func (mi *MemoryIndex) GetIndexByHash(h plumbing.Hash) (int, error) { | |||
| i, ok := mi.indexMap[h] | |||
| if ok { | |||
| return i, nil | |||
| } | |||
| return 0, plumbing.ErrObjectNotFound | |||
| } | |||
| // GetCommitDataByIndex gets the commit node from the commit graph using index | |||
| // obtained from child node, if available | |||
| func (mi *MemoryIndex) GetCommitDataByIndex(i int) (*CommitData, error) { | |||
| if i >= len(mi.commitData) { | |||
| return nil, plumbing.ErrObjectNotFound | |||
| } | |||
| commitData := mi.commitData[i] | |||
| // Map parent hashes to parent indexes | |||
| if commitData.ParentIndexes == nil { | |||
| parentIndexes := make([]int, 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, 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, commitData *CommitData) { | |||
| // The parent indexes are calculated lazily in GetNodeByIndex | |||
| // which allows adding nodes out of order as long as all parents | |||
| // are eventually resolved | |||
| commitData.ParentIndexes = nil | |||
| mi.indexMap[hash] = len(mi.commitData) | |||
| mi.commitData = append(mi.commitData, commitData) | |||
| } | |||
| @@ -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 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() { | |||
| } | |||
| @@ -1,131 +1,131 @@ | |||
| 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" | |||
| "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), | |||
| ) | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -1,105 +1,105 @@ | |||
| package commitgraph | |||
| import ( | |||
| "io" | |||
| "github.com/emirpasic/gods/trees/binaryheap" | |||
| "github.com/go-git/go-git/v5/plumbing" | |||
| "github.com/go-git/go-git/v5/plumbing/storer" | |||
| ) | |||
| 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/emirpasic/gods/trees/binaryheap" | |||
| "github.com/go-git/go-git/v5/plumbing" | |||
| "github.com/go-git/go-git/v5/plumbing/storer" | |||
| ) | |||
| 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() {} | |||
| @@ -1,22 +1,22 @@ | |||
| Copyright (c) 2013 Caleb Spare | |||
| MIT License | |||
| Permission is hereby granted, free of charge, to any person obtaining | |||
| a copy of this software and associated documentation files (the | |||
| "Software"), to deal in the Software without restriction, including | |||
| without limitation the rights to use, copy, modify, merge, publish, | |||
| distribute, sublicense, and/or sell copies of the Software, and to | |||
| permit persons to whom the Software is furnished to do so, subject to | |||
| the following conditions: | |||
| The above copyright notice and this permission notice shall be | |||
| included in all copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
| Copyright (c) 2013 Caleb Spare | |||
| MIT License | |||
| Permission is hereby granted, free of charge, to any person obtaining | |||
| a copy of this software and associated documentation files (the | |||
| "Software"), to deal in the Software without restriction, including | |||
| without limitation the rights to use, copy, modify, merge, publish, | |||
| distribute, sublicense, and/or sell copies of the Software, and to | |||
| permit persons to whom the Software is furnished to do so, subject to | |||
| the following conditions: | |||
| The above copyright notice and this permission notice shall be | |||
| included in all copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||