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  }