Skip to content
go-libfossil Artifact [a5682d850b]
Login

Artifact [a5682d850b]

Artifact a5682d850b7ef502a3ec81c52cf3ac73bb7cc00abe5515886382dc76db243e50:


package checkout

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"sort"

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

// UpdateRequiredFiles resolves the update target and returns the covered target
// payloads plus any base payloads needed for a local merge. Planning scans live
// working files without storing scan results; no working bytes survive recovery.
// Once resolved, target is retained even on structural refusal so the caller
// can bind it and RecoverySnapshot before an unlocked exchange.
func (c *Checkout) UpdateRequiredFiles(
	ctx context.Context, opts UpdateOpts,
) (libfossil.FslID, []manifest.FileEntry, error) {
	target, _, files, err := c.prepareUpdate(ctx, opts)
	return target, files, err
}

func (c *Checkout) prepareUpdate(
	ctx context.Context, opts UpdateOpts,
) (libfossil.FslID, []updateStep, []manifest.FileEntry, error) {
	if c == nil {
		panic("checkout.Update: nil checkout")
	}
	if ctx == nil {
		panic("checkout.Update: nil context")
	}
	if err := ctx.Err(); err != nil {
		return 0, nil, nil, err
	}
	target, current, err := c.updateTarget(opts.TargetRID)
	if err != nil || target == 0 {
		return target, nil, nil, err
	}
	parents, err := c.mergeParents(current)
	if err != nil {
		return target, nil, nil, err
	}
	parents = append(parents, current, target)
	if err := manifest.RequireParentContent(ctx, c.repo.DB(), parents); err != nil {
		return target, nil, nil, err
	}
	steps, err := c.planUpdate(current, target)
	if err != nil {
		return target, nil, nil, err
	}
	files, err := c.updateFileNeeds(ctx, target, steps)
	return target, steps, files, err
}

// updateFiles retains all target metadata, even when the worktree or store
// excludes its payload. Planning compares immutable identities, not bytes.
func (c *Checkout) updateFiles(
	target libfossil.FslID,
) ([]manifest.FileEntry, []libfossil.FslID, error) {
	files, err := manifest.ListFiles(c.repo, target)
	if err != nil {
		return nil, nil, err
	}
	rids := make([]libfossil.FslID, len(files))
	for i, file := range files {
		err := c.repo.DB().QueryRow("SELECT rid FROM blob WHERE uuid=?", file.UUID).Scan(&rids[i])
		if err != nil && !errors.Is(err, sql.ErrNoRows) {
			return nil, nil, err
		}
	}
	return files, rids, nil
}

func (c *Checkout) updateFileNeeds(
	ctx context.Context, target libfossil.FslID, steps []updateStep,
) ([]manifest.FileEntry, error) {
	files, err := manifest.ListFiles(c.repo, target)
	if err != nil {
		return nil, err
	}
	needs := make([]manifest.FileEntry, 0, len(files))
	seen := make(map[string]bool, len(files))
	for _, file := range files {
		if err := ctx.Err(); err != nil {
			return nil, err
		}
		if c.covers(file.Name) && !seen[file.UUID] {
			needs = append(needs, file)
			seen[file.UUID] = true
		}
	}
	for _, step := range steps {
		if err := ctx.Err(); err != nil {
			return nil, err
		}
		if step.action != updateMerge {
			continue
		}
		var uuid string
		err := c.repo.DB().QueryRow(
			"SELECT uuid FROM blob WHERE rid=?", step.row.RID,
		).Scan(&uuid)
		if err != nil {
			return nil, err
		}
		if !seen[uuid] {
			needs = append(needs, manifest.FileEntry{Name: step.name, UUID: uuid})
			seen[uuid] = true
		}
	}
	sort.Slice(needs, func(i, j int) bool {
		if needs[i].Name == needs[j].Name {
			return needs[i].UUID < needs[j].UUID
		}
		return needs[i].Name < needs[j].Name
	})
	return needs, nil
}

func (c *Checkout) requireUpdateFiles(ctx context.Context, files []manifest.FileEntry) error {
	var missing []string
	for _, file := range files {
		if err := ctx.Err(); err != nil {
			return err
		}
		_, available, err := content.CheckAvailableByUUID(c.repo.DB(), file.UUID)
		if err != nil {
			return err
		}
		if !available {
			missing = append(missing, file.Name)
		}
	}
	if len(missing) > 0 {
		return fmt.Errorf("checkout.Update: %w", missingContentError(missing))
	}
	return ctx.Err()
}