diff options
-rw-r--r-- | backend.go | 10 | ||||
-rw-r--r-- | cmd_command.go | 21 | ||||
-rw-r--r-- | cmd_comment.go | 9 | ||||
-rw-r--r-- | cmd_commit.go | 29 | ||||
-rw-r--r-- | ez.go | 18 | ||||
-rw-r--r-- | frontend.go | 8 | ||||
-rw-r--r-- | parse_catblob.go | 35 | ||||
-rw-r--r-- | parse_fastimport.go | 8 | ||||
-rw-r--r-- | textproto/catblob.go (renamed from textproto/io.go) | 100 | ||||
-rw-r--r-- | textproto/doc.go | 24 | ||||
-rw-r--r-- | textproto/fastimport.go | 108 | ||||
-rw-r--r-- | types.go (renamed from textproto/types.go) | 14 | ||||
-rw-r--r-- | util.go | 9 |
13 files changed, 220 insertions, 173 deletions
@@ -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] @@ -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 { @@ -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 { |