summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend.go10
-rw-r--r--cmd_command.go21
-rw-r--r--cmd_comment.go9
-rw-r--r--cmd_commit.go29
-rw-r--r--ez.go18
-rw-r--r--frontend.go8
-rw-r--r--parse_catblob.go35
-rw-r--r--parse_fastimport.go8
-rw-r--r--textproto/catblob.go (renamed from textproto/io.go)100
-rw-r--r--textproto/doc.go24
-rw-r--r--textproto/fastimport.go108
-rw-r--r--types.go (renamed from textproto/types.go)14
-rw-r--r--util.go9
13 files changed, 220 insertions, 173 deletions
diff --git a/backend.go b/backend.go
index 0a95547..0486e98 100644
--- a/backend.go
+++ b/backend.go
@@ -17,10 +17,10 @@ package libfastimport
import (
"bufio"
- "fmt"
"io"
"git.lukeshu.com/go/libfastimport/textproto"
+ "github.com/pkg/errors"
)
// A Backend is something that consumes a fast-import stream; the
@@ -85,7 +85,7 @@ func NewBackend(fastImport io.WriteCloser, catBlob io.Reader, onErr func(error)
// It is an error (panic) if Cmd is a type that may only be used in a
// commit but we aren't in a commit.
func (b *Backend) Do(cmd Cmd) error {
- if b.err == nil {
+ if b.err != nil {
return b.err
}
@@ -94,12 +94,12 @@ func (b *Backend) Do(cmd Cmd) error {
_, b.inCommit = cmd.(CmdCommit)
case cmdClassCommit:
if !b.inCommit {
- panic(fmt.Errorf("Cannot issue commit sub-command outside of a commit: %[1]T(%#[1]v)", cmd))
+ panic(errors.Errorf("Cannot issue commit sub-command outside of a commit: %[1]T(%#[1]v)", cmd))
}
case cmdClassComment:
/* do nothing */
default:
- panic(fmt.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
+ panic(errors.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
}
err := cmd.fiCmdWrite(b.fastImportWrite)
@@ -167,7 +167,7 @@ func (b *Backend) CatBlob(cmd CmdCatBlob) (sha1 string, data string, err error)
//
// It is an error (panic) to call Ls if NewBackend did not have a
// cat-blob reader passed to it.
-func (b *Backend) Ls(cmd CmdLs) (mode textproto.Mode, dataref string, path textproto.Path, err error) {
+func (b *Backend) Ls(cmd CmdLs) (mode Mode, dataref string, path Path, err error) {
err = b.Do(cmd)
if err != nil {
return
diff --git a/cmd_command.go b/cmd_command.go
index 7d65727..ff74f80 100644
--- a/cmd_command.go
+++ b/cmd_command.go
@@ -16,11 +16,10 @@
package libfastimport
import (
- "fmt"
"strconv"
"strings"
- "git.lukeshu.com/go/libfastimport/textproto"
+ "github.com/pkg/errors"
)
// commit //////////////////////////////////////////////////////////////////////
@@ -36,8 +35,8 @@ import (
type CmdCommit struct {
Ref string
Mark int // optional; < 1 for non-use
- Author *textproto.Ident
- Committer textproto.Ident
+ Author *Ident
+ Committer Ident
Msg string
From string
Merge []string
@@ -81,16 +80,16 @@ func (CmdCommit) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
// ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
if strings.HasPrefix(ez.PeekLine(), "author ") {
- author, err := textproto.ParseIdent(trimLinePrefix(ez.ReadLine(), "author "))
+ author, err := ParseIdent(trimLinePrefix(ez.ReadLine(), "author "))
ez.Errcheck(err)
c.Author = &author
}
// 'committer' (SP <name>)? SP LT <email> GT SP <when> LF
if !strings.HasPrefix(ez.PeekLine(), "committer ") {
- ez.Errcheck(fmt.Errorf("commit: expected committer command: %v", ez.ReadLine()))
+ ez.Errcheck(errors.Errorf("commit: expected committer command: %v", ez.ReadLine()))
}
- c.Committer, err = textproto.ParseIdent(trimLinePrefix(ez.ReadLine(), "committer "))
+ c.Committer, err = ParseIdent(trimLinePrefix(ez.ReadLine(), "committer "))
ez.Errcheck(err)
// data
@@ -133,7 +132,7 @@ func (CmdCommitEnd) fiCmdRead(fir fiReader) (Cmd, error) { panic("not reached")
type CmdTag struct {
RefName string
CommitIsh string
- Tagger textproto.Ident
+ Tagger Ident
Data string
}
@@ -158,15 +157,15 @@ func (CmdTag) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
// 'from' SP <commit-ish> LF
if !strings.HasPrefix(ez.PeekLine(), "from ") {
- ez.Errcheck(fmt.Errorf("tag: expected from command: %v", ez.ReadLine()))
+ ez.Errcheck(errors.Errorf("tag: expected from command: %v", ez.ReadLine()))
}
c.CommitIsh = trimLinePrefix(ez.ReadLine(), "from ")
// 'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
if !strings.HasPrefix(ez.PeekLine(), "tagger ") {
- ez.Errcheck(fmt.Errorf("tag: expected tagger command: %v", ez.ReadLine()))
+ ez.Errcheck(errors.Errorf("tag: expected tagger command: %v", ez.ReadLine()))
}
- c.Tagger, err = textproto.ParseIdent(trimLinePrefix(ez.ReadLine(), "tagger "))
+ c.Tagger, err = ParseIdent(trimLinePrefix(ez.ReadLine(), "tagger "))
ez.Errcheck(err)
// data
diff --git a/cmd_comment.go b/cmd_comment.go
index c640fef..c0325b4 100644
--- a/cmd_comment.go
+++ b/cmd_comment.go
@@ -16,11 +16,10 @@
package libfastimport
import (
- "fmt"
"strconv"
"strings"
- "git.lukeshu.com/go/libfastimport/textproto"
+ "github.com/pkg/errors"
)
// comment /////////////////////////////////////////////////////////////////////
@@ -65,7 +64,7 @@ func (CmdGetMark) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
c := CmdGetMark{}
c.Mark, err = strconv.Atoi(trimLinePrefix(line, "get-mark :"))
if err != nil {
- return nil, fmt.Errorf("get-mark: %v", err)
+ return nil, errors.Wrap(err, "get-mark")
}
return c, nil
}
@@ -103,7 +102,7 @@ func (CmdCatBlob) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
// 40-byte SHA-1.
type CmdLs struct {
DataRef string // optional if inside of a commit
- Path textproto.Path
+ Path Path
}
// If you're thinking "but wait, parser_registerCmd will see CmdLs as
@@ -140,7 +139,7 @@ func (CmdLs) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
sp = strings.IndexByte(line, ' ')
}
c := CmdLs{}
- c.Path = textproto.PathUnescape(str[sp+1:])
+ c.Path = PathUnescape(str[sp+1:])
if sp >= 0 {
c.DataRef = str[:sp]
}
diff --git a/cmd_commit.go b/cmd_commit.go
index 83b9815..170c511 100644
--- a/cmd_commit.go
+++ b/cmd_commit.go
@@ -16,11 +16,10 @@
package libfastimport
import (
- "fmt"
"strconv"
"strings"
- "git.lukeshu.com/go/libfastimport/textproto"
+ "github.com/pkg/errors"
)
// M ///////////////////////////////////////////////////////////////////////////
@@ -33,8 +32,8 @@ import (
// To specify the full content of the file inline, use
// FileModifyInline instead.
type FileModify struct {
- Mode textproto.Mode
- Path textproto.Path
+ Mode Mode
+ Path Path
DataRef string
}
@@ -52,7 +51,7 @@ func (FileModify) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
str := trimLinePrefix(line, "M ")
fields := strings.SplitN(str, " ", 3)
if len(fields) != 3 {
- return nil, fmt.Errorf("commit: malformed modify command: %v", line)
+ return nil, errors.Errorf("commit: malformed modify command: %v", line)
}
nMode, err := strconv.ParseUint(fields[0], 8, 18)
@@ -61,7 +60,7 @@ func (FileModify) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
}
ref := fields[1]
- path := textproto.PathUnescape(fields[2])
+ path := PathUnescape(fields[2])
if ref == "inline" {
line, err = fir.ReadLine()
@@ -73,13 +72,13 @@ func (FileModify) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
return nil, err
}
return FileModifyInline{
- Mode: textproto.Mode(nMode),
+ Mode: Mode(nMode),
Path: path,
Data: data,
}, nil
} else {
return FileModify{
- Mode: textproto.Mode(nMode),
+ Mode: Mode(nMode),
Path: path,
DataRef: ref,
}, nil
@@ -94,8 +93,8 @@ func (FileModify) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
// To instead specify the content with a mark reference (":<idnum>")
// or with a full 40-byte SHA-1, use FileModify instead.
type FileModifyInline struct {
- Mode textproto.Mode
- Path textproto.Path
+ Mode Mode
+ Path Path
Data string
}
@@ -113,7 +112,7 @@ func (FileModifyInline) fiCmdRead(fiReader) (Cmd, error) { panic("not reached")
// FileDelete appears after a CmdCommit (and before a CmdCommitEnd),
// and causes the CmdCommit to recursively remove a file or directory.
type FileDelete struct {
- Path textproto.Path
+ Path Path
}
func (o FileDelete) fiCmdClass() cmdClass { return cmdClassCommit }
@@ -126,7 +125,7 @@ func (FileDelete) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
if err != nil {
return nil, err
}
- return FileDelete{Path: textproto.PathUnescape(trimLinePrefix(line, "D "))}, nil
+ return FileDelete{Path: PathUnescape(trimLinePrefix(line, "D "))}, nil
}
// C ///////////////////////////////////////////////////////////////////////////
@@ -135,8 +134,8 @@ func (FileDelete) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
// and causes the CmdCommit to recursively copy an existing file or
// subdirectory to a different location.
type FileCopy struct {
- Src textproto.Path
- Dst textproto.Path
+ Src Path
+ Dst Path
}
func (o FileCopy) fiCmdClass() cmdClass { return cmdClassCommit }
@@ -218,7 +217,7 @@ func (NoteModify) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
str := trimLinePrefix(line, "N ")
sp := strings.IndexByte(str, ' ')
if sp < 0 {
- return nil, fmt.Errorf("commit: malformed notemodify command: %v", line)
+ return nil, errors.Errorf("commit: malformed notemodify command: %v", line)
}
ref := str[:sp]
diff --git a/ez.go b/ez.go
index 15f1af8..be57d58 100644
--- a/ez.go
+++ b/ez.go
@@ -17,6 +17,8 @@ package libfastimport
import (
"strconv"
+
+ "github.com/pkg/errors"
)
type ezfiw struct {
@@ -47,12 +49,18 @@ type ezfir struct {
err error
}
+var ezPanic = errors.New("everything is fine")
+
func (e *ezfir) Defer() error {
- if r := recover(); r != nil {
- if e.err != nil {
- return e.err
+ if e.err != nil {
+ r := recover()
+ if r == nil {
+ panic("ezfir.err got set, but didn't panic")
+ }
+ if r != ezPanic {
+ panic(r)
}
- panic(r)
+ return e.err
}
return nil
}
@@ -62,7 +70,7 @@ func (e *ezfir) Errcheck(err error) {
return
}
e.err = err
- panic("everything is fine")
+ panic(ezPanic)
}
func (e *ezfir) PeekLine() string {
diff --git a/frontend.go b/frontend.go
index dc8e771..c069ddf 100644
--- a/frontend.go
+++ b/frontend.go
@@ -111,21 +111,21 @@ func (f *Frontend) RespondCatBlob(sha1 string, data string) error {
//
// It is an error (panic) to call RespondLs if NewFrontend did not
// have a cat-blob writer passed to it.
-func (f *Frontend) RespondLs(mode textproto.Mode, dataref string, path textproto.Path) error {
+func (f *Frontend) RespondLs(mode Mode, dataref string, path Path) error {
var err error
if mode == 0 {
err = f.catBlobWrite.WriteLine("missing", path)
} else {
var t string
switch mode {
- case textproto.ModeDir:
+ case ModeDir:
t = "tree"
- case textproto.ModeGit:
+ case ModeGit:
t = "commit"
default:
t = "blob"
}
- err = f.catBlobWrite.WriteLine(mode, t, dataref+"\t"+textproto.PathEscape(path))
+ err = f.catBlobWrite.WriteLine(mode, t, dataref+"\t"+PathEscape(path))
}
if err != nil {
return err
diff --git a/parse_catblob.go b/parse_catblob.go
index d03d0f5..9f08da8 100644
--- a/parse_catblob.go
+++ b/parse_catblob.go
@@ -16,23 +16,22 @@
package libfastimport
import (
- "fmt"
"strconv"
"strings"
- "git.lukeshu.com/go/libfastimport/textproto"
+ "github.com/pkg/errors"
)
func cbpGetMark(line string) (string, error) {
if len(line) != 41 {
- return "", fmt.Errorf("get-mark: short <sha1>\\n: %q", line)
+ return "", errors.Errorf("get-mark: short <sha1>\\n: %q", line)
}
if line[40] != '\n' {
- return "", fmt.Errorf("get-mark: malformed <sha1>\\n: %q", line)
+ return "", errors.Errorf("get-mark: malformed <sha1>\\n: %q", line)
}
for _, b := range line[:40] {
if !(('0' <= b && b <= '9') || ('a' <= b && b <= 'f')) {
- return "", fmt.Errorf("get-mark: malformed <sha1>: %q", line[:40])
+ return "", errors.Errorf("get-mark: malformed <sha1>: %q", line[:40])
}
}
return line[:40], nil
@@ -45,63 +44,63 @@ func cbpCatBlob(full string) (sha1 string, data string, err error) {
// <data> LF
if full[len(full)-1] != '\n' {
- return "", "", fmt.Errorf("cat-blob: missing trailing newline")
+ return "", "", errors.Errorf("cat-blob: missing trailing newline")
}
lf := strings.IndexByte(full, '\n')
if lf < 0 || lf == len(full)-1 {
- return "", "", fmt.Errorf("cat-blob: malformed header: %q", full)
+ return "", "", errors.Errorf("cat-blob: malformed header: %q", full)
}
head := full[:lf]
data = full[lf+1 : len(full)-1]
if len(head) < 40+6+1 {
- return "", "", fmt.Errorf("cat-blob: malformed header: %q", head)
+ return "", "", errors.Errorf("cat-blob: malformed header: %q", head)
}
sha1 = head[:40]
for _, b := range sha1 {
if !(('0' <= b && b <= '9') || ('a' <= b && b <= 'f')) {
- return "", "", fmt.Errorf("cat-blob: malformed <sha1>: %q", sha1)
+ return "", "", errors.Errorf("cat-blob: malformed <sha1>: %q", sha1)
}
}
if string(head[40:46]) != " blob " {
- return "", "", fmt.Errorf("cat-blob: malformed header: %q", head)
+ return "", "", errors.Errorf("cat-blob: malformed header: %q", head)
}
size, err := strconv.Atoi(head[46:])
if err != nil {
- return "", "", fmt.Errorf("cat-blob: malformed blob size: %v", err)
+ return "", "", errors.Wrap(err, "cat-blob: malformed blob size")
}
if size != len(data) {
- return "", "", fmt.Errorf("cat-blob: size header (%d) didn't match delivered size (%d)", size, len(data))
+ return "", "", errors.Errorf("cat-blob: size header (%d) didn't match delivered size (%d)", size, len(data))
}
return sha1, data, err
}
-func cbpLs(line string) (mode textproto.Mode, dataref string, path textproto.Path, err error) {
+func cbpLs(line string) (mode Mode, dataref string, path Path, err error) {
// <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
// or
// 'missing' SP <path> LF
if line[len(line)-1] != '\n' {
- return 0, "", "", fmt.Errorf("ls: missing trailing newline")
+ return 0, "", "", errors.New("ls: missing trailing newline")
}
line = line[:len(line)-1]
if strings.HasPrefix(line, "missing ") {
strPath := line[8:]
- return 0, "", textproto.PathUnescape(strPath), nil
+ return 0, "", PathUnescape(strPath), nil
} else {
fields := strings.SplitN(line, " ", 3)
if len(fields) < 3 {
- return 0, "", "", fmt.Errorf("ls: malformed line: %q", line)
+ return 0, "", "", errors.Errorf("ls: malformed line: %q", line)
}
ht := strings.IndexByte(fields[2], '\t')
if ht < 0 {
- return 0, "", "", fmt.Errorf("ls: malformed line: %q", line)
+ return 0, "", "", errors.Errorf("ls: malformed line: %q", line)
}
strMode := fields[0]
//strType := fields[1]
@@ -112,6 +111,6 @@ func cbpLs(line string) (mode textproto.Mode, dataref string, path textproto.Pat
if err != nil {
return 0, "", "", err
}
- return textproto.Mode(nMode), strRef, textproto.PathUnescape(strPath), nil
+ return Mode(nMode), strRef, PathUnescape(strPath), nil
}
}
diff --git a/parse_fastimport.go b/parse_fastimport.go
index 11b27aa..b01b251 100644
--- a/parse_fastimport.go
+++ b/parse_fastimport.go
@@ -16,10 +16,10 @@
package libfastimport
import (
- "fmt"
"strings"
"git.lukeshu.com/go/libfastimport/textproto"
+ "github.com/pkg/errors"
)
var parser_regularCmds = make(map[string]Cmd)
@@ -32,7 +32,7 @@ func parser_registerCmd(prefix string, cmd Cmd) {
case cmdClassComment:
parser_commentCmds[prefix] = cmd
default:
- panic(fmt.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
+ panic(errors.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
}
}
@@ -137,12 +137,12 @@ func (p *parser) parse() error {
_, p.inCommit = cmd.(CmdCommit)
case cmdClassCommit:
if !p.inCommit {
- return fmt.Errorf("Got in-commit-only command outside of a commit: %[1]T(%#[1]v)", cmd)
+ return errors.Errorf("Got in-commit-only command outside of a commit: %[1]T(%#[1]v)", cmd)
}
case cmdClassComment:
/* do nothing */
default:
- panic(fmt.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
+ panic(errors.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
}
p.ret_cmd <- cmd
diff --git a/textproto/io.go b/textproto/catblob.go
index dde5470..6836689 100644
--- a/textproto/io.go
+++ b/textproto/catblob.go
@@ -13,14 +13,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-// Package textproto implements low-level details of the fast-import
-// format.
-//
-// This package deals with parsing and marshalling idiosyncratic
-// datatypes used by the format (Ident tuples, 18-bit Mode numbers,
-// oddly-quoted Path strings), and abstracting over special-case
-// commands that break the "line-based" nature of the format (the
-// "data" command, responses to the "cat-blob" command).
package textproto
import (
@@ -28,93 +20,8 @@ import (
"fmt"
"io"
"strconv"
- "strings"
)
-// FIReader is a low-level parser of a fast-import stream.
-type FIReader struct {
- r *bufio.Reader
-
- line *string
- err error
-}
-
-// NewFIReader creates a new FIReader parser.
-func NewFIReader(r io.Reader) *FIReader {
- return &FIReader{
- r: bufio.NewReader(r),
- }
-}
-
-// ReadLine reads a "line" from the stream; with special handling for
-// the "data" command, which isn't really a single line, but rather
-// contains arbitrary binary data.
-func (fir *FIReader) ReadLine() (line string, err error) {
- for len(line) <= 1 {
- line, err = fir.r.ReadString('\n')
- if err != nil {
- return
- }
- }
-
- if strings.HasPrefix(line, "data ") {
- if line[5:7] == "<<" {
- // Delimited format
- delim := line[7 : len(line)-1]
- suffix := "\n" + delim + "\n"
-
- for !strings.HasSuffix(line, suffix) {
- var _line string
- _line, err = fir.r.ReadString('\n')
- line += _line
- if err != nil {
- return
- }
- }
- } else {
- // Exact byte count format
- var size int
- size, err = strconv.Atoi(line[5 : len(line)-1])
- if err != nil {
- return
- }
- data := make([]byte, size)
- _, err = io.ReadFull(fir.r, data)
- line += string(data)
- }
- }
- return
-}
-
-// FIWriter is a low-level marshaller of a fast-import stream.
-type FIWriter struct {
- w io.Writer
-}
-
-// NewFIWriter creates a new FIWriter marshaller.
-func NewFIWriter(w io.Writer) *FIWriter {
- return &FIWriter{
- w: w,
- }
-}
-
-// WriteLine writes an ordinary line to the stream; arguments are
-// handled similarly to fmt.Println.
-func (fiw *FIWriter) WriteLine(a ...interface{}) error {
- _, err := fmt.Fprintln(fiw.w, a...)
- return err
-}
-
-// WriteData writes a 'data' command to the stream.
-func (fiw *FIWriter) WriteData(data string) error {
- err := fiw.WriteLine("data", len(data))
- if err != nil {
- return err
- }
- _, err = io.WriteString(fiw.w, data)
- return err
-}
-
// CatBlobReader is a low-level parser of an fast-import auxiliary
// "cat-blob" stream.
type CatBlobReader struct {
@@ -140,11 +47,12 @@ func (cbr *CatBlobReader) ReadLine() (line string, err error) {
}
// get-mark : <sha1> LF
- // cat-blob : <sha1> SP 'blob' SP <size> LF <data> LF
+ // cat-blob : <sha1> SP 'blob' SP <size> LF
+ // <data> LF
// ls : <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
// ls : 'missing' SP <path> LF
- // decide if we have a cat-blob result
+ // decide if we have a cat-blob result (return early if we don't)
if len(line) <= 46 || line[40:46] != " blob " {
return
}
@@ -161,7 +69,7 @@ func (cbr *CatBlobReader) ReadLine() (line string, err error) {
}
data := make([]byte, size+1)
_, err = io.ReadFull(cbr.r, data)
- line += string(data[:size])
+ line += string(data)
return
}
diff --git a/textproto/doc.go b/textproto/doc.go
new file mode 100644
index 0000000..82154a1
--- /dev/null
+++ b/textproto/doc.go
@@ -0,0 +1,24 @@
+// Copyright (C) 2017-2018 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Package textproto implements low-level details of the fast-import
+// format.
+//
+// This package deals with parsing and marshalling idiosyncratic
+// datatypes used by the format (Ident tuples, 18-bit Mode numbers,
+// oddly-quoted Path strings), and abstracting over special-case
+// commands that break the "line-based" nature of the format (the
+// "data" command, responses to the "cat-blob" command).
+package textproto
diff --git a/textproto/fastimport.go b/textproto/fastimport.go
new file mode 100644
index 0000000..b43c378
--- /dev/null
+++ b/textproto/fastimport.go
@@ -0,0 +1,108 @@
+// Copyright (C) 2017-2018 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+package textproto
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+// FIReader is a low-level parser of a fast-import stream.
+type FIReader struct {
+ r *bufio.Reader
+
+ line *string
+ err error
+}
+
+// NewFIReader creates a new FIReader parser.
+func NewFIReader(r io.Reader) *FIReader {
+ return &FIReader{
+ r: bufio.NewReader(r),
+ }
+}
+
+// ReadLine reads a "line" from the stream; with special handling for
+// the "data" command, which isn't really a single line, but rather
+// contains arbitrary binary data.
+func (fir *FIReader) ReadLine() (line string, err error) {
+ for len(line) <= 1 {
+ line, err = fir.r.ReadString('\n')
+ if err != nil {
+ return
+ }
+ }
+
+ if strings.HasPrefix(line, "data ") {
+ if line[5:7] == "<<" {
+ // Delimited format
+ delim := line[7 : len(line)-1]
+ suffix := "\n" + delim + "\n"
+
+ for !strings.HasSuffix(line, suffix) {
+ var _line string
+ _line, err = fir.r.ReadString('\n')
+ line += _line
+ if err != nil {
+ return
+ }
+ }
+ } else {
+ // Exact byte count format
+ var size int
+ size, err = strconv.Atoi(line[5 : len(line)-1])
+ if err != nil {
+ return
+ }
+ data := make([]byte, size)
+ _, err = io.ReadFull(fir.r, data)
+ line += string(data)
+ }
+ }
+ return
+}
+
+// FIWriter is a low-level marshaller of a fast-import stream.
+type FIWriter struct {
+ w io.Writer
+}
+
+// NewFIWriter creates a new FIWriter marshaller.
+func NewFIWriter(w io.Writer) *FIWriter {
+ return &FIWriter{
+ w: w,
+ }
+}
+
+// WriteLine writes an ordinary line to the stream; arguments are
+// handled similarly to fmt.Println.
+func (fiw *FIWriter) WriteLine(a ...interface{}) error {
+ _, err := fmt.Fprintln(fiw.w, a...)
+ return err
+}
+
+// WriteData writes a 'data' command to the stream.
+func (fiw *FIWriter) WriteData(data string) error {
+ err := fiw.WriteLine("data", len(data))
+ if err != nil {
+ return err
+ }
+ _, err = io.WriteString(fiw.w, data)
+ return err
+}
diff --git a/textproto/types.go b/types.go
index 1ff21a8..2a89048 100644
--- a/textproto/types.go
+++ b/types.go
@@ -13,13 +13,15 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-package textproto
+package libfastimport
import (
"fmt"
"strconv"
"strings"
"time"
+
+ "github.com/pkg/errors"
)
// Ident is a tuple of a commiter's (or author's) name, email, and a
@@ -63,27 +65,27 @@ func ParseIdent(str string) (Ident, error) {
ret := Ident{}
lt := strings.IndexAny(str, "<>")
if lt < 0 || str[lt] != '<' {
- return ret, fmt.Errorf("Missing < in ident string: %v", str)
+ return ret, errors.Errorf("Missing < in ident string: %v", str)
}
if lt > 0 {
if str[lt-1] != ' ' {
- return ret, fmt.Errorf("Missing space before < in ident string: %v", str)
+ return ret, errors.Errorf("Missing space before < in ident string: %v", str)
}
ret.Name = str[:lt-1]
}
gt := lt + 1 + strings.IndexAny(str[lt+1:], "<>")
if gt < lt+1 || str[gt] != '>' {
- return ret, fmt.Errorf("Missing > in ident string: %v", str)
+ return ret, errors.Errorf("Missing > in ident string: %v", str)
}
if str[gt+1] != ' ' {
- return ret, fmt.Errorf("Missing space after > in ident string: %v", str)
+ return ret, errors.Errorf("Missing space after > in ident string: %v", str)
}
ret.Email = str[lt+1 : gt]
strWhen := str[gt+2:]
sp := strings.IndexByte(strWhen, ' ')
if sp < 0 {
- return ret, fmt.Errorf("missing time zone in when: %v", str)
+ return ret, errors.Errorf("missing time zone in when: %v", str)
}
sec, err := strconv.ParseInt(strWhen[:sp], 10, 64)
if err != nil {
diff --git a/util.go b/util.go
index 35aadeb..4ca60e9 100644
--- a/util.go
+++ b/util.go
@@ -16,9 +16,10 @@
package libfastimport
import (
- "fmt"
"strconv"
"strings"
+
+ "github.com/pkg/errors"
)
func trimLinePrefix(line string, prefix string) string {
@@ -34,19 +35,19 @@ func trimLinePrefix(line string, prefix string) string {
func parse_data(line string) (data string, err error) {
nl := strings.IndexByte(line, '\n')
if nl < 0 {
- return "", fmt.Errorf("data: expected newline: %v", data)
+ return "", errors.Errorf("data: expected newline: %v", data)
}
head := line[:nl+1]
rest := line[nl+1:]
if !strings.HasPrefix(head, "data ") {
- return "", fmt.Errorf("data: could not parse: %v", data)
+ return "", errors.Errorf("data: could not parse: %v", data)
}
if strings.HasPrefix(head, "data <<") {
// Delimited format
delim := trimLinePrefix(head, "data <<")
suffix := "\n" + delim + "\n"
if !strings.HasSuffix(rest, suffix) {
- return "", fmt.Errorf("data: did not find suffix: %v", suffix)
+ return "", errors.Errorf("data: did not find suffix: %v", suffix)
}
data = strings.TrimSuffix(rest, suffix)
} else {