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 "^" 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 "/pack-", // 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 "^" 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 "^" 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 }