From 902bb1cc2a0a8644e160f303be1a2e0ad354bfd5 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 15 Nov 2017 15:38:05 -0500 Subject: initial commit --- commands.go | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++++ f.txt | 13 ++++ fileactions.go | 76 ++++++++++++++++++++ filter.go | 103 +++++++++++++++++++++++++++ git-fast-import.go | 118 +++++++++++++++++++++++++++++++ io.go | 132 ++++++++++++++++++++++++++++++++++ read.go | 65 +++++++++++++++++ types.go | 65 +++++++++++++++++ 8 files changed, 776 insertions(+) create mode 100644 commands.go create mode 100644 f.txt create mode 100644 fileactions.go create mode 100644 filter.go create mode 100644 git-fast-import.go create mode 100644 io.go create mode 100644 read.go create mode 100644 types.go diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..6c5a0c7 --- /dev/null +++ b/commands.go @@ -0,0 +1,204 @@ +package libfastimport + +import ( + "strconv" +) + +type ezfiw struct { + fiw *FIWriter + err error +} + +func (e *ezfiw) WriteLine(a ...interface{}) { + if e.err == nil { + e.err = e.fiw.WriteLine(a...) + } +} + +func (e *ezfiw) WriteData(data []byte) { + 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 Cmd interface { + fiWriteCmd(*FIWriter) error + fiReadCmd(*FIReader) error +} + +type CmdCommit struct { + Ref string + Mark int // optional; < 1 for non-use + Author *UserTime + Committer UserTime + Msg []byte + Parents []string + Tree []FileAction +} + +func (c *CmdCommit) fiWriteCmd(fiw *FIWriter) error { + ez := &ezfiw{fiw: fiw} + + ez.WriteLine("commit", c.Ref) + if c.Mark > 0 { + ez.WriteMark(c.Mark) + } + if c.Author != nil { + ez.WriteLine("author", *c.Author) + } + ez.WriteLine("committer", c.Committer) + ez.WriteData(c.Data) + if len(c.Parents) > 0 { + ez.WriteLine("from", c.Parents[0]) + if len(c.Parents) > 1 { + for _, parent := range c.Parents[1:] { + ez.WriteLine("merge", parent) + } + } + } + + if ez.err != nil { + return ez.err + } + + for _, action := range c.Tree { + err := action.fiWriteFA(fi) + if err != nil { + return err + } + } + + return nil +} + +type CmdTag struct { + RefName string + CommitIsh string + Tagger UserTime + Data []byte +} + +func (c *CmdTag) fiWriteCmd(fiw *FIWriter) error { + ez := &ezfiw{fiw: fiw} + + ez.WriteLine("tag", c.RefName) + ez.WriteLine("from", c.CommitIsh) + ez.WriteLine("tagger", c.Tagger) + ez.WriteData(c.Data) + + return ez.err +} + +type CmdReset struct { + RefName string + CommitIsh string // optional +} + +func (c *CmdReset) fiWriteCmd(fiw *FIWriter) error { + ez := &ezfiw{fiw: fiw} + + ez.WriteLine("reset", c.RefName) + if c.CommitIsh != "" { + ez.WriteLine("from", c.CommitIsh) + } + + return ez.err +} + +type CmdBlob struct { + Mark int + Data []byte +} + +func (c *CmdBlob) fiWriteCmd(fiw *FIWriter) error { + ez := &ezfiw{fiw: fiw} + + ez.WriteLine("blob") + if mark > 0 { + ez.WriteMark(c.Mark) + } + ez.WriteData(c.Data) + + return ez.err +} + +type CmdCheckpoint struct{} + +func (c *CmdCheckpoint) fiWriteCmd(fiw *FIWriter) error { + return fiw.WriteLine("checkpoint") +} + +type CmdProgress struct { + Str string +} + +func (c *CmdProgress) fiWriteCmd(fiw *FIWriter) error { + return fiw.WriteLine("progress", c.Str) +} + +type CmdGetMark struct { + Mark int +} + +func (c *CmdGetmark) fiWriteCmd(fiw *FIWriter) error { + return fiw.WriteLine("get-mark", ":"+strconv.Itoa(c.Mark)) +} + +type CmdCatBlob struct { + DataRef string +} + +func (c *CmdCatBlob) fiWriteCmd(fiw *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 +} + +func (c *CmdLs) fiWriteCmd(fiw *FIWriter) error { + return fiw.WriteLine("ls", c.DataRef, c.Path) +} + +type CmdFeature struct { + Feature string + Argument string +} + +func (c *CmdFeature) fiWriteCmd(fiw *FIWriter) error { + if c.Argument != "" { + return fiw.WriteLine("feature", c.Feature+"="+c.Argument) + } else { + return fiw.WriteLine("feature", c.Feature) + } +} + +type CmdOption struct { + Option string +} + +func (c *CmdOption) fiWriteCmd(fiw *FIWriter) error { + return fiw.WriteLine("option", c.Option) +} + +type CmdDone struct{} + +func (c *CmdDone) fiWriteCmd(fiw *FIWriter) error { + return fiw.WriteLine("done") +} + +type CmdComment struct { + Comment string +} + +func (c *CmdComment) fiWriteCmd(fiw *FIWriter) error { + return fiw.WriteLine("#" + c.Comment) +} diff --git a/f.txt b/f.txt new file mode 100644 index 0000000..14dcb23 --- /dev/null +++ b/f.txt @@ -0,0 +1,13 @@ +commit +tag +reset +blob +checkpoint +progress +feature +option +done +# +get-mark +cat-blob +ls diff --git a/fileactions.go b/fileactions.go new file mode 100644 index 0000000..b45f776 --- /dev/null +++ b/fileactions.go @@ -0,0 +1,76 @@ +type FileAction interface { + fiWrite(fi *FastImport) error +} + +type FileModify struct { + Mode FileMode + Path string + DataRef string +} + +func (o FileModify) fiWrite(fi *FastImport) error { + return fi.printf(w, "M %06o %s %s\n", o.Mode, o.DataRef, pathEscape(o.Path)) +} + +type FileModifyInline struct { + Mode FileMode + Path string + Data []byte +} + +func (o FileModifyInline) fiWrite(fi *FastImport) error { + fi.printf("M %06o inline %s\n", o.Mode, pathEscape(o.Path)) + return fi.data(o.Data) +} + +type FileDelete struct { + Path string +} + +func (o FileDelete) fiWrite(fi *FastImport) error { + return fi.printf("D %s\n", pathEscape(o.Path)) +} + +type FileCopy struct { + Src string + Dst string +} + +func (o FileCopy) fiWrite(fi *FastImport) error { + return fi.printf("C %s %s\n", pathEscape(o.Src), pathEscape(o.Dst)) +} + +type FileRename struct { + Src string + Dst string +} + +func (o FileRename) fiWrite(fi *FastImport) error { + return fi.printf("R %s %s\n", pathEscape(o.Src), pathEscape(o.Dst)) +} + +type FileDeleteAll struct{} + +func (o FileDeleteAll) fiWrite(fi *FastImport) error { + return fi.printf("deleteall\n") +} + +type NoteModify struct { + CommitIsh string + DataRef string +} + +func (o NoteModify) fiWrite(fi *FastImport) error { + return fi.printf("N %s %s\n", o.DataRef, o.CommitIsh) +} + +type NoteModifyInline struct { + CommitIsh string + Data []byte +} + +func (o NoteModify) fiWrite(fi *FastImport) error { + fi.printf("N inline %s\n", o.CommitIsh) + return fi.data(o.Data) +} + diff --git a/filter.go b/filter.go new file mode 100644 index 0000000..0c09963 --- /dev/null +++ b/filter.go @@ -0,0 +1,103 @@ +package main + +import ( + "os" + "os/exec" + "bytes" +) + +var prog [string]string + +func init() { + var err error + for _, p := range []string{"git", "makepkg"} { + prog[p], err = exec.LookPath("git") + if err != nil { + panic(err) + } + } +} + +func pipeline(cmds ...*exec.Cmd) err error { + for i, cmd := range cmds[:len(cmds)-1] { + cmds[i+1].Stdin, err = cmd.StdoutPipe() + if err != nil { + return + } + } + + stderr := make([]bytes.Buffer, len(cmds)) + for i, cmd := range cmds { + cmd.Stderr = &stderr[i] + if err = cmd.Start(); err != nil { + break + } + } + + for i, cmd := range cmds { + if cmd.Process == nil { + continue + } + if _err := cmd.Wait(); _err != nil { + if ee, ok := _err.(*exec.ExitError); ok { + ee.Stderr = stderr[i].Bytes() + } + if err != nil { + err = _err + } + } + } + return +} + +func pkgbuild2srcinfo(pkgbuildId string) (string, error) { + cachefilename := "filter/pkgbuild2srcinfo/"+pkgbuildId + for { + b, err := ioutil.ReadFile(cachefilename) + if err == nil && len(bytes.TrimSpace(b)) == 40 { + return bytes.TrimSpace(b).String(), nil + } + + file, err := os.OpenFile("filter/tmp/PKGBUILD", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return "", err + } + err = pipeline( + &exec.Cmd{ + Path: prog["git"], Args: ["git", "cat-file", "blob", pkgbuildId], + Stdout: file, + }) + file.Close() + if err != nil { + return "", err + } + + + file, err = os.OpenFile(cachefilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + err = pipeline( + &exec.Cmd{ + Path: prog["makepkg"], Args: ["makepkg", "--printsrcinfo"], + Dir: "filter/tmp" + }, + &exec.Cmd{ + Path: prog["git"], Args: ["git", "hash-object", "-t", "blob", "-w", "--stdin", "--no-filters"], + Stdout: &buf, + }) + file.Close() + if err != nil { + return "", err + } + } +} + + + +func filter(fromPfix string, toPfix string) { + exec.Cmd{ + Path: prog["git"], Args: ["git", "fast-export", + "--use-done-feature", + "--no-data", + "--", fromPfix + "/master"], + } + +} diff --git a/git-fast-import.go b/git-fast-import.go new file mode 100644 index 0000000..d9c7795 --- /dev/null +++ b/git-fast-import.go @@ -0,0 +1,118 @@ +package libfastimport + +import ( + "time" +) + +type FastImport struct { + w *FIWriter + r *CatBlobReader + err error +} + +func (fi *FastImport) GetMark(mark int) (string, error) { + if fi.printf("get-mark :%d\n", mark) != nil { + return "", fi.ioErr + } + var dat [41]byte + var n int + _, fi.ioErr = io.ReadFull(fi.r, dat[:]) + if fi.ioErr != nil { + return "", fi.ioErr + } + if dat[40] != '\n' { + fi.ioErr = fmt.Errorf("get-mark: malformed \\n: %q", string(dat)) + return "", fi.ioErr + } + for _, b := range dat[:40] { + if !(('0' <= b && b <= '9') || ('a' <= b && b <= 'f')) { + fi.ioErr = fmt.Errorf("get-mark: malformed : %q", string(dat[:40])) + return "", fi.ioErr + } + } + return string(dat[:40]) +} + +func (fi *FastImport) CatBlob(dataref string) (sha1 string, data []byte, err error) { + if fi.println("cat-blob %s\n", dataref) != nil { + return "", nil, fi.ioErr + } + // SP 'blob' SP LF + // + // That comes out to be 47+len(itoa(size)). Assuming that + // size is at most a 64-bit integer (a safe assumption), then + // its limit is 20 digits. + var head [67]byte + i := 0 + for { + _, fi.ioErr = io.ReadFull(fi.r, head[i:i+1]) + if fi.ioErr != nil { + return "", nil, fi.ioErr + } + if head[i] == '\n' { + break + } + i++ + if i == len(head) { + fi.ioErr = fmt.Errorf("cat-blob: overly-long header line: %q", string(head)) + return "", nil, fi.ioErr + } + } + i-- + if head[i] != '\n' { + panic("wut") + } + for _, b := range head[:40] { + if !(('0' <= b && b <= '9') || ('a' <= b && b <= 'f')) { + fi.ioErr = fmt.Errorf("cat-blob: malformed : %q", string(head[:40])) + return "", nil, fi.ioErr + } + } + if string(head[40:46]) != " blob " { + fi.ioErr = fmt.Errorf("cat-blob: malformed header: %q", string(head[:i])) + return "", nil, fi.ioErr + } + size, err := strconv.Atoi(string(head[46:i])) + if err != nil { + fi.ioErr = fmt.Errorf("cat-blob: malformed blob size: %v", err) + return "", nil, fi.ioErr + } + dat := make([]byte, size+1) + _, fi.ioErr = io.ReadFull(fi.r, dat) + if dat[size] != '\n' { + fi.ioErr = fmt.Errorf("cat-blob: expected newline after data") + return "", nil, fi.ioErr + } + return string(head[:40]), dat[:size], nil +} + +func (fi *FastImport) Ls(dataref string, path string) error { + if dataref == "" { + fi.printf("ls %s\n", quotePath(path)) + } else { + fi.printf("ls %s %s\n", dataref, quotePath(path)) + } + if fi.ioErr != nil { + return fi.ioErr + } + k +} +func (fi *FastImport) Feature() error +func (fi *FastImport) Option() error + +func (fi *FastImport) Done() error { + fi.printf("done\n") + if fi.ioErr == nil { + fi.ioErr = w.Close() + } + return fi.ioErr +} + +func init() { + x := exec.Cmd{ + Path: prog["git"], + Args: {"git", "fast-import", + "--done", + "--cat-blob-fd=" + strconv.Itoa(TODO)}, + } +} diff --git a/io.go b/io.go new file mode 100644 index 0000000..f3f4909 --- /dev/null +++ b/io.go @@ -0,0 +1,132 @@ +package libfastimport + +import ( + "bufio" + "bytes" + "io" + "strconv" +) + +type FIReader struct { + r *bufio.Reader +} + +func NewFIReader(r io.Reader) *FIReader { + return &FIReader{ + r: bufio.NewReader(r), + } +} + +func (fir *FIReader) ReadSlice() (line []byte, err error) { +retry: + line, err = fir.r.ReadSlice('\n') + if err != nil { + return + } + if len(line) == 1 { + goto retry + } + + if bytes.HasPrefix(line, []byte("data ")) { + if string(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 + + for !bytes.HasSuffix(line, suffix) { + _line, err = fir.r.ReadSlice('\n') + line == append(line, _line) + if err != nil { + return + } + } + } else { + // Exact byte count format + var size int + size, err = strconv.Atoi(string(line[5 : len(line)-1])) + if err != nil { + return + } + _line := make([]byte, size+len(line)) + copy(_line, line) + n, err := io.ReadFull(fir.r, _line[len(line):]) + line = _line[:n+len(line)] + } + } + return +} + +type FIWriter struct { + w io.Writer +} + +func NewFIWriter(w io.Writer) *FIWriter { + return &FIWriter{ + w: w, + } +} + +func (fiw *FIReader) WriteLine(a ...interface{}) error { + _, err := fmt.Fprintln(fiw.w, a...) + return err +} + +func (fiw *FIReader) WriteData(data []byte) error { + err := fiw.WriteLine("data", len(data)) + if err != nil { + return err + } + _, err = fiw.w.Write(data) + return err +} + +type CatBlobReader struct { + r *bufio.Reader +} + +func NewCatBlobReader(r io.Reader) *CatBlobReader { + return &CatBlobReader{ + r: bufio.NewReader(r), + } +} + +func (cbr *CatBlobReader) ReadSlice() (line []byte, err error) { +retry: + line, err = fir.r.ReadSlice('\n') + if err != nil { + return + } + if len(line) == 1 { + goto retry + } + + // get-mark : LF + // cat-blob : SP 'blob' SP LF LF + // ls : SP ('blob' | 'tree' | 'commit') SP HT LF + // ls : 'missing' SP LF + + // decide if we have a cat-blob result + if len(line) <= 46 || string(line[40:46]) != " blob " { + return + } + for _, b := range line[:40] { + if !(('0' <= b && b <= '9') || ('a' <= b && b <= 'f')) { + return + } + } + // we have a cat-blob result + var size int + size, err = strconv.Atoi(string(line[46 : len(line)-1])) + if err != nil { + return + } + _line = make([]byte, len(line)+size+1) + copy(_line, line) + n, err := io.ReadFull(fir.r, _line[len(line):]) + line = _line[:n+len(line)] + return +} diff --git a/read.go b/read.go new file mode 100644 index 0000000..cfe35c4 --- /dev/null +++ b/read.go @@ -0,0 +1,65 @@ +package libfastimport + +type UnsupportedCommand string + +func (e UnsupportedCommand) Error() string { + return "Unsupported command: "+string(e) +} + +type Parser struct { + fir *FIReader + + cmd chan Cmd +} + +func (p *Parser) GetCmd() (Cmd, error) { + for p.cmd == nil { + slice, err := p.fir.ReadSlice() + if err != nil { + return nil, err + } + err = p.putSlice(slice) + if err != nil { + return nil, err + } + } + return p.cmd, nil +} + +func (p *Parser) putSlice(slice []byte) error { +} + +func utSlice(fir *FIReader) (Cmd, error) { + slice, err := fir.PeekSlice() + if err != nil { + return nil, err + } + if len(slice) < 1 { + return nil, UnsupportedCommand(slice) + } + switch slice[0] { + case '#': // comment + case 'b': // blob + case 'c': + if len(slice) < 2 { + return nil, UnsupportedCommand(slice) + } + switch slice[1] { + case 'o': // commit + case 'h': // checkpoint + case 'a': // cat-blob + default: + return nil, UnsupportedCommand(slice) + } + case 'd': // done + case 'f': // feature + case 'g': // get-mark + case 'l': // ls + case 'o': // option + case 'p': // progress + case 'r': // reset + case 't': // tag + default: + return nil, UnsupportedCommand(slice) + } +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..d5c3f7a --- /dev/null +++ b/types.go @@ -0,0 +1,65 @@ +package libfastimport + +import ( + "fmt" + "strings" + "time" +) + +type UserTime struct { + Name string + Email string + Time time.Time +} + +func (ut UserTime) String() string { + if ut.Name == "" { + return fmt.Sprintf("<%s> %d %s", + ut.Name, + ut.Email, + ut.Time.Unix(), + ut.Time.Format("-0700")) + } else { + return fmt.Sprintf("%s <%s> %d %s", + ut.Name, + ut.Email, + ut.Time.Unix(), + ut.Time.Format("-0700")) + } +} + +type Mode uint32 // 18 bits + +var ( + ModeFil = Mode(0100644) + ModeExe = Mode(0100755) + ModeSym = Mode(0120000) + ModeGit = Mode(0160000) + ModeDir = Mode(0040000) +) + +func (m Mode) String() string { + return fmt.Sprintf("%06o", m) +} + +func PathEscape(path string) string { + if strings.HasPrefix(path, "\"") || strings.ContainsRune("\n") { + return "\"" + strings.Replace(strings.Replace(strings.Replace(path, "\\", "\\\\", -1), "\"", "\\\"", -1), "\n", "\\n", -1) + "\"" + } else { + return path + } +} + +func PathUnescape(epath string) string { + if strings.HasPrefix(epath, "\"") { + return strings.Replace(strings.Replace(strings.Replace(epath[1:len(epath)-1], "\\n", "\n", -1), "\\\"", "\"", -1), "\\\\", "\\", -1) + } else { + return epath + } +} + +type Path string + +func (p Path) String() string { + return PathEscape(string(p)) +} -- cgit v1.2.3