Files
kandev/apps/backend/internal/gitcheckout/sparse.go
T

68 lines
2.6 KiB
Go

// Package gitcheckout applies task-owned checkout scopes before file population.
package gitcheckout
import (
"context"
"fmt"
"strings"
"github.com/kandev/kandev/internal/task/models"
)
// Run executes Git with the caller's credentials, admission, and cancellation.
type Run func(context.Context, []string, string) ([]byte, error)
func ApplySparse(ctx context.Context, path string, options *models.RepositoryCheckoutOptions, run Run) error {
options, err := models.NormalizeRepositoryCheckoutOptions(options)
if err != nil || options == nil || len(options.SparseDirectories) == 0 {
return err
}
for _, directory := range options.SparseDirectories {
out, err := run(ctx, []string{"-C", path, "--literal-pathspecs", "ls-tree", "-d", "-z", "HEAD", "--", directory}, "")
if err != nil {
return fmt.Errorf("inspect selected checkout directory: %w", err)
}
directoryLike := strings.HasPrefix(string(out), "040000 tree ") || strings.HasPrefix(string(out), "160000 commit ")
if !directoryLike || !strings.HasSuffix(string(out), "\t"+directory+"\x00") {
return &DirectoryError{Directory: directory}
}
}
if err := ConfigureSparse(ctx, path, options, run); err != nil {
return err
}
// A --no-checkout worktree has no populated index for sparse-checkout set
// to update. Read HEAD only after the sparse patterns are installed.
if _, err := run(ctx, []string{"-C", path, "read-tree", "-mu", "HEAD"}, ""); err != nil {
return fmt.Errorf("populate selected checkout folders: %w", err)
}
return nil
}
// ConfigureSparse installs patterns on an empty index before a branch checkout.
func ConfigureSparse(ctx context.Context, path string, options *models.RepositoryCheckoutOptions, run Run) error {
if options == nil || len(options.SparseDirectories) == 0 {
return nil
}
_, err := run(ctx, []string{"-C", path, "sparse-checkout", "set", "--cone", "--stdin"}, SparseInput(options.SparseDirectories))
if err != nil {
return fmt.Errorf("configure selected checkout folders: %w", err)
}
return nil
}
// SparseInput uses Git's quoted pathname format, preserving quotes and Unicode.
func SparseInput(directories []string) string {
quoted := make([]string, 0, len(directories))
for _, directory := range directories {
quoted = append(quoted, `"`+strings.ReplaceAll(directory, `"`, `\"`)+`"`)
}
return strings.Join(quoted, "\n") + "\n"
}
// DirectoryError contains only a validated repository-relative directory.
type DirectoryError struct{ Directory string }
func (e *DirectoryError) Error() string {
return fmt.Sprintf("selected folder %q is not a directory at the selected revision", e.Directory)
}