diff options
-rw-r--r-- | commands.go | 38 | ||||
-rw-r--r-- | ezfiw.go | 4 | ||||
-rw-r--r-- | fileactions.go | 40 | ||||
-rw-r--r-- | frontend.go | 68 | ||||
-rw-r--r-- | parse_catblob.go | 97 | ||||
-rw-r--r-- | read_catblob.go | 69 | ||||
-rw-r--r-- | read_fastimport.go | 28 | ||||
-rw-r--r-- | textproto/io.go (renamed from io.go) | 52 | ||||
-rw-r--r-- | textproto/types.go (renamed from types.go) | 18 |
9 files changed, 258 insertions, 156 deletions
diff --git a/commands.go b/commands.go index a1f7f51..d017f7f 100644 --- a/commands.go +++ b/commands.go @@ -2,23 +2,25 @@ package libfastimport import ( "strconv" + + "git.lukeshu.com/go/libfastimport/textproto" ) type Cmd interface { - fiWriteCmd(*FIWriter) error + fiWriteCmd(*textproto.FIWriter) error } type CmdCommit struct { Ref string Mark int // optional; < 1 for non-use - Author *UserTime - Committer UserTime + Author *textproto.UserTime + Committer textproto.UserTime Msg []byte Parents []string Tree []FileAction } -func (c *CmdCommit) fiWriteCmd(fiw *FIWriter) error { +func (c CmdCommit) fiWriteCmd(fiw *textproto.FIWriter) error { ez := &ezfiw{fiw: fiw} ez.WriteLine("commit", c.Ref) @@ -56,11 +58,11 @@ func (c *CmdCommit) fiWriteCmd(fiw *FIWriter) error { type CmdTag struct { RefName string CommitIsh string - Tagger UserTime + Tagger textproto.UserTime Data []byte } -func (c *CmdTag) fiWriteCmd(fiw *FIWriter) error { +func (c CmdTag) fiWriteCmd(fiw *textproto.FIWriter) error { ez := &ezfiw{fiw: fiw} ez.WriteLine("tag", c.RefName) @@ -76,7 +78,7 @@ type CmdReset struct { CommitIsh string // optional } -func (c *CmdReset) fiWriteCmd(fiw *FIWriter) error { +func (c CmdReset) fiWriteCmd(fiw *textproto.FIWriter) error { ez := &ezfiw{fiw: fiw} ez.WriteLine("reset", c.RefName) @@ -92,7 +94,7 @@ type CmdBlob struct { Data []byte } -func (c *CmdBlob) fiWriteCmd(fiw *FIWriter) error { +func (c CmdBlob) fiWriteCmd(fiw *textproto.FIWriter) error { ez := &ezfiw{fiw: fiw} ez.WriteLine("blob") @@ -106,7 +108,7 @@ func (c *CmdBlob) fiWriteCmd(fiw *FIWriter) error { type CmdCheckpoint struct{} -func (c *CmdCheckpoint) fiWriteCmd(fiw *FIWriter) error { +func (c CmdCheckpoint) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("checkpoint") } @@ -114,7 +116,7 @@ type CmdProgress struct { Str string } -func (c *CmdProgress) fiWriteCmd(fiw *FIWriter) error { +func (c CmdProgress) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("progress", c.Str) } @@ -122,7 +124,7 @@ type CmdGetMark struct { Mark int } -func (c *CmdGetMark) fiWriteCmd(fiw *FIWriter) error { +func (c CmdGetMark) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("get-mark", ":"+strconv.Itoa(c.Mark)) } @@ -130,17 +132,17 @@ type CmdCatBlob struct { DataRef string } -func (c *CmdCatBlob) fiWriteCmd(fiw *FIWriter) error { +func (c CmdCatBlob) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("cat-blob", c.DataRef) } // See FileLs for using ls inside of a commit type CmdLs struct { DataRef string - Path Path + Path textproto.Path } -func (c *CmdLs) fiWriteCmd(fiw *FIWriter) error { +func (c CmdLs) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("ls", c.DataRef, c.Path) } @@ -149,7 +151,7 @@ type CmdFeature struct { Argument string } -func (c *CmdFeature) fiWriteCmd(fiw *FIWriter) error { +func (c CmdFeature) fiWriteCmd(fiw *textproto.FIWriter) error { if c.Argument != "" { return fiw.WriteLine("feature", c.Feature+"="+c.Argument) } else { @@ -161,13 +163,13 @@ type CmdOption struct { Option string } -func (c *CmdOption) fiWriteCmd(fiw *FIWriter) error { +func (c CmdOption) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("option", c.Option) } type CmdDone struct{} -func (c *CmdDone) fiWriteCmd(fiw *FIWriter) error { +func (c CmdDone) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("done") } @@ -175,6 +177,6 @@ type CmdComment struct { Comment string } -func (c *CmdComment) fiWriteCmd(fiw *FIWriter) error { +func (c CmdComment) fiWriteCmd(fiw *textproto.FIWriter) error { return fiw.WriteLine("#" + c.Comment) } @@ -2,10 +2,12 @@ package libfastimport import ( "strconv" + + "git.lukeshu.com/go/libfastimport/textproto" ) type ezfiw struct { - fiw *FIWriter + fiw *textproto.FIWriter err error } diff --git a/fileactions.go b/fileactions.go index 2d81177..d45b982 100644 --- a/fileactions.go +++ b/fileactions.go @@ -1,26 +1,30 @@ package libfastimport +import ( + "git.lukeshu.com/go/libfastimport/textproto" +) + type FileAction interface { - fiWriteFA(*FIWriter) error + fiWriteFA(*textproto.FIWriter) error } type FileModify struct { - Mode Mode - Path Path + Mode textproto.Mode + Path textproto.Path DataRef string } -func (o FileModify) fiWriteFA(fiw *FIWriter) error { +func (o FileModify) fiWriteFA(fiw *textproto.FIWriter) error { return fiw.WriteLine("M", o.Mode, o.DataRef, o.Path) } type FileModifyInline struct { - Mode Mode - Path Path + Mode textproto.Mode + Path textproto.Path Data []byte } -func (o FileModifyInline) fiWriteFA(fiw *FIWriter) error { +func (o FileModifyInline) fiWriteFA(fiw *textproto.FIWriter) error { ez := &ezfiw{fiw: fiw} ez.WriteLine("M", o.Mode, "inline", o.Path) ez.WriteData(o.Data) @@ -28,19 +32,19 @@ func (o FileModifyInline) fiWriteFA(fiw *FIWriter) error { } type FileDelete struct { - Path Path + Path textproto.Path } -func (o FileDelete) fiWriteFA(fiw *FIWriter) error { +func (o FileDelete) fiWriteFA(fiw *textproto.FIWriter) error { return fiw.WriteLine("D", o.Path) } type FileCopy struct { - Src Path - Dst Path + Src textproto.Path + Dst textproto.Path } -func (o FileCopy) fiWriteFA(fiw *FIWriter) error { +func (o FileCopy) fiWriteFA(fiw *textproto.FIWriter) error { return fiw.WriteLine("C", o.Src, o.Dst) } @@ -49,13 +53,13 @@ type FileRename struct { Dst string } -func (o FileRename) fiWriteFA(fiw *FIWriter) error { +func (o FileRename) fiWriteFA(fiw *textproto.FIWriter) error { return fiw.WriteLine("R", o.Src, o.Dst) } type FileDeleteAll struct{} -func (o FileDeleteAll) fiWriteFA(fiw *FIWriter) error { +func (o FileDeleteAll) fiWriteFA(fiw *textproto.FIWriter) error { return fiw.WriteLine("deleteall") } @@ -64,7 +68,7 @@ type NoteModify struct { DataRef string } -func (o NoteModify) fiWriteFA(fiw *FIWriter) error { +func (o NoteModify) fiWriteFA(fiw *textproto.FIWriter) error { return fiw.WriteLine("N", o.DataRef, o.CommitIsh) } @@ -73,7 +77,7 @@ type NoteModifyInline struct { Data []byte } -func (o NoteModifyInline) fiWriteFA(fiw *FIWriter) error { +func (o NoteModifyInline) fiWriteFA(fiw *textproto.FIWriter) error { ez := &ezfiw{fiw: fiw} ez.WriteLine("N", "inline", o.CommitIsh) ez.WriteData(o.Data) @@ -82,9 +86,9 @@ func (o NoteModifyInline) fiWriteFA(fiw *FIWriter) error { // See CmdLs for using ls outside of a commit type FileLs struct { - Path Path + Path textproto.Path } -func (o FileLs) fiWriteFA(fiw *FIWriter) error { +func (o FileLs) fiWriteFA(fiw *textproto.FIWriter) error { return fiw.WriteLine("ls", o.Path) } diff --git a/frontend.go b/frontend.go new file mode 100644 index 0000000..73f6d73 --- /dev/null +++ b/frontend.go @@ -0,0 +1,68 @@ +package libfastimport + +import ( + "io" + "bufio" + + "git.lukeshu.com/go/libfastimport/textproto" +) + +type Frontend struct { + w *bufio.Writer + fiw *textproto.FIWriter + cbr *textproto.CatBlobReader +} + +func NewFrontend(w io.Writer, r io.Reader) *Frontend { + ret := Frontend{} + ret.w = bufio.NewWriter(w) + ret.fiw = textproto.NewFIWriter(ret.w) + if r != nil { + ret.cbr = textproto.NewCatBlobReader(r) + } + return &ret +} + +func (f *Frontend) Do(cmd Cmd) error { + err := cmd.fiWriteCmd(f.fiw) + if err != nil { + return err + } + return f.w.Flush() +} + +func (f *Frontend) GetMark(cmd CmdGetMark) (string, error) { + err := f.Do(cmd) + if err != nil { + return "", err + } + line, err := f.cbr.ReadLine() + if err != nil { + return "", err + } + return cbpGetMark(line) +} + +func (f *Frontend) CatBlob(cmd CmdCatBlob) (sha1 string, data string, err error) { + err = f.Do(cmd) + if err != nil { + return "", "", err + } + line, err := f.cbr.ReadLine() + if err != nil { + return "", "", err + } + return cbpCatBlob(line) +} + +func (f *Frontend) Ls(cmd CmdLs) (mode textproto.Mode, dataref string, path textproto.Path, err error) { + err = f.Do(cmd) + if err != nil { + return 0, "", "", err + } + line, err := f.cbr.ReadLine() + if err != nil { + return 0, "", "", err + } + return cbpLs(line) +} diff --git a/parse_catblob.go b/parse_catblob.go new file mode 100644 index 0000000..53f7d52 --- /dev/null +++ b/parse_catblob.go @@ -0,0 +1,97 @@ +package libfastimport + +import ( + "fmt" + "strings" + "strconv" + + "git.lukeshu.com/go/libfastimport/textproto" +) + +func cbpGetMark(line string) (string, error) { + if len(line) != 41 { + return "", fmt.Errorf("get-mark: short <sha1>\\n: %q", line) + } + if line[40] != '\n' { + return "", fmt.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 line[:40], nil +} + +func cbpCatBlob(full string) (sha1 string, data string, err error) { + // The format is: + // + // <sha1> SP 'blob' SP <size> LF + // <data> LF + + if full[len(full)-1] != '\n' { + return "", "", fmt.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) + } + 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) + } + + 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) + } + } + + if string(head[40:46]) != " blob " { + return "", "", fmt.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) + } + + if size != len(data) { + return "", "", fmt.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) { + // <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") + } + if strings.HasPrefix(line, "missing ") { + strPath := line[8:len(line)-1] + return 0, "", textproto.PathUnescape(strPath), nil + } else { + sp1 := strings.IndexByte(line, ' ') + sp2 := strings.IndexByte(line[sp1+1:], ' ') + ht := strings.IndexByte(line[sp2+1:], '\t') + if sp1 < 0 || sp2 < 0 || ht < 0 { + return 0, "", "", fmt.Errorf("ls: malformed line: %q", line) + } + strMode := line[:sp1] + strRef := line[sp2+1:ht] + strPath := line[ht+1:len(line)-1] + + nMode, err := strconv.ParseUint(strMode, 8, 18) + if err != nil { + return 0, "", "", err + } + return textproto.Mode(nMode), strRef, textproto.PathUnescape(strPath), nil + } +} diff --git a/read_catblob.go b/read_catblob.go deleted file mode 100644 index ed5cb14..0000000 --- a/read_catblob.go +++ /dev/null @@ -1,69 +0,0 @@ -package libfastimport - -import ( - "fmt" - "bytes" - "strconv" -) - -func CatBlobParseGetMark(dat []byte) (string, error) { - if len(dat) != 41 { - return "", fmt.Errorf("get-mark: short <sha1>\\n: %q", string(dat)) - } - if dat[40] != '\n' { - return "", fmt.Errorf("get-mark: malformed <sha1>\\n: %q", string(dat)) - } - for _, b := range dat[:40] { - if !(('0' <= b && b <= '9') || ('a' <= b && b <= 'f')) { - return "", fmt.Errorf("get-mark: malformed <sha1>: %q", string(dat[:40])) - } - } - return string(dat[:40]), nil -} - -func CatBlobParseCatBlob(full []byte) (sha1 string, data []byte, err error) { - // The format is: - // - // <sha1> SP 'blob' SP <size> LF - // <data> LF - - lf := bytes.IndexByte(full, '\n') - if lf < 0 || lf == len(full)-1 { - return "", nil, fmt.Errorf("cat-blob: malformed header: %q", string(full)) - } - head := full[:lf] - data = full[lf+1:len(full)-1] - - if len(head) < 40+6+1 { - return "", nil, fmt.Errorf("cat-blob: malformed header: %q", string(head)) - } - - sha1 = string(head[:40]) - for _, b := range sha1 { - if !(('0' <= b && b <= '9') || ('a' <= b && b <= 'f')) { - return "", nil, fmt.Errorf("cat-blob: malformed <sha1>: %q", sha1) - } - } - - if string(head[40:46]) != " blob " { - return "", nil, fmt.Errorf("cat-blob: malformed header: %q", head) - } - - size, err := strconv.Atoi(string(head[46:])) - if err != nil { - return "", nil, fmt.Errorf("cat-blob: malformed blob size: %v", err) - } - - if size != len(data) { - return "", nil, fmt.Errorf("cat-blob: size header (%d) didn't match delivered size (%d)", size, len(data)) - } - - return sha1, data, err -} - -func CatBlobParseLs(dataref string, path string) error { - // <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF - // or - // 'missing' SP <path> LF - return nil // TODO -} diff --git a/read_fastimport.go b/read_fastimport.go index ea10885..abafa4b 100644 --- a/read_fastimport.go +++ b/read_fastimport.go @@ -1,5 +1,9 @@ package libfastimport +import ( + "git.lukeshu.com/go/libfastimport/textproto" +) + type UnsupportedCommand string func (e UnsupportedCommand) Error() string { @@ -7,18 +11,18 @@ func (e UnsupportedCommand) Error() string { } type Parser struct { - fir *FIReader + fir *textproto.FIReader cmd chan Cmd } func (p *Parser) GetCmd() (Cmd, error) { for p.cmd == nil { - slice, err := p.fir.ReadSlice() + line, err := p.fir.ReadLine() if err != nil { return nil, err } - err = p.putSlice(slice) + err = p.putLine(line) if err != nil { return nil, err } @@ -26,23 +30,23 @@ func (p *Parser) GetCmd() (Cmd, error) { return <-p.cmd, nil } -func (p *Parser) putSlice(slice []byte) error { - if len(slice) < 1 { - return UnsupportedCommand(slice) +func (p *Parser) putLine(line string) error { + if len(line) < 1 { + return UnsupportedCommand(line) } - switch slice[0] { + switch line[0] { case '#': // comment case 'b': // blob case 'c': - if len(slice) < 2 { - return UnsupportedCommand(slice) + if len(line) < 2 { + return UnsupportedCommand(line) } - switch slice[1] { + switch line[1] { case 'o': // commit case 'h': // checkpoint case 'a': // cat-blob default: - return UnsupportedCommand(slice) + return UnsupportedCommand(line) } case 'd': // done case 'f': // feature @@ -53,7 +57,7 @@ func (p *Parser) putSlice(slice []byte) error { case 'r': // reset case 't': // tag default: - return UnsupportedCommand(slice) + return UnsupportedCommand(line) } return nil // TODO } @@ -1,11 +1,11 @@ -package libfastimport +package textproto import ( - "fmt" "bufio" - "bytes" + "fmt" "io" "strconv" + "strings" ) type FIReader struct { @@ -18,9 +18,9 @@ func NewFIReader(r io.Reader) *FIReader { } } -func (fir *FIReader) ReadSlice() (line []byte, err error) { +func (fir *FIReader) ReadLine() (line string, err error) { retry: - line, err = fir.r.ReadSlice('\n') + line, err = fir.r.ReadString('\n') if err != nil { return } @@ -28,19 +28,16 @@ retry: goto retry } - if bytes.HasPrefix(line, []byte("data ")) { - if string(line[5:7]) == "<<" { + if strings.HasPrefix(line, "data ") { + if line[5:7] == "<<" { // Delimited format delim := line[7 : len(line)-1] - suffix := []byte("\n" + string(delim) + "\n") - - _line := make([]byte, len(line)) - copy(_line, line) - line = _line + suffix := "\n" + delim + "\n" - for !bytes.HasSuffix(line, suffix) { - _line, err = fir.r.ReadSlice('\n') - line = append(line, _line...) + for !strings.HasSuffix(line, suffix) { + var _line string + _line, err = fir.r.ReadString('\n') + line += _line if err != nil { return } @@ -48,15 +45,13 @@ retry: } else { // Exact byte count format var size int - size, err = strconv.Atoi(string(line[5 : len(line)-1])) + size, err = strconv.Atoi(line[5 : len(line)-1]) if err != nil { return } - _line := make([]byte, size+len(line)) - copy(_line, line) - var n int - n, err = io.ReadFull(fir.r, _line[len(line):]) - line = _line[:n+len(line)] + data := make([]byte, size) + _, err = io.ReadFull(fir.r, data) + line += string(data) } } return @@ -96,9 +91,9 @@ func NewCatBlobReader(r io.Reader) *CatBlobReader { } } -func (cbr *CatBlobReader) ReadSlice() (line []byte, err error) { +func (cbr *CatBlobReader) ReadLine() (line string, err error) { retry: - line, err = cbr.r.ReadSlice('\n') + line, err = cbr.r.ReadString('\n') if err != nil { return } @@ -112,7 +107,7 @@ retry: // ls : 'missing' SP <path> LF // decide if we have a cat-blob result - if len(line) <= 46 || string(line[40:46]) != " blob " { + if len(line) <= 46 || line[40:46] != " blob " { return } for _, b := range line[:40] { @@ -122,14 +117,13 @@ retry: } // we have a cat-blob result var size int - size, err = strconv.Atoi(string(line[46 : len(line)-1])) + size, err = strconv.Atoi(line[46 : len(line)-1]) if err != nil { return } - _line := make([]byte, len(line)+size+1) - copy(_line, line) - n, err := io.ReadFull(cbr.r, _line[len(line):]) - line = _line[:n+len(line)] + data := make([]byte, size+1) + _, err = io.ReadFull(cbr.r, data) + line += string(data[:size]) return } diff --git a/types.go b/textproto/types.go index 1ce0741..2109517 100644 --- a/types.go +++ b/textproto/types.go @@ -1,4 +1,4 @@ -package libfastimport +package textproto import ( "fmt" @@ -42,24 +42,24 @@ func (m Mode) String() string { return fmt.Sprintf("%06o", m) } -func PathEscape(path string) string { - if strings.HasPrefix(path, "\"") || strings.ContainsRune(path, '\n') { - return "\"" + strings.Replace(strings.Replace(strings.Replace(path, "\\", "\\\\", -1), "\"", "\\\"", -1), "\n", "\\n", -1) + "\"" +func PathEscape(path Path) string { + if strings.HasPrefix(string(path), "\"") || strings.ContainsRune(string(path), '\n') { + return "\"" + strings.Replace(strings.Replace(strings.Replace(string(path), "\\", "\\\\", -1), "\"", "\\\"", -1), "\n", "\\n", -1) + "\"" } else { - return path + return string(path) } } -func PathUnescape(epath string) string { +func PathUnescape(epath string) Path { if strings.HasPrefix(epath, "\"") { - return strings.Replace(strings.Replace(strings.Replace(epath[1:len(epath)-1], "\\n", "\n", -1), "\\\"", "\"", -1), "\\\\", "\\", -1) + return Path(strings.Replace(strings.Replace(strings.Replace(epath[1:len(epath)-1], "\\n", "\n", -1), "\\\"", "\"", -1), "\\\\", "\\", -1)) } else { - return epath + return Path(epath) } } type Path string func (p Path) String() string { - return PathEscape(string(p)) + return PathEscape(p) } |