2014-07-28 19:26:10 +00:00
|
|
|
package pointer
|
2013-09-27 17:08:19 +00:00
|
|
|
|
|
|
|
import (
|
2014-07-24 21:23:02 +00:00
|
|
|
"bufio"
|
2013-09-27 17:08:19 +00:00
|
|
|
"bytes"
|
2014-06-04 15:38:16 +00:00
|
|
|
"errors"
|
2014-07-24 22:02:27 +00:00
|
|
|
"fmt"
|
2015-03-19 19:30:55 +00:00
|
|
|
"github.com/github/git-lfs/lfs"
|
2013-09-27 17:08:19 +00:00
|
|
|
"io"
|
2014-04-16 21:08:58 +00:00
|
|
|
"regexp"
|
2014-07-24 21:23:02 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2013-09-27 17:08:19 +00:00
|
|
|
)
|
|
|
|
|
2014-07-24 20:28:37 +00:00
|
|
|
var (
|
2014-07-24 20:49:47 +00:00
|
|
|
MediaWarning = []byte("# git-media\n")
|
|
|
|
alpha = "http://git-media.io/v/1"
|
2015-01-30 16:56:12 +00:00
|
|
|
beta = "http://git-media.io/v/2"
|
2015-03-19 19:30:55 +00:00
|
|
|
latest = "https://git-lfs.github.com/spec/v1"
|
2014-07-24 20:49:47 +00:00
|
|
|
oidType = "sha256"
|
|
|
|
alphaHeaderRE = regexp.MustCompile(`\A# (.*git-media|external)`)
|
2014-10-04 16:28:12 +00:00
|
|
|
oidRE = regexp.MustCompile(`\A[0-9a-fA-F]{64}`)
|
2014-08-14 17:15:22 +00:00
|
|
|
template = `version %s
|
|
|
|
oid sha256:%s
|
|
|
|
size %d
|
2014-07-24 22:02:27 +00:00
|
|
|
`
|
2015-03-19 19:30:55 +00:00
|
|
|
matcherRE = regexp.MustCompile("git-media|hawser|git-lfs")
|
2014-09-23 16:06:29 +00:00
|
|
|
pointerKeys = []string{"version", "oid", "size"}
|
2014-07-24 20:28:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Pointer struct {
|
|
|
|
Version string
|
|
|
|
Oid string
|
|
|
|
Size int64
|
2014-07-24 20:49:47 +00:00
|
|
|
OidType string
|
2014-07-24 20:28:37 +00:00
|
|
|
}
|
2013-09-27 17:08:53 +00:00
|
|
|
|
2014-07-24 20:34:19 +00:00
|
|
|
func NewPointer(oid string, size int64) *Pointer {
|
2014-07-24 21:23:02 +00:00
|
|
|
return &Pointer{latest, oid, size, oidType}
|
2014-07-24 20:34:19 +00:00
|
|
|
}
|
|
|
|
|
2015-03-19 19:30:55 +00:00
|
|
|
func (p *Pointer) Smudge(writer io.Writer, cb lfs.CopyCallback) error {
|
2014-08-07 14:53:13 +00:00
|
|
|
return Smudge(writer, p, cb)
|
2014-08-06 21:40:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Pointer) Encode(writer io.Writer) (int, error) {
|
|
|
|
return Encode(writer, p)
|
|
|
|
}
|
|
|
|
|
2014-09-18 18:38:10 +00:00
|
|
|
func (p *Pointer) Encoded() string {
|
|
|
|
return fmt.Sprintf(template, latest, p.Oid, p.Size)
|
|
|
|
}
|
|
|
|
|
2014-07-24 20:34:19 +00:00
|
|
|
func Encode(writer io.Writer, pointer *Pointer) (int, error) {
|
2014-09-18 18:38:10 +00:00
|
|
|
return writer.Write([]byte(pointer.Encoded()))
|
2013-09-27 17:08:19 +00:00
|
|
|
}
|
|
|
|
|
2014-07-24 20:28:37 +00:00
|
|
|
func Decode(reader io.Reader) (*Pointer, error) {
|
2014-07-24 20:49:47 +00:00
|
|
|
buf := make([]byte, 200)
|
2013-10-04 13:48:02 +00:00
|
|
|
written, err := reader.Read(buf)
|
|
|
|
if err != nil {
|
2014-07-24 20:28:37 +00:00
|
|
|
return nil, err
|
2013-10-04 13:48:02 +00:00
|
|
|
}
|
2013-09-27 17:08:19 +00:00
|
|
|
|
2014-07-24 20:49:47 +00:00
|
|
|
data := bytes.TrimSpace(buf[0:written])
|
2013-09-27 17:08:19 +00:00
|
|
|
|
2014-07-24 20:49:47 +00:00
|
|
|
if alphaHeaderRE.Match(data) {
|
|
|
|
return decodeAlpha(data)
|
|
|
|
} else {
|
2014-08-14 17:15:22 +00:00
|
|
|
return decodeKV(data)
|
2014-06-04 16:01:45 +00:00
|
|
|
}
|
2014-07-24 20:49:47 +00:00
|
|
|
}
|
2014-06-04 16:01:45 +00:00
|
|
|
|
2014-08-14 17:15:22 +00:00
|
|
|
func decodeKV(data []byte) (*Pointer, error) {
|
|
|
|
parsed, err := decodeKVData(data)
|
2014-07-24 21:23:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-08-14 17:15:22 +00:00
|
|
|
v, ok := parsed["version"]
|
2015-01-30 16:56:12 +00:00
|
|
|
if !ok || (v != latest && v != beta) {
|
2014-07-24 21:23:02 +00:00
|
|
|
if len(v) == 0 {
|
|
|
|
v = "--"
|
|
|
|
}
|
|
|
|
return nil, errors.New("Invalid version: " + v)
|
|
|
|
}
|
|
|
|
|
2014-08-14 17:15:22 +00:00
|
|
|
oidValue, ok := parsed["oid"]
|
2014-07-24 21:23:02 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid Oid")
|
|
|
|
}
|
|
|
|
|
|
|
|
oidParts := strings.SplitN(oidValue, ":", 2)
|
|
|
|
if len(oidParts) != 2 {
|
|
|
|
return nil, errors.New("Invalid Oid type in" + oidValue)
|
|
|
|
}
|
|
|
|
if oidParts[0] != oidType {
|
|
|
|
return nil, errors.New("Invalid Oid type: " + oidParts[0])
|
|
|
|
}
|
|
|
|
oid := oidParts[1]
|
|
|
|
|
|
|
|
var size int64
|
2014-08-14 17:15:22 +00:00
|
|
|
sizeStr, ok := parsed["size"]
|
2014-07-24 21:23:02 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid Oid")
|
|
|
|
} else {
|
|
|
|
size, err = strconv.ParseInt(sizeStr, 10, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("Invalid size: " + sizeStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NewPointer(oid, size), nil
|
|
|
|
}
|
|
|
|
|
2014-08-14 17:15:22 +00:00
|
|
|
func decodeKVData(data []byte) (map[string]string, error) {
|
|
|
|
m := make(map[string]string)
|
2014-08-14 20:23:19 +00:00
|
|
|
|
2015-01-30 16:56:12 +00:00
|
|
|
if !matcherRE.Match(data) {
|
2015-03-19 19:30:55 +00:00
|
|
|
return m, fmt.Errorf("Not a valid Git LFS pointer file.")
|
2014-08-14 20:23:19 +00:00
|
|
|
}
|
|
|
|
|
2014-08-14 17:15:22 +00:00
|
|
|
scanner := bufio.NewScanner(bytes.NewBuffer(data))
|
2014-08-14 20:23:19 +00:00
|
|
|
line := 0
|
2014-08-14 20:30:33 +00:00
|
|
|
numKeys := len(pointerKeys)
|
2014-08-14 17:15:22 +00:00
|
|
|
for scanner.Scan() {
|
2014-08-14 20:30:33 +00:00
|
|
|
text := scanner.Text()
|
|
|
|
if len(text) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := strings.SplitN(text, " ", 2)
|
|
|
|
key := parts[0]
|
|
|
|
|
|
|
|
if numKeys <= line {
|
|
|
|
return m, fmt.Errorf("Extra line: %s", text)
|
|
|
|
}
|
|
|
|
|
|
|
|
if expected := pointerKeys[line]; key != expected {
|
|
|
|
return m, fmt.Errorf("Expected key %s, got %s", expected, key)
|
|
|
|
}
|
|
|
|
|
2014-08-14 20:23:19 +00:00
|
|
|
line += 1
|
|
|
|
if len(parts) < 2 {
|
2014-08-14 20:30:33 +00:00
|
|
|
return m, fmt.Errorf("Error reading line %d: %s", line, text)
|
2014-08-14 17:15:22 +00:00
|
|
|
}
|
|
|
|
|
2014-08-14 20:30:33 +00:00
|
|
|
m[key] = parts[1]
|
2014-08-14 17:15:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return m, scanner.Err()
|
|
|
|
}
|
|
|
|
|
2014-07-24 20:49:47 +00:00
|
|
|
func decodeAlpha(data []byte) (*Pointer, error) {
|
2014-07-24 21:23:02 +00:00
|
|
|
lines := bytes.Split(data, []byte("\n"))
|
2014-07-24 20:49:47 +00:00
|
|
|
last := len(lines) - 1
|
|
|
|
if last == 0 {
|
2014-08-14 20:23:19 +00:00
|
|
|
return nil, errors.New("No OID in pointer file")
|
2013-10-04 13:48:02 +00:00
|
|
|
}
|
2014-10-04 16:28:12 +00:00
|
|
|
if !oidRE.Match(lines[last]) {
|
|
|
|
return nil, errors.New("Invalid OID in pointer file")
|
|
|
|
}
|
2014-04-16 21:08:58 +00:00
|
|
|
|
2014-07-24 20:49:47 +00:00
|
|
|
return &Pointer{alpha, string(lines[last]), 0, oidType}, nil
|
2013-09-27 17:08:19 +00:00
|
|
|
}
|