summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd.go6
-rw-r--r--cmd_command.go189
-rw-r--r--cmd_comment.go67
-rw-r--r--cmd_commit.go119
-rw-r--r--ez.go65
-rw-r--r--ezfiw.go30
-rw-r--r--frontend.go369
-rw-r--r--parse_fastimport.go162
-rw-r--r--textproto/io.go3
9 files changed, 634 insertions, 376 deletions
diff --git a/cmd.go b/cmd.go
index 7ba40e0..5e76708 100644
--- a/cmd.go
+++ b/cmd.go
@@ -4,6 +4,11 @@ import (
"git.lukeshu.com/go/libfastimport/textproto"
)
+type fiReader interface {
+ PeekLine() (string, error)
+ ReadLine() (string, error)
+}
+
type cmdClass int
const (
@@ -14,6 +19,7 @@ const (
)
type Cmd interface {
+ fiCmdRead(fiReader) (Cmd, error)
fiCmdWrite(*textproto.FIWriter) error
fiCmdClass() cmdClass
}
diff --git a/cmd_command.go b/cmd_command.go
index a1d1a41..b438cb6 100644
--- a/cmd_command.go
+++ b/cmd_command.go
@@ -1,9 +1,15 @@
package libfastimport
import (
+ "fmt"
+ "strconv"
+ "strings"
+
"git.lukeshu.com/go/libfastimport/textproto"
)
+// commit //////////////////////////////////////////////////////////////////////
+
type CmdCommit struct {
Ref string
Mark int // optional; < 1 for non-use
@@ -36,6 +42,59 @@ func (c CmdCommit) fiCmdWrite(fiw *textproto.FIWriter) error {
return ez.err
}
+func init() { parser_registerCmd("commit ", CmdCommit{}) }
+func (CmdCommit) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ ez := &ezfir{fir: fir}
+ defer func() { err = ez.Defer() }()
+
+ // 'commit' SP <ref> LF
+ c := CmdCommit{Ref: trimLinePrefix(ez.ReadLine(), "commit ")}
+
+ // mark?
+ if strings.HasPrefix(ez.PeekLine(), "mark :") {
+ c.Mark, err = strconv.Atoi(trimLinePrefix(ez.ReadLine(), "mark :"))
+ ez.Errcheck(err)
+ }
+
+ // ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
+ if strings.HasPrefix(ez.PeekLine(), "author ") {
+ author, err := textproto.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()))
+ }
+ c.Committer, err = textproto.ParseIdent(trimLinePrefix(ez.ReadLine(), "committer "))
+ ez.Errcheck(err)
+
+ // data
+ c.Msg, err = parse_data(ez.ReadLine())
+ ez.Errcheck(err)
+
+ // ('from' SP <commit-ish> LF)?
+ if strings.HasPrefix(ez.PeekLine(), "from ") {
+ c.From = trimLinePrefix(ez.ReadLine(), "from ")
+ }
+
+ // ('merge' SP <commit-ish> LF)*
+ for strings.HasPrefix(ez.PeekLine(), "merge ") {
+ c.Merge = append(c.Merge, trimLinePrefix(ez.ReadLine(), "merge "))
+ }
+
+ cmd = c
+ return
+}
+
+type CmdCommitEnd struct{}
+
+func (CmdCommitEnd) fiCmdClass() cmdClass { return cmdClassCommit }
+func (CmdCommitEnd) fiCmdWrite(fiw *textproto.FIWriter) error { return nil }
+func (CmdCommitEnd) fiCmdRead(fir fiReader) (Cmd, error) { panic("not reached") }
+
+// tag /////////////////////////////////////////////////////////////////////////
type CmdTag struct {
RefName string
@@ -55,6 +114,36 @@ func (c CmdTag) fiCmdWrite(fiw *textproto.FIWriter) error {
return ez.err
}
+func init() { parser_registerCmd("tag ", CmdTag{}) }
+func (CmdTag) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ ez := &ezfir{fir: fir}
+ defer func() { err = ez.Defer() }()
+
+ // 'tag' SP <name> LF
+ c := CmdTag{RefName: trimLinePrefix(ez.ReadLine(), "tag ")}
+
+ // 'from' SP <commit-ish> LF
+ if !strings.HasPrefix(ez.PeekLine(), "from ") {
+ ez.Errcheck(fmt.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()))
+ }
+ c.Tagger, err = textproto.ParseIdent(trimLinePrefix(ez.ReadLine(), "tagger "))
+ ez.Errcheck(err)
+
+ // data
+ c.Data, err = parse_data(ez.ReadLine())
+ ez.Errcheck(err)
+
+ cmd = c
+ return
+}
+
+// reset ///////////////////////////////////////////////////////////////////////
type CmdReset struct {
RefName string
@@ -72,6 +161,24 @@ func (c CmdReset) fiCmdWrite(fiw *textproto.FIWriter) error {
return ez.err
}
+func init() { parser_registerCmd("reset ", CmdReset{}) }
+func (CmdReset) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ ez := &ezfir{fir: fir}
+ defer func() { err = ez.Defer() }()
+
+ // 'reset' SP <ref> LF
+ c := CmdReset{RefName: trimLinePrefix(ez.ReadLine(), "reset ")}
+
+ // ('from' SP <commit-ish> LF)?
+ if strings.HasPrefix(ez.PeekLine(), "from ") {
+ c.CommitIsh = trimLinePrefix(ez.ReadLine(), "from ")
+ }
+
+ cmd = c
+ return
+}
+
+// blob ////////////////////////////////////////////////////////////////////////
type CmdBlob struct {
Mark int
@@ -90,6 +197,29 @@ func (c CmdBlob) fiCmdWrite(fiw *textproto.FIWriter) error {
return ez.err
}
+func init() { parser_registerCmd("blob\n", CmdBlob{}) }
+func (CmdBlob) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ ez := &ezfir{fir: fir}
+ defer func() { err = ez.Defer() }()
+
+ // 'blob' LF
+ _ = ez.ReadLine()
+ c := CmdBlob{}
+
+ // mark?
+ if strings.HasPrefix(ez.PeekLine(), "mark :") {
+ c.Mark, err = strconv.Atoi(trimLinePrefix(ez.ReadLine(), "mark :"))
+ }
+
+ // data
+ c.Data, err = parse_data(ez.ReadLine())
+ ez.Errcheck(err)
+
+ cmd = c
+ return
+}
+
+// checkpoint //////////////////////////////////////////////////////////////////
type CmdCheckpoint struct{}
@@ -97,6 +227,16 @@ func (c CmdCheckpoint) fiCmdClass() cmdClass { return cmdClassCommand }
func (c CmdCheckpoint) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("checkpoint")
}
+func init() { parser_registerCmd("checkpoint\n", CmdCheckpoint{}) }
+func (CmdCheckpoint) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ _, err = fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return CmdCheckpoint{}, nil
+}
+
+// progress ////////////////////////////////////////////////////////////////////
type CmdProgress struct {
Str string
@@ -106,6 +246,16 @@ func (c CmdProgress) fiCmdClass() cmdClass { return cmdClassCommand }
func (c CmdProgress) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("progress", c.Str)
}
+func init() { parser_registerCmd("progress ", CmdProgress{}) }
+func (CmdProgress) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return CmdProgress{Str: trimLinePrefix(line, "progress ")}, nil
+}
+
+// feature /////////////////////////////////////////////////////////////////////
type CmdFeature struct {
Feature string
@@ -120,6 +270,26 @@ func (c CmdFeature) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("feature", c.Feature)
}
}
+func init() { parser_registerCmd("feature ", CmdFeature{}) }
+func (CmdFeature) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ // 'feature' SP <feature> ('=' <argument>)? LF
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ str := trimLinePrefix(line, "feature ")
+ eq := strings.IndexByte(str, '=')
+ if eq < 0 {
+ return CmdFeature{Feature: str}, nil
+ } else {
+ return CmdFeature{
+ Feature: str[:eq],
+ Argument: str[eq+1:],
+ }, nil
+ }
+}
+
+// option //////////////////////////////////////////////////////////////////////
type CmdOption struct {
Option string
@@ -129,6 +299,17 @@ func (c CmdOption) fiCmdClass() cmdClass { return cmdClassCommand }
func (c CmdOption) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("option", c.Option)
}
+func init() { parser_registerCmd("option ", CmdOption{}) }
+func (CmdOption) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ // 'option' SP <option> LF
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return CmdOption{Option: trimLinePrefix(line, "option ")}, nil
+}
+
+// done ////////////////////////////////////////////////////////////////////////
type CmdDone struct{}
@@ -136,3 +317,11 @@ func (c CmdDone) fiCmdClass() cmdClass { return cmdClassCommand }
func (c CmdDone) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("done")
}
+func init() { parser_registerCmd("done\n", CmdDone{}) }
+func (CmdDone) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ _, err = fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return CmdDone{}, nil
+}
diff --git a/cmd_comment.go b/cmd_comment.go
index d783ecc..6ce801c 100644
--- a/cmd_comment.go
+++ b/cmd_comment.go
@@ -1,11 +1,15 @@
package libfastimport
import (
+ "fmt"
"strconv"
+ "strings"
"git.lukeshu.com/go/libfastimport/textproto"
)
+// comment /////////////////////////////////////////////////////////////////////
+
type CmdComment struct {
Comment string
}
@@ -14,6 +18,16 @@ func (c CmdComment) fiCmdClass() cmdClass { return cmdClassComment }
func (c CmdComment) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("#" + c.Comment)
}
+func init() { parser_registerCmd("#", CmdComment{}) }
+func (CmdComment) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return CmdComment{Comment: trimLinePrefix(line, "#")}, nil
+}
+
+// get-mark ////////////////////////////////////////////////////////////////////
type CmdGetMark struct {
Mark int
@@ -23,6 +37,21 @@ func (c CmdGetMark) fiCmdClass() cmdClass { return cmdClassComment }
func (c CmdGetMark) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("get-mark", ":"+strconv.Itoa(c.Mark))
}
+func init() { parser_registerCmd("get-mark :", CmdGetMark{}) }
+func (CmdGetMark) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ c := CmdGetMark{}
+ c.Mark, err = strconv.Atoi(trimLinePrefix(line, "get-mark :"))
+ if err != nil {
+ return nil, fmt.Errorf("get-mark: %v", err)
+ }
+ return c, nil
+}
+
+// cat-blob ////////////////////////////////////////////////////////////////////
type CmdCatBlob struct {
DataRef string
@@ -32,12 +61,30 @@ func (c CmdCatBlob) fiCmdClass() cmdClass { return cmdClassComment }
func (c CmdCatBlob) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("cat-blob", c.DataRef)
}
+func init() { parser_registerCmd("cat-blob ", CmdCatBlob{}) }
+func (CmdCatBlob) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return CmdCatBlob{DataRef: trimLinePrefix(line, "cat-blob ")}, nil
+}
+
+// ls //////////////////////////////////////////////////////////////////////////
type CmdLs struct {
DataRef string // optional if inside of a commit
Path textproto.Path
}
+// If you're thinking "but wait, parser_registerCmd will see CmdLs as
+// cmdClassCommit, not cmdClassComment, that means it won't be allowed
+// embedded inside other commands! (while still allowing it both
+// inside and outside of a commit)", you're absolutely correct.
+// That's the desired behavior. It's a happy accident that the little
+// fiCmdClass hack works out that way, instead of having to add even
+// more complexity.
+
func (c CmdLs) fiCmdClass() cmdClass {
if c.DataRef == "" {
return cmdClassCommit
@@ -51,4 +98,22 @@ func (c CmdLs) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("ls", c.DataRef, c.Path)
}
}
-
+func init() { parser_registerCmd("ls ", CmdLs{}) }
+func (CmdLs) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ // 'ls' SP <dataref> SP <path> LF
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ str := trimLinePrefix(line, "ls ")
+ sp := -1
+ if !strings.HasPrefix(str, "\"") {
+ sp = strings.IndexByte(line, ' ')
+ }
+ c := CmdLs{}
+ c.Path = textproto.PathUnescape(str[sp+1:])
+ if sp >= 0 {
+ c.DataRef = str[:sp]
+ }
+ return c, nil
+}
diff --git a/cmd_commit.go b/cmd_commit.go
index 5c882f8..a5dcb90 100644
--- a/cmd_commit.go
+++ b/cmd_commit.go
@@ -1,9 +1,15 @@
package libfastimport
import (
+ "fmt"
+ "strconv"
+ "strings"
+
"git.lukeshu.com/go/libfastimport/textproto"
)
+// M ///////////////////////////////////////////////////////////////////////////
+
type FileModify struct {
Mode textproto.Mode
Path textproto.Path
@@ -14,6 +20,47 @@ func (o FileModify) fiCmdClass() cmdClass { return cmdClassCommit }
func (o FileModify) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("M", o.Mode, o.DataRef, o.Path)
}
+func init() { parser_registerCmd("M ", FileModify{}) }
+func (FileModify) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ // NB: This parses both FileModify and FileModifyInline
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ str := trimLinePrefix(line, "M ")
+ sp1 := strings.IndexByte(str, ' ')
+ sp2 := strings.IndexByte(str[sp1+1:], ' ')
+ if sp1 < 0 || sp2 < 0 {
+ return nil, fmt.Errorf("commit: malformed modify command: %v", line)
+ }
+ nMode, err := strconv.ParseUint(str[:sp1], 8, 18)
+ if err != nil {
+ return nil, err
+ }
+ ref := str[sp1+1 : sp2]
+ path := textproto.PathUnescape(str[sp2+1:])
+ if ref == "inline" {
+ line, err = fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ data, err := parse_data(line)
+ if err != nil {
+ return nil, err
+ }
+ return FileModifyInline{
+ Mode: textproto.Mode(nMode),
+ Path: path,
+ Data: data,
+ }, nil
+ } else {
+ return FileModify{
+ Mode: textproto.Mode(nMode),
+ Path: path,
+ DataRef: ref,
+ }, nil
+ }
+}
type FileModifyInline struct {
Mode textproto.Mode
@@ -28,6 +75,9 @@ func (o FileModifyInline) fiCmdWrite(fiw *textproto.FIWriter) error {
ez.WriteData(o.Data)
return ez.err
}
+func (FileModifyInline) fiCmdRead(fiReader) (Cmd, error) { panic("not reached") }
+
+// D ///////////////////////////////////////////////////////////////////////////
type FileDelete struct {
Path textproto.Path
@@ -37,6 +87,16 @@ func (o FileDelete) fiCmdClass() cmdClass { return cmdClassCommit }
func (o FileDelete) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("D", o.Path)
}
+func init() { parser_registerCmd("D ", FileDelete{}) }
+func (FileDelete) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return FileDelete{Path: textproto.PathUnescape(trimLinePrefix(line, "D "))}, nil
+}
+
+// C ///////////////////////////////////////////////////////////////////////////
type FileCopy struct {
Src textproto.Path
@@ -47,6 +107,13 @@ func (o FileCopy) fiCmdClass() cmdClass { return cmdClassCommit }
func (o FileCopy) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("C", o.Src, o.Dst)
}
+func init() { parser_registerCmd("C ", FileDelete{}) }
+func (FileCopy) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ // BUG(lukeshu): TODO: commit C not implemented
+ panic("TODO: commit C not implemented")
+}
+
+// R ///////////////////////////////////////////////////////////////////////////
type FileRename struct {
Src string
@@ -57,6 +124,13 @@ func (o FileRename) fiCmdClass() cmdClass { return cmdClassCommit }
func (o FileRename) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("R", o.Src, o.Dst)
}
+func init() { parser_registerCmd("R ", FileDelete{}) }
+func (FileRename) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ // BUG(lukeshu): TODO: commit R not implemented
+ panic("TODO: commit R not implemented")
+}
+
+// deleteall ///////////////////////////////////////////////////////////////////
type FileDeleteAll struct{}
@@ -64,6 +138,16 @@ func (o FileDeleteAll) fiCmdClass() cmdClass { return cmdClassCommit }
func (o FileDeleteAll) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("deleteall")
}
+func init() { parser_registerCmd("deleteall\n", FileDeleteAll{}) }
+func (FileDeleteAll) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ _, err = fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ return FileDeleteAll{}, nil
+}
+
+// N ///////////////////////////////////////////////////////////////////////////
type NoteModify struct {
CommitIsh string
@@ -74,6 +158,40 @@ func (o NoteModify) fiCmdClass() cmdClass { return cmdClassCommit }
func (o NoteModify) fiCmdWrite(fiw *textproto.FIWriter) error {
return fiw.WriteLine("N", o.DataRef, o.CommitIsh)
}
+func init() { parser_registerCmd("N ", NoteModify{}) }
+func (NoteModify) fiCmdRead(fir fiReader) (cmd Cmd, err error) {
+ // NB: This parses both NoteModify and NoteModifyInline
+ line, err := fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ str := trimLinePrefix(line, "N ")
+ sp := strings.IndexByte(str, ' ')
+ if sp < 0 {
+ return nil, fmt.Errorf("commit: malformed notemodify command: %v", line)
+ }
+ ref := str[:sp]
+ commitish := str[sp+1:]
+ if ref == "inline" {
+ line, err = fir.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ data, err := parse_data(line)
+ if err != nil {
+ return nil, err
+ }
+ return NoteModifyInline{
+ CommitIsh: commitish,
+ Data: data,
+ }, nil
+ } else {
+ return NoteModify{
+ CommitIsh: commitish,
+ DataRef: ref,
+ }, nil
+ }
+}
type NoteModifyInline struct {
CommitIsh string
@@ -87,3 +205,4 @@ func (o NoteModifyInline) fiCmdWrite(fiw *textproto.FIWriter) error {
ez.WriteData(o.Data)
return ez.err
}
+func (NoteModifyInline) fiCmdRead(fiReader) (Cmd, error) { panic("not reached") }
diff --git a/ez.go b/ez.go
new file mode 100644
index 0000000..4c220b5
--- /dev/null
+++ b/ez.go
@@ -0,0 +1,65 @@
+package libfastimport
+
+import (
+ "strconv"
+
+ "git.lukeshu.com/go/libfastimport/textproto"
+)
+
+type ezfiw struct {
+ fiw *textproto.FIWriter
+ err error
+}
+
+func (e *ezfiw) WriteLine(a ...interface{}) {
+ if e.err == nil {
+ e.err = e.fiw.WriteLine(a...)
+ }
+}
+
+func (e *ezfiw) WriteData(data string) {
+ if e.err == nil {
+ e.err = e.fiw.WriteData(data)
+ }
+}
+
+func (e *ezfiw) WriteMark(idnum int) {
+ if e.err == nil {
+ e.err = e.fiw.WriteLine("mark", ":"+strconv.Itoa(idnum))
+ }
+}
+
+type ezfir struct {
+ fir fiReader
+ err error
+}
+
+func (e *ezfir) Defer() error {
+ if r := recover(); r != nil {
+ if e.err != nil {
+ return e.err
+ }
+ panic(r)
+ }
+ return nil
+}
+
+func (e *ezfir) Errcheck(err error) {
+ if err == nil {
+ return
+ }
+ e.err = err
+ panic("everything is fine")
+}
+
+func (e *ezfir) PeekLine() string {
+ line, err := e.fir.PeekLine()
+ e.Errcheck(err)
+ return line
+}
+
+func (e *ezfir) ReadLine() string {
+ line, err := e.fir.ReadLine()
+ e.Errcheck(err)
+ return line
+}
diff --git a/ezfiw.go b/ezfiw.go
deleted file mode 100644
index 2cc01b6..0000000
--- a/ezfiw.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package libfastimport
-
-import (
- "strconv"
-
- "git.lukeshu.com/go/libfastimport/textproto"
-)
-
-type ezfiw struct {
- fiw *textproto.FIWriter
- err error
-}
-
-func (e *ezfiw) WriteLine(a ...interface{}) {
- if e.err == nil {
- e.err = e.fiw.WriteLine(a...)
- }
-}
-
-func (e *ezfiw) WriteData(data string) {
- if e.err == nil {
- e.err = e.fiw.WriteData(data)
- }
-}
-
-func (e *ezfiw) WriteMark(idnum int) {
- if e.err == nil {
- e.err = e.fiw.WriteLine("mark", ":"+strconv.Itoa(idnum))
- }
-}
diff --git a/frontend.go b/frontend.go
index 604e7af..285bbd9 100644
--- a/frontend.go
+++ b/frontend.go
@@ -2,11 +2,8 @@ package libfastimport
import (
"bufio"
- "fmt"
"io"
"os"
- "strconv"
- "strings"
"git.lukeshu.com/go/libfastimport/textproto"
)
@@ -20,378 +17,60 @@ func (e UnsupportedCommand) Error() string {
// A Frontend is something that produces a fast-import stream; the
// Frontend object provides methods for reading from it.
type Frontend struct {
- fir *textproto.FIReader
- cbw *textproto.CatBlobWriter
- w *bufio.Writer
+ fastImport *parser
+ catBlobWrite *textproto.CatBlobWriter
+ catBlobFlush *bufio.Writer
- inCommit bool
-
- cmd chan Cmd
- err error
+ onErr func(error) error
}
func NewFrontend(fastImport io.Reader, catBlob io.Writer, onErr func(error) error) *Frontend {
ret := &Frontend{}
- ret.fir = textproto.NewFIReader(fastImport)
+
+ ret.fastImport = newParser(textproto.NewFIReader(fastImport))
+
if catBlob == nil {
catBlob = os.Stdout
}
- ret.w = bufio.NewWriter(catBlob)
- ret.cbw = textproto.NewCatBlobWriter(ret.w)
- ret.cmd = make(chan Cmd)
- go func() {
- ret.err = ret.parse()
- if onErr != nil {
- ret.err = onErr(ret.err)
- }
- close(ret.cmd)
- }()
- return ret
-}
-
-func (f *Frontend) nextLine() (line string, err error) {
- for {
- line, err = f.fir.ReadLine()
- if err != nil {
- return
- }
- switch {
- case strings.HasPrefix(line, "#"):
- f.cmd <- CmdComment{Comment: line[1:]}
- case strings.HasPrefix(line, "cat-blob "):
- // 'cat-blob' SP <dataref> LF
- f.cmd <- CmdCatBlob{DataRef: trimLinePrefix(line, "cat-blob ")}
- case strings.HasPrefix(line, "get-mark :"):
- // 'get-mark' SP ':' <idnum> LF
- c := CmdGetMark{}
- c.Mark, err = strconv.Atoi(trimLinePrefix(line, "get-mark :"))
- if err != nil {
- line = ""
- err = fmt.Errorf("get-mark: %v", err)
- return
- }
- f.cmd <- c
- default:
- return
- }
- }
-}
+ ret.catBlobFlush = bufio.NewWriter(catBlob)
+ ret.catBlobWrite = textproto.NewCatBlobWriter(ret.catBlobFlush)
-func (f *Frontend) parse() error {
- line, err := f.nextLine()
- if err != nil {
- return err
+ if onErr == nil {
+ onErr = func(e error) error { return e }
}
- for {
- switch {
- case line == "blob\n":
- // 'blob' LF
- // mark?
- // data
- c := CmdBlob{}
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if strings.HasPrefix(line, "mark :") {
- c.Mark, err = strconv.Atoi(trimLinePrefix(line, "mark :"))
- if err != nil {
- return err
- }
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- }
- if !strings.HasPrefix(line, "data ") {
- return fmt.Errorf("Unexpected command in blob: %q", line)
- }
- c.Data, err = parse_data(line)
- if err != nil {
- return err
- }
- f.cmd <- c
- case line == "checkpoint\n":
- f.cmd <- CmdCheckpoint{}
- case line == "done\n":
- f.cmd <- CmdDone{}
- case strings.HasPrefix(line, "commit "):
- // 'commit' SP <ref> LF
- // mark?
- // ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
- // 'committer' (SP <name>)? SP LT <email> GT SP <when> LF
- // data
- // ('from' SP <commit-ish> LF)?
- // ('merge' SP <commit-ish> LF)*
- // (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)*
- c := CmdCommit{Ref: trimLinePrefix(line, "commit ")}
+ ret.onErr = onErr
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if strings.HasPrefix(line, "mark :") {
- c.Mark, err = strconv.Atoi(trimLinePrefix(line, "mark :"))
- if err != nil {
- return err
- }
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- }
- if strings.HasPrefix(line, "author ") {
- author, err := textproto.ParseIdent(trimLinePrefix(line, "author "))
- if err != nil {
- return err
- }
- c.Author = &author
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- }
- if !strings.HasPrefix(line, "committer ") {
- return fmt.Errorf("commit: expected committer command: %v", line)
- }
- c.Committer, err = textproto.ParseIdent(trimLinePrefix(line, "committer "))
- if err != nil {
- return err
- }
-
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if !strings.HasPrefix(line, "data ") {
- return fmt.Errorf("commit: expected data command: %v", line)
- }
- c.Msg, err = parse_data(line)
- if err != nil {
- return err
- }
-
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if strings.HasPrefix(line, "from ") {
- c.From = trimLinePrefix(line, "from ")
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- }
- for strings.HasPrefix(line, "merge ") {
- c.Merge = append(c.Merge, trimLinePrefix(line, "merge "))
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- }
- f.cmd <- c
- case strings.HasPrefix(line, "M "):
- fmt.Printf("line: %q\n", line)
- str := trimLinePrefix(line, "M ")
- sp1 := strings.IndexByte(str, ' ')
- sp2 := strings.IndexByte(str[sp1+1:], ' ')
- if sp1 < 0 || sp2 < 0 {
- return fmt.Errorf("commit: malformed modify command: %v", line)
- }
- nMode, err := strconv.ParseUint(str[:sp1], 8, 18)
- if err != nil {
- return err
- }
- ref := str[sp1+1 : sp2]
- path := textproto.PathUnescape(str[sp2+1:])
- if ref == "inline" {
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if !strings.HasPrefix(line, "data ") {
- return fmt.Errorf("commit: modify: expected data command: %v", line)
- }
- data, err := parse_data(line)
- if err != nil {
- return err
- }
- f.cmd <- FileModifyInline{Mode: textproto.Mode(nMode), Path: path, Data: data}
- } else {
- f.cmd <- FileModify{Mode: textproto.Mode(nMode), Path: path, DataRef: ref}
- }
- case strings.HasPrefix(line, "D "):
- f.cmd <- FileDelete{Path: textproto.PathUnescape(trimLinePrefix(line, "D "))}
- case strings.HasPrefix(line, "C "):
- // BUG(lukeshu): TODO: commit C not implemented
- panic("TODO: commit C not implemented")
- case strings.HasPrefix(line, "R "):
- // BUG(lukeshu): TODO: commit R not implemented
- panic("TODO: commit R not implemented")
- case strings.HasPrefix(line, "N "):
- str := trimLinePrefix(line, "N ")
- sp := strings.IndexByte(str, ' ')
- if sp < 0 {
- return fmt.Errorf("commit: malformed notemodify command: %v", line)
- }
- ref := str[:sp]
- commitish := str[sp+1:]
- if ref == "inline" {
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if !strings.HasPrefix(line, "data ") {
- return fmt.Errorf("commit: notemodify: expected data command: %v", line)
- }
- data, err := parse_data(line)
- if err != nil {
- return err
- }
- f.cmd <- NoteModifyInline{CommitIsh: commitish, Data: data}
- } else {
- f.cmd <- NoteModify{CommitIsh: commitish, DataRef: ref}
- }
- case line == "deleteall\n":
- f.cmd <- FileDeleteAll{}
- case strings.HasPrefix(line, "feature "):
- // 'feature' SP <feature> ('=' <argument>)? LF
- str := trimLinePrefix(line, "feature ")
- eq := strings.IndexByte(str, '=')
- if eq < 0 {
- f.cmd <- CmdFeature{
- Feature: str,
- }
- } else {
- f.cmd <- CmdFeature{
- Feature: str[:eq],
- Argument: str[eq+1:],
- }
- }
- case strings.HasPrefix(line, "ls "):
- // 'ls' SP <dataref> SP <path> LF
- str := trimLinePrefix(line, "ls ")
- sp := -1
- if !strings.HasPrefix(str, "\"") {
- sp = strings.IndexByte(line, ' ')
- }
- c := CmdLs{}
- c.Path = textproto.PathUnescape(str[sp+1:])
- if sp >= 0 {
- c.DataRef = str[:sp]
- }
- f.cmd <- c
- case strings.HasPrefix(line, "option "):
- // 'option' SP <option> LF
- f.cmd <- CmdOption{Option: trimLinePrefix(line, "option ")}
- case strings.HasPrefix(line, "progress "):
- // 'progress' SP <any> LF
- f.cmd <- CmdProgress{Str: trimLinePrefix(line, "progress ")}
- case strings.HasPrefix(line, "reset "):
- // 'reset' SP <ref> LF
- // ('from' SP <commit-ish> LF)?
- c := CmdReset{RefName: trimLinePrefix(line, "reset ")}
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if strings.HasPrefix(line, "from ") {
- c.CommitIsh = trimLinePrefix(line, "from ")
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- }
- f.cmd <- c
- continue
- case strings.HasPrefix(line, "tag "):
- // 'tag' SP <name> LF
- // 'from' SP <commit-ish> LF
- // 'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
- // data
- c := CmdTag{RefName: trimLinePrefix(line, "tag ")}
-
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if !strings.HasPrefix(line, "from ") {
- return fmt.Errorf("tag: expected from command: %v", line)
- }
- c.CommitIsh = trimLinePrefix(line, "from ")
-
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- if !strings.HasPrefix(line, "tagger ") {
- return fmt.Errorf("tag: expected tagger command: %v", line)
- }
- c.Tagger, err = textproto.ParseIdent(trimLinePrefix(line, "tagger "))
- if err != nil {
- return err
- }
-
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- c.Data, err = parse_data(line)
- if err != nil {
- return err
- }
- f.cmd <- c
- default:
- return UnsupportedCommand(line)
- }
- line, err = f.nextLine()
- if err != nil {
- return err
- }
- }
+ return ret
}
func (f *Frontend) ReadCmd() (Cmd, error) {
- cmd, ok := <-f.cmd
- if ok {
- switch cmd.fiCmdClass() {
- case cmdClassCommand:
- _, f.inCommit = cmd.(CmdCommit)
- case cmdClassCommit:
- if !f.inCommit {
- // BUG(lukeshu): idk what to do here
- panic("oops")
- }
- case cmdClassComment:
- /* do nothing */
- default:
- panic(fmt.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
- }
- return cmd, nil
+ cmd, err := f.fastImport.ReadCmd()
+ if err != nil {
+ err = f.onErr(err)
}
- return nil, f.err
+ return cmd, err
}
func (f *Frontend) RespondGetMark(sha1 string) error {
- err := f.cbw.WriteLine(sha1)
+ err := f.catBlobWrite.WriteLine(sha1)
if err != nil {
return err
}
- return f.w.Flush()
+ return f.catBlobFlush.Flush()
}
func (f *Frontend) RespondCatBlob(sha1 string, data string) error {
- err := f.cbw.WriteBlob(sha1, data)
+ err := f.catBlobWrite.WriteBlob(sha1, data)
if err != nil {
return err
}
- return f.w.Flush()
+ return f.catBlobFlush.Flush()
}
func (f *Frontend) RespondLs(mode textproto.Mode, dataref string, path textproto.Path) error {
var err error
if mode == 0 {
- err = f.cbw.WriteLine("missing", path)
+ err = f.catBlobWrite.WriteLine("missing", path)
} else {
var t string
switch mode {
@@ -402,10 +81,10 @@ func (f *Frontend) RespondLs(mode textproto.Mode, dataref string, path textproto
default:
t = "blob"
}
- err = f.cbw.WriteLine(mode, t, dataref+"\t"+textproto.PathEscape(path))
+ err = f.catBlobWrite.WriteLine(mode, t, dataref+"\t"+textproto.PathEscape(path))
}
if err != nil {
return err
}
- return f.w.Flush()
+ return f.catBlobFlush.Flush()
}
diff --git a/parse_fastimport.go b/parse_fastimport.go
new file mode 100644
index 0000000..80488de
--- /dev/null
+++ b/parse_fastimport.go
@@ -0,0 +1,162 @@
+package libfastimport
+
+import (
+ "fmt"
+ "strings"
+
+ "git.lukeshu.com/go/libfastimport/textproto"
+)
+
+var parser_regularCmds = make(map[string]Cmd)
+var parser_commentCmds = make(map[string]Cmd)
+
+func parser_registerCmd(prefix string, cmd Cmd) {
+ switch cmd.fiCmdClass() {
+ case cmdClassCommand, cmdClassCommit:
+ parser_regularCmds[prefix] = cmd
+ case cmdClassComment:
+ parser_commentCmds[prefix] = cmd
+ default:
+ panic(fmt.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
+ }
+}
+
+var parser_regular func(line string) func(fiReader) (Cmd, error)
+var parser_comment func(line string) func(fiReader) (Cmd, error)
+
+func parser_compile(cmds map[string]Cmd) func(line string) func(fiReader) (Cmd, error) {
+ // This assumes that 2 characters is enough to uniquely
+ // identify a command, and that "#" is the only one-chara
+ ch2map := make(map[string]string, len(cmds))
+ for prefix := range cmds {
+ var ch2 string
+ if len(prefix) < 2 {
+ if prefix != "#" {
+ panic("the assumptions of parser_compile are invalid, it must be rewritten")
+ }
+ ch2 = prefix
+ } else {
+ ch2 = prefix[:2]
+ }
+ if _, dup := ch2map[ch2]; dup {
+ panic("the assumptions of parser_compile are invalid, it must be rewritten")
+ }
+ ch2map[ch2] = prefix
+ }
+ return func(line string) func(fiReader) (Cmd, error) {
+ n := 2
+ if strings.HasPrefix(line, "#") {
+ n = 1
+ }
+ if len(line) < n {
+ return nil
+ }
+ prototype := cmds[ch2map[line[:n]]]
+ if prototype == nil {
+ return nil
+ }
+ return prototype.fiCmdRead
+ }
+}
+
+type parser struct {
+ fir *textproto.FIReader
+
+ inCommit bool
+
+ buf_line *string
+ buf_err error
+
+ ret_cmd chan Cmd
+ ret_err error
+}
+
+func newParser(fir *textproto.FIReader) *parser {
+ if parser_regular == nil {
+ parser_regular = parser_compile(parser_regularCmds)
+ }
+ if parser_comment == nil {
+ parser_comment = parser_compile(parser_commentCmds)
+ }
+
+ ret := &parser{
+ fir: fir,
+ ret_cmd: make(chan Cmd),
+ }
+ go func() {
+ ret.ret_err = ret.parse()
+ close(ret.ret_cmd)
+ }()
+ return ret
+}
+
+func (p *parser) ReadCmd() (Cmd, error) {
+ cmd, ok := <-p.ret_cmd
+ if !ok {
+ return nil, p.ret_err
+ }
+ return cmd, nil
+}
+
+func (p *parser) parse() error {
+ for {
+ line, err := p.PeekLine()
+ if err != nil {
+ return err
+ }
+ subparser := parser_regular(line)
+ if subparser == nil {
+ return UnsupportedCommand(line)
+ }
+ cmd, err := subparser(p)
+ if err != nil {
+ return err
+ }
+
+ switch cmd.fiCmdClass() {
+ case cmdClassCommand:
+ if p.inCommit {
+ p.ret_cmd <- CmdCommitEnd{}
+ }
+ _, 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)
+ }
+ case cmdClassComment:
+ /* do nothing */
+ default:
+ panic(fmt.Errorf("invalid cmdClass: %d", cmd.fiCmdClass()))
+ }
+
+ p.ret_cmd <- cmd
+ }
+}
+
+func (p *parser) PeekLine() (string, error) {
+ for p.buf_line == nil && p.buf_err == nil {
+ var line string
+ line, p.buf_err = p.fir.ReadLine()
+ p.buf_line = &line
+ if p.buf_err != nil {
+ return *p.buf_line, p.buf_err
+ }
+ subparser := parser_comment(line)
+ if subparser != nil {
+ var cmd Cmd
+ cmd, p.buf_err = subparser(p)
+ if p.buf_err != nil {
+ return "", p.buf_err
+ }
+ p.ret_cmd <- cmd
+ }
+ }
+ return *p.buf_line, p.buf_err
+}
+
+func (p *parser) ReadLine() (string, error) {
+ line, err := p.PeekLine()
+ p.buf_line = nil
+ p.buf_err = nil
+ return line, err
+}
diff --git a/textproto/io.go b/textproto/io.go
index 8350021..09f36d7 100644
--- a/textproto/io.go
+++ b/textproto/io.go
@@ -10,6 +10,9 @@ import (
type FIReader struct {
r *bufio.Reader
+
+ line *string
+ err error
}
func NewFIReader(r io.Reader) *FIReader {