github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/lfs/pointer.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package lfs
     7  
     8  import (
     9  	"crypto/sha256"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"path"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/gitbundle/modules/log"
    20  )
    21  
    22  const (
    23  	blobSizeCutoff = 1024
    24  
    25  	// MetaFileIdentifier is the string appearing at the first line of LFS pointer files.
    26  	// https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
    27  	MetaFileIdentifier = "version https://git-lfs.github.com/spec/v1"
    28  
    29  	// MetaFileOidPrefix appears in LFS pointer files on a line before the sha256 hash.
    30  	MetaFileOidPrefix = "oid sha256:"
    31  )
    32  
    33  var (
    34  	// ErrMissingPrefix occurs if the content lacks the LFS prefix
    35  	ErrMissingPrefix = errors.New("Content lacks the LFS prefix")
    36  
    37  	// ErrInvalidStructure occurs if the content has an invalid structure
    38  	ErrInvalidStructure = errors.New("Content has an invalid structure")
    39  
    40  	// ErrInvalidOIDFormat occurs if the oid has an invalid format
    41  	ErrInvalidOIDFormat = errors.New("OID has an invalid format")
    42  )
    43  
    44  // ReadPointer tries to read LFS pointer data from the reader
    45  func ReadPointer(reader io.Reader) (Pointer, error) {
    46  	buf := make([]byte, blobSizeCutoff)
    47  	n, err := io.ReadFull(reader, buf)
    48  	if err != nil && err != io.ErrUnexpectedEOF {
    49  		return Pointer{}, err
    50  	}
    51  	buf = buf[:n]
    52  
    53  	return ReadPointerFromBuffer(buf)
    54  }
    55  
    56  var oidPattern = regexp.MustCompile(`^[a-f\d]{64}$`)
    57  
    58  // ReadPointerFromBuffer will return a pointer if the provided byte slice is a pointer file or an error otherwise.
    59  func ReadPointerFromBuffer(buf []byte) (Pointer, error) {
    60  	var p Pointer
    61  
    62  	headString := string(buf)
    63  	if !strings.HasPrefix(headString, MetaFileIdentifier) {
    64  		return p, ErrMissingPrefix
    65  	}
    66  
    67  	splitLines := strings.Split(headString, "\n")
    68  	if len(splitLines) < 3 {
    69  		return p, ErrInvalidStructure
    70  	}
    71  
    72  	oid := strings.TrimPrefix(splitLines[1], MetaFileOidPrefix)
    73  	if len(oid) != 64 || !oidPattern.MatchString(oid) {
    74  		return p, ErrInvalidOIDFormat
    75  	}
    76  	size, err := strconv.ParseInt(strings.TrimPrefix(splitLines[2], "size "), 10, 64)
    77  	if err != nil {
    78  		return p, err
    79  	}
    80  
    81  	p.Oid = oid
    82  	p.Size = size
    83  
    84  	return p, nil
    85  }
    86  
    87  // IsValid checks if the pointer has a valid structure.
    88  // It doesn't check if the pointed-to-content exists.
    89  func (p Pointer) IsValid() bool {
    90  	if len(p.Oid) != 64 {
    91  		return false
    92  	}
    93  	if !oidPattern.MatchString(p.Oid) {
    94  		return false
    95  	}
    96  	if p.Size < 0 {
    97  		return false
    98  	}
    99  	return true
   100  }
   101  
   102  // StringContent returns the string representation of the pointer
   103  // https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#the-pointer
   104  func (p Pointer) StringContent() string {
   105  	return fmt.Sprintf("%s\n%s%s\nsize %d\n", MetaFileIdentifier, MetaFileOidPrefix, p.Oid, p.Size)
   106  }
   107  
   108  // RelativePath returns the relative storage path of the pointer
   109  func (p Pointer) RelativePath() string {
   110  	if len(p.Oid) < 5 {
   111  		return p.Oid
   112  	}
   113  
   114  	return path.Join(p.Oid[0:2], p.Oid[2:4], p.Oid[4:])
   115  }
   116  
   117  // ColorFormat provides a basic color format for a Team
   118  func (p Pointer) ColorFormat(s fmt.State) {
   119  	if p.Oid == "" && p.Size == 0 {
   120  		log.ColorFprintf(s, "<empty>")
   121  		return
   122  	}
   123  	log.ColorFprintf(s, "%s:%d",
   124  		log.NewColoredIDValue(p.Oid),
   125  		p.Size)
   126  }
   127  
   128  // GeneratePointer generates a pointer for arbitrary content
   129  func GeneratePointer(content io.Reader) (Pointer, error) {
   130  	h := sha256.New()
   131  	c, err := io.Copy(h, content)
   132  	if err != nil {
   133  		return Pointer{}, err
   134  	}
   135  	sum := h.Sum(nil)
   136  	return Pointer{Oid: hex.EncodeToString(sum), Size: c}, nil
   137  }