Skip to content
go-libfossil Artifact [9c50e2c3e2]
Login

Artifact [9c50e2c3e2]

Artifact 9c50e2c3e28883430f63f3ba48773183ca05d0637caa46c6ede476c0f2dd45d0:


package cli

import (
	"fmt"
	"os"
	"path/filepath"

	libfossil "github.com/danmestas/go-libfossil"
	"github.com/danmestas/go-libfossil/internal/content"
)

// RepoCoCmd checks out a version to the working directory.
type RepoCoCmd struct {
	Version string `arg:"" optional:"" help:"Version to checkout (default: tip)"`
	Dir     string `short:"d" help:"Output directory (default: current dir)" default:"."`
	Force   bool   `help:"Overwrite existing files"`
}

func (c *RepoCoCmd) Run(g *Globals) error {
	r, err := g.OpenRepo()
	if err != nil {
		return err
	}
	defer r.Close()

	rid, err := resolveRID(r, c.Version)
	if err != nil {
		return err
	}

	files, err := r.ListFiles(rid)
	if err != nil {
		return err
	}

	if err := c.preflight(r, files); err != nil {
		return err
	}
	db := r.Inner().DB()
	for _, f := range files {
		fileRid, ok := content.AvailableByUUID(db, f.UUID)
		if !ok {
			return fmt.Errorf("blob %s not found for file %s", f.UUID, f.Name)
		}
		data, err := content.Expand(db, fileRid)
		if err != nil {
			return fmt.Errorf("expanding %s: %w", f.Name, err)
		}

		outPath := filepath.Join(c.Dir, f.Name)
		if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
			return err
		}

		if !c.Force {
			if _, err := os.Stat(outPath); err == nil {
				return fmt.Errorf("file exists: %s (use --force to overwrite)", outPath)
			}
		}

		perm := os.FileMode(0o644)
		if f.Perm == "x" {
			perm = 0o755
		}
		if err := os.WriteFile(outPath, data, perm); err != nil {
			return err
		}

		fmt.Printf("  %s\n", f.Name)
	}

	fmt.Printf("checked out %d files\n", len(files))
	return nil
}

// Validate every payload and overwrite decision before touching the destination.
// Discard expanded bytes between files rather than retaining an entire export in
// memory. The write pass rereads immutable artifacts; filesystem failures can
// still interrupt that pass, but known absent content cannot leave a partial tree.
func (c *RepoCoCmd) preflight(r *libfossil.Repo, files []libfossil.FileEntry) error {
	db := r.Inner().DB()
	for _, file := range files {
		rid, err := content.RequireAvailableByUUID(db, file.UUID)
		if err != nil {
			return fmt.Errorf("required file payload for %s: %w", file.Name, err)
		}
		if _, err := content.Expand(db, rid); err != nil {
			return fmt.Errorf("expanding %s: %w", file.Name, err)
		}
		if !c.Force {
			outPath := filepath.Join(c.Dir, file.Name)
			if _, err := os.Stat(outPath); err == nil {
				return fmt.Errorf("file exists: %s (use --force to overwrite)", outPath)
			} else if !os.IsNotExist(err) {
				return err
			}
		}
	}
	return nil
}