github.com/rcowham/go-libgitfastimport@v0.1.6/backend.go (about) 1 // Copyright (C) 2017-2018, 2021 Luke Shumaker <lukeshu@lukeshu.com> 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 16 package libfastimport 17 18 import ( 19 "bufio" 20 "io" 21 22 "github.com/pkg/errors" 23 "github.com/rcowham/go-libgitfastimport/textproto" 24 ) 25 26 // A Backend is something that consumes a fast-import stream; the 27 // Backend object provides methods for writing to it. A program that 28 // reads from a Backend would itself be a frontend. 29 // 30 // You may think of a "Backend" object as a "Writer" object, though it was not 31 // given that name because the GetMark, CatBlob, and Ls methods 32 // actually provide 2-way communication. 33 type Backend struct { 34 fastImportClose io.Closer 35 fastImportFlush *bufio.Writer 36 fastImportWrite *textproto.FIWriter 37 catBlob *textproto.CatBlobReader 38 39 inCommit bool 40 41 err error 42 onErr func(error) error 43 } 44 45 // NewBackend creates a new Backend object that writes to the given 46 // io.WriteCloser. 47 // 48 // Optionally, you may also provide an io.Reader that responses to 49 // "cat-blob", "get-mark", and "ls" commands can be read from. 50 // 51 // Optionally, you may also provide an onErr function that can be used 52 // to handle or transform errors when they are encountered. 53 func NewBackend(fastImport io.WriteCloser, catBlob io.Reader, onErr func(error) error) *Backend { 54 ret := &Backend{} 55 56 ret.fastImportClose = fastImport 57 ret.fastImportFlush = bufio.NewWriter(fastImport) 58 ret.fastImportWrite = textproto.NewFIWriter(ret.fastImportFlush) 59 60 if catBlob != nil { 61 ret.catBlob = textproto.NewCatBlobReader(catBlob) 62 } 63 64 ret.onErr = func(err error) error { 65 ret.err = err 66 67 // Close the underlying writer, but don't let the 68 // error mask the previous error. 69 err = ret.fastImportClose.Close() 70 if ret.err == nil { 71 ret.err = err 72 } 73 74 if onErr != nil { 75 ret.err = onErr(ret.err) 76 } 77 return ret.err 78 } 79 80 return ret 81 } 82 83 // Do tells the Backend to do the given command. 84 // 85 // It is an error (panic) if Cmd is a type that may only be used in a 86 // commit but we aren't in a commit. 87 func (b *Backend) Do(cmd Cmd) error { 88 if b.err != nil { 89 return b.err 90 } 91 92 switch { 93 case !cmdIs(cmd, cmdClassInCommit): 94 _, b.inCommit = cmd.(CmdCommit) 95 case !b.inCommit && !cmdIs(cmd, cmdClassCommand): 96 panic(errors.Errorf("Cannot issue commit sub-command outside of a commit: %[1]T(%#[1]v)", cmd)) 97 } 98 99 err := cmd.fiCmdWrite(b.fastImportWrite) 100 if err != nil { 101 return b.onErr(err) 102 } 103 err = b.fastImportFlush.Flush() 104 if err != nil { 105 return b.onErr(err) 106 } 107 108 if _, isDone := cmd.(CmdDone); isDone { 109 return b.onErr(nil) 110 } 111 112 return nil 113 } 114 115 // GetMark gets the SHA-1 referred to by the given mark from the 116 // Backend. 117 // 118 // It is an error (panic) to call GetMark if NewBackend did not have a 119 // cat-blob reader passed to it. 120 func (b *Backend) GetMark(cmd CmdGetMark) (sha1 string, err error) { 121 err = b.Do(cmd) 122 if err != nil { 123 return 124 } 125 line, err := b.catBlob.ReadLine() 126 if err != nil { 127 err = b.onErr(err) 128 return 129 } 130 sha1, err = cbpGetMark(line) 131 if err != nil { 132 err = b.onErr(err) 133 } 134 return 135 } 136 137 // CatBlob gets the SHA-1 and content of the specified blob from the 138 // Backend. 139 // 140 // It is an error (panic) to call CatBlob if NewBackend did not have a 141 // cat-blob reader passed to it. 142 func (b *Backend) CatBlob(cmd CmdCatBlob) (sha1 string, data string, err error) { 143 err = b.Do(cmd) 144 if err != nil { 145 return 146 } 147 line, err := b.catBlob.ReadLine() 148 if err != nil { 149 err = b.onErr(err) 150 return 151 } 152 sha1, data, err = cbpCatBlob(line) 153 if err != nil { 154 err = b.onErr(err) 155 } 156 return 157 } 158 159 // Ls gets information about the file at the specified path from the 160 // Backend. 161 // 162 // It is an error (panic) to call Ls if NewBackend did not have a 163 // cat-blob reader passed to it. 164 func (b *Backend) Ls(cmd CmdLs) (mode Mode, dataref string, path Path, err error) { 165 err = b.Do(cmd) 166 if err != nil { 167 return 168 } 169 line, err := b.catBlob.ReadLine() 170 if err != nil { 171 err = b.onErr(err) 172 return 173 } 174 mode, dataref, path, err = cbpLs(line) 175 if err != nil { 176 err = b.onErr(err) 177 } 178 return 179 }