Skip to content
go-libfossil Artifact [a22ba40741]
Login

Artifact [a22ba40741]

Artifact a22ba40741b29d6733d7fcd167b79c174806a0ddebb6df93cbd54856ac57e8ec:


package checkout

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

	"github.com/danmestas/go-libfossil/db"
)

// moveMarker moves the SQLite database, not working files in Env.Storage.
// Closing after a complete checkpoint prevents connections or WAL sidecars
// from continuing to write under the old name. Linking refuses replacement;
// interruption before unlink leaves two markers, which discovery rejects.
func (c *Checkout) moveMarker(name string) error {
	target := filepath.Join(filepath.Dir(c.dbPath), name)
	if target == c.dbPath {
		return nil
	}
	if err := checkMarkerTarget(target); err != nil {
		return err
	}
	var busy, log, checkpointed int
	if err := c.db.QueryRow("PRAGMA wal_checkpoint(TRUNCATE)").Scan(&busy, &log, &checkpointed); err != nil {
		return err
	}
	if busy != 0 {
		return fmt.Errorf("checkout marker checkpoint is busy")
	}
	if err := c.db.Close(); err != nil {
		return err
	}
	old := c.dbPath
	moveErr := os.Link(old, target)
	if moveErr == nil {
		if err := syncMarkerDirectory(filepath.Dir(old)); err != nil {
			moveErr = errors.Join(err, os.Remove(target))
		} else if err := os.Remove(old); err != nil {
			moveErr = errors.Join(err, os.Remove(target))
		} else {
			c.dbPath = target
			moveErr = syncMarkerDirectory(filepath.Dir(old))
		}
	}
	reopened, err := db.OpenSQL(c.dbPath, db.OpenConfig{}, nil)
	if err == nil {
		err = reopened.Ping()
	}
	if err != nil {
		if reopened != nil {
			reopened.Close()
		}
		return errors.Join(moveErr, fmt.Errorf("reopen checkout marker: %w", err))
	}
	c.db = reopened
	return moveErr
}

func syncMarkerDirectory(dir string) error {
	f, err := os.Open(dir)
	if err != nil {
		return err
	}
	return errors.Join(f.Sync(), f.Close())
}

func checkMarkerTarget(target string) error {
	// Opening SQLite can overwrite or remove sidecars even when its main file
	// does not exist. Lstat also refuses dangling links and filesystem aliases.
	for _, suffix := range []string{"", "-wal", "-shm", "-journal"} {
		name := target + suffix
		if _, err := os.Lstat(name); err == nil {
			return fmt.Errorf("checkout marker already exists: %s", name)
		} else if !os.IsNotExist(err) {
			return err
		}
	}
	return nil
}