// Copyright 2023 Harness, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "bufio" "bytes" "context" "io" "strconv" "strings" "github.com/harness/gitness/errors" "github.com/harness/gitness/git/command" "github.com/harness/gitness/git/sha" "github.com/djherbis/buffer" "github.com/djherbis/nio/v3" ) // WriteCloserError wraps an io.WriteCloser with an additional CloseWithError function. type WriteCloserError interface { io.WriteCloser CloseWithError(err error) error } // CatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, // a stdout reader and cancel function. func CatFileBatch( ctx context.Context, repoPath string, alternateObjectDirs []string, flags ...command.CmdOptionFunc, ) (WriteCloserError, *bufio.Reader, func()) { flags = append(flags, command.WithFlag("--batch")) return catFileBatch(ctx, repoPath, alternateObjectDirs, flags...) } func CatFileBatchCheck( ctx context.Context, repoPath string, alternateObjectDirs []string, flags ...command.CmdOptionFunc, ) (WriteCloserError, *bufio.Reader, func()) { flags = append(flags, command.WithFlag("--batch-check")) return catFileBatch(ctx, repoPath, alternateObjectDirs, flags...) } func catFileBatch( ctx context.Context, repoPath string, alternateObjectDirs []string, flags ...command.CmdOptionFunc, ) (WriteCloserError, *bufio.Reader, func()) { const bufferSize = 32 * 1024 // We often want to feed the commits in order into cat-file --batch, // followed by their trees and sub trees as necessary. batchStdinReader, batchStdinWriter := io.Pipe() batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(bufferSize)) ctx, ctxCancel := context.WithCancel(ctx) closed := make(chan struct{}) cancel := func() { ctxCancel() _ = batchStdinWriter.Close() _ = batchStdoutReader.Close() <-closed } // Ensure cancel is called as soon as the provided context is cancelled go func() { <-ctx.Done() cancel() }() go func() { stderr := bytes.Buffer{} cmd := command.New("cat-file", command.WithAlternateObjectDirs(alternateObjectDirs...), ) cmd.Add(flags...) err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdin(batchStdinReader), command.WithStdout(batchStdoutWriter), command.WithStderr(&stderr), ) if err != nil { _ = batchStdoutWriter.CloseWithError(command.NewError(err, stderr.Bytes())) _ = batchStdinReader.CloseWithError(command.NewError(err, stderr.Bytes())) } else { _ = batchStdoutWriter.Close() _ = batchStdinReader.Close() } close(closed) }() // For simplicities sake we'll us a buffered reader to read from the cat-file --batch batchReader := bufio.NewReaderSize(batchStdoutReader, bufferSize) return batchStdinWriter, batchReader, cancel } type BatchHeaderResponse struct { SHA sha.SHA Type string Size int64 } // ReadBatchHeaderLine reads the header line from cat-file --batch // SP SP LF // sha is a 40byte not 20byte here. func ReadBatchHeaderLine(rd *bufio.Reader) (*BatchHeaderResponse, error) { line, err := rd.ReadString('\n') if err != nil { return nil, err } if len(line) == 1 { line, err = rd.ReadString('\n') if err != nil { return nil, err } } idx := strings.IndexByte(line, ' ') if idx < 0 { return nil, errors.NotFound("missing space char for: %s", line) } id := line[:idx] objType := line[idx+1:] idx = strings.IndexByte(objType, ' ') if idx < 0 { return nil, errors.NotFound("sha '%s' not found", id) } sizeStr := objType[idx+1 : len(objType)-1] objType = objType[:idx] size, err := strconv.ParseInt(sizeStr, 10, 64) if err != nil { return nil, err } return &BatchHeaderResponse{ SHA: sha.Must(id), Type: objType, Size: size, }, nil }