summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2017-11-15 15:38:05 -0500
committerLuke Shumaker <lukeshu@lukeshu.com>2017-11-15 15:38:05 -0500
commit902bb1cc2a0a8644e160f303be1a2e0ad354bfd5 (patch)
treee6ddce8d0bc8664a99ffc346ef38c65d39a76dae
initial commit
-rw-r--r--commands.go204
-rw-r--r--f.txt13
-rw-r--r--fileactions.go76
-rw-r--r--filter.go103
-rw-r--r--git-fast-import.go118
-rw-r--r--io.go132
-rw-r--r--read.go65
-rw-r--r--types.go65
8 files changed, 776 insertions, 0 deletions
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 <sha1>\\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 <sha1>: %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
+ }
+ // <sha1> SP 'blob' SP <size> 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 <sha1>: %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 : <sha1> LF
+ // cat-blob : <sha1> SP 'blob' SP <size> LF <data> LF
+ // ls : <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
+ // ls : 'missing' SP <path> LF
+
+ // decide if we have a cat-blob result
+ 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))
+}