diff options
-rw-r--r-- | cmd.go | 6 | ||||
-rw-r--r-- | cmd_command.go | 189 | ||||
-rw-r--r-- | cmd_comment.go | 67 | ||||
-rw-r--r-- | cmd_commit.go | 119 | ||||
-rw-r--r-- | ez.go | 65 | ||||
-rw-r--r-- | ezfiw.go | 30 | ||||
-rw-r--r-- | frontend.go | 369 | ||||
-rw-r--r-- | parse_fastimport.go | 162 | ||||
-rw-r--r-- | textproto/io.go | 3 |
9 files changed, 634 insertions, 376 deletions
@@ -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") } @@ -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 { |