package testgit
import (
"bytes"
"errors"
"fmt"
"path/filepath"
"strings"
"testing"
"lindenii.org/go/furgit/object/id"
)
// ErrInvalidPackObjectsOptions reports an inconsistent
// combination of pack-objects options.
var ErrInvalidPackObjectsOptions = errors.New("internal/testgit: invalid pack-objects options")
// PackObjectsOptions controls one on-disk pack-objects invocation.
type PackObjectsOptions struct {
// RevIndex requests writing a .rev reverse index alongside the pack.
RevIndex bool
// Revs changes how the input is interpreted.
//
// When false, each include is one object name,
// and exactly those objects are packed.
//
// When true, each include is a revision argument,
// and pack-objects packs the closure of objects
// reachable from the includes but not from the excludes,
// the same walk git rev-list --objects performs.
Revs bool
// Exclude lists objects to omit by reachability,
// fed as "^<oid>" revision arguments.
// A non-nil Exclude requires Revs.
Exclude []id.ObjectID
}
// PackObjects packs the include objects with git pack-objects
// into a temporary directory,
// and returns the artifact path prefix "<dir>/pack-<hash>",
// to which ".pack", ".idx", and ".rev" suffixes apply.
func (repo *Repo) PackObjects(tb testing.TB, include []id.ObjectID, opts PackObjectsOptions) (string, error) {
tb.Helper()
if opts.Exclude != nil && !opts.Revs {
return "", fmt.Errorf("%w: Exclude requires Revs", ErrInvalidPackObjectsOptions)
}
dir := tb.TempDir()
revIndex := "false"
if opts.RevIndex {
revIndex = "true"
}
args := []string{"-c", "pack.writeReverseIndex=" + revIndex, "pack-objects"}
if opts.Revs {
args = append(args, "--revs")
}
args = append(args, "--end-of-options", filepath.Join(dir, "pack"))
out, err := repo.run(tb, packObjectsStdin(include, opts.Exclude), "git", args...)
if err != nil {
return "", err
}
return filepath.Join(dir, "pack-"+strings.TrimSpace(string(out))), nil
}
// PackObjectsStdoutOptions controls one streamed pack-objects invocation.
type PackObjectsStdoutOptions struct {
// Revs changes how the input is interpreted.
//
// When false, each include is one object name,
// and exactly those objects are packed.
//
// When true, each include is a revision argument,
// and pack-objects packs the closure of objects
// reachable from the includes but not from the excludes,
// the same walk git rev-list --objects performs.
Revs bool
// Thin omits excluded base objects from the pack,
// producing a thin pack that must be completed before use.
// Thin requires Revs.
Thin bool
// Exclude lists objects to omit by reachability,
// fed as "^<oid>" revision arguments.
// A non-nil Exclude requires Revs.
Exclude []id.ObjectID
}
// PackObjectsStdout packs the include objects with git pack-objects
// and returns the pack stream written to standard output.
func (repo *Repo) PackObjectsStdout(tb testing.TB, include []id.ObjectID, opts PackObjectsStdoutOptions) ([]byte, error) {
tb.Helper()
if opts.Exclude != nil && !opts.Revs {
return nil, fmt.Errorf("%w: Exclude requires Revs", ErrInvalidPackObjectsOptions)
}
if opts.Thin && !opts.Revs {
return nil, fmt.Errorf("%w: Thin requires Revs", ErrInvalidPackObjectsOptions)
}
args := []string{"pack-objects", "--stdout"}
if opts.Revs {
args = append(args, "--revs")
}
if opts.Thin {
args = append(args, "--thin")
}
out, err := repo.run(tb, packObjectsStdin(include, opts.Exclude), "git", args...)
if err != nil {
return nil, err
}
return out, nil
}
// packObjectsStdin builds the pack-objects standard input:
// the include object IDs,
// followed by the exclude object IDs as "^<oid>" lines.
func packObjectsStdin(include, exclude []id.ObjectID) *bytes.Buffer {
var stdin bytes.Buffer
for _, oid := range include {
stdin.WriteString(oid.String())
stdin.WriteByte('\n')
}
for _, oid := range exclude {
stdin.WriteByte('^')
stdin.WriteString(oid.String())
stdin.WriteByte('\n')
}
return &stdin
}