refactor(massive simplification of vcs now that we're using ndjson natively):

This commit is contained in:
Lewis Wynne 2026-02-11 00:28:56 +00:00
parent 84c55311d1
commit cb441b112c
4 changed files with 61 additions and 203 deletions

View file

@ -34,16 +34,12 @@ var gitCmd = &cobra.Command{
Short: "Run any arbitrary command. Use with caution.",
Long: `Run any arbitrary command. Use with caution.
Be wary of how pda! version control operates before using this.
Regular data is stored in "PDA_DATA/pda/stores" as a store; the
Git repository is in "PDA_DATA/pda/vcs" and contains a plaintext
replica of the store data.
The Git repository lives directly in the stores directory
("PDA_DATA/pda/stores"). Store files (*.ndjson) are tracked
by Git as-is.
The regular sync command (or auto-syncing) exports pda! data into
plaintext in the Git repository. If you manually modify the
repository without using the built-in commands, or exporting your
data to the Git folder in the correct format first, you may desync
your repository.
If you manually modify files without using the built-in
commands, you may desync your repository.
Generally prefer "pda sync".`,
Args: cobra.ArbitraryArgs,

View file

@ -40,7 +40,7 @@ var initCmd = &cobra.Command{
}
func init() {
initCmd.Flags().Bool("clean", false, "Remove existing VCS directory before initialising")
initCmd.Flags().Bool("clean", false, "Remove .git from stores directory before initialising")
rootCmd.AddCommand(initCmd)
}
@ -55,25 +55,29 @@ func vcsInit(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
hasRemote := len(args) == 1
if clean {
entries, err := os.ReadDir(repoDir)
if err == nil && len(entries) > 0 {
fmt.Printf("remove existing VCS directory '%s'? (y/n)\n", repoDir)
gitDir := filepath.Join(repoDir, ".git")
if _, err := os.Stat(gitDir); err == nil {
fmt.Printf("remove .git from '%s'? (y/n)\n", repoDir)
var confirm string
if _, err := fmt.Scanln(&confirm); err != nil {
return fmt.Errorf("cannot clean vcs dir: %w", err)
return fmt.Errorf("cannot clean git dir: %w", err)
}
if strings.ToLower(confirm) != "y" {
return fmt.Errorf("aborted cleaning vcs dir")
return fmt.Errorf("aborted cleaning git dir")
}
if err := os.RemoveAll(gitDir); err != nil {
return fmt.Errorf("cannot clean git dir: %w", err)
}
if err := os.RemoveAll(repoDir); err != nil {
return fmt.Errorf("cannot clean vcs dir: %w", err)
}
if hasRemote {
dbs, err := store.AllStores()
if err == nil && len(dbs) > 0 {
fmt.Printf("remove all existing stores? (y/n)\n")
fmt.Printf("remove all existing stores and .gitignore? (required for clone) (y/n)\n")
var confirm string
if _, err := fmt.Scanln(&confirm); err != nil {
return fmt.Errorf("cannot clean stores: %w", err)
@ -84,37 +88,41 @@ func vcsInit(cmd *cobra.Command, args []string) error {
if err := wipeAllStores(store); err != nil {
return fmt.Errorf("cannot clean stores: %w", err)
}
gi := filepath.Join(repoDir, ".gitignore")
if err := os.Remove(gi); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("cannot remove .gitignore: %w", err)
}
}
}
if err := os.MkdirAll(filepath.Join(repoDir), 0o750); err != nil {
return err
}
gitDir := filepath.Join(repoDir, ".git")
if _, err := os.Stat(gitDir); os.IsNotExist(err) {
if len(args) == 1 {
if _, err := os.Stat(gitDir); err == nil {
fmt.Println("vcs already initialised; use --clean to reinitialise")
return nil
}
if hasRemote {
// git clone requires the target directory to be empty
entries, err := os.ReadDir(repoDir)
if err == nil && len(entries) > 0 {
return fmt.Errorf("stores directory is not empty; use --clean with a remote to wipe and clone")
}
remote := args[0]
fmt.Printf("running: git clone %s %s\n", remote, repoDir)
if err := runGit("", "clone", remote, repoDir); err != nil {
return err
}
} else {
if err := os.MkdirAll(repoDir, 0o750); err != nil {
return err
}
fmt.Printf("running: git init\n")
if err := runGit(repoDir, "init"); err != nil {
return err
}
}
} else {
fmt.Println("vcs already initialised; use --clean to reinitialise")
return nil
}
if err := writeGitignore(repoDir); err != nil {
return err
}
if len(args) == 0 {
return nil
}
return restoreAllSnapshots(store, repoDir)
return writeGitignore(repoDir)
}

View file

@ -44,7 +44,6 @@ func init() {
}
func sync(manual bool) error {
store := &Store{}
repoDir, err := ensureVCSInitialized()
if err != nil {
return err
@ -89,18 +88,12 @@ func sync(manual bool) error {
return err
}
}
if err := pullRemote(repoDir, remoteInfo); err != nil {
return err
}
return restoreAllSnapshots(store, repoDir)
return pullRemote(repoDir, remoteInfo)
}
}
}
if err := exportAllStores(store, repoDir); err != nil {
return err
}
if err := runGit(repoDir, "add", storeDirName); err != nil {
if err := runGit(repoDir, "add", "-A"); err != nil {
return err
}
changed, err := repoHasStagedChanges(repoDir)

View file

@ -9,22 +9,10 @@ import (
"path/filepath"
"strconv"
"strings"
gap "github.com/muesli/go-app-paths"
)
const storeDirName = "stores"
func vcsRepoRoot() (string, error) {
scope := gap.NewVendorScope(gap.User, "pda", "vcs")
dir, err := scope.DataPath("")
if err != nil {
return "", err
}
if err := os.MkdirAll(dir, 0o750); err != nil {
return "", err
}
return dir, nil
return (&Store{}).path()
}
func ensureVCSInitialized() (string, error) {
@ -47,10 +35,8 @@ func writeGitignore(repoDir string) error {
content := strings.Join([]string{
"# generated by pda",
"*",
"!/",
"!.gitignore",
"!" + storeDirName + "/",
"!" + storeDirName + "/*",
"!*.ndjson",
"",
}, "\n")
if err := os.WriteFile(path, []byte(content), 0o640); err != nil {
@ -66,71 +52,6 @@ func writeGitignore(repoDir string) error {
return nil
}
// snapshotDB copies a store's .ndjson file into the VCS directory.
func snapshotDB(store *Store, repoDir, db string) error {
targetDir := filepath.Join(repoDir, storeDirName)
if err := os.MkdirAll(targetDir, 0o750); err != nil {
return err
}
srcPath, err := store.storePath(db)
if err != nil {
return err
}
data, err := os.ReadFile(srcPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
target := filepath.Join(targetDir, db+".ndjson")
return os.WriteFile(target, data, 0o640)
}
// exportAllStores copies every store's .ndjson file to repoDir/stores
// and removes stale snapshot files for deleted stores.
func exportAllStores(store *Store, repoDir string) error {
stores, err := store.AllStores()
if err != nil {
return err
}
targetDir := filepath.Join(repoDir, storeDirName)
if err := os.MkdirAll(targetDir, 0o750); err != nil {
return err
}
current := make(map[string]struct{})
for _, db := range stores {
current[db] = struct{}{}
if err := snapshotDB(store, repoDir, db); err != nil {
return fmt.Errorf("snapshot %q: %w", db, err)
}
}
entries, err := os.ReadDir(targetDir)
if err != nil {
return err
}
for _, e := range entries {
if e.IsDir() || filepath.Ext(e.Name()) != ".ndjson" {
continue
}
dbName := strings.TrimSuffix(e.Name(), ".ndjson")
if _, ok := current[dbName]; ok {
continue
}
if err := os.Remove(filepath.Join(targetDir, e.Name())); err != nil && !os.IsNotExist(err) {
return err
}
}
return nil
}
func runGit(dir string, args ...string) error {
cmd := exec.Command("git", args...)
cmd.Dir = dir
@ -288,66 +209,6 @@ func currentBranch(dir string) (string, error) {
return branch, nil
}
// restoreAllSnapshots copies .ndjson files from VCS snapshot dir into store paths,
// and removes local stores that are not in the snapshot.
func restoreAllSnapshots(store *Store, repoDir string) error {
targetDir := filepath.Join(repoDir, storeDirName)
entries, err := os.ReadDir(targetDir)
if err != nil {
if os.IsNotExist(err) {
fmt.Printf("no existing stores found, not restoring")
return nil
}
return err
}
snapshotDBs := make(map[string]struct{})
for _, e := range entries {
if e.IsDir() {
continue
}
if filepath.Ext(e.Name()) != ".ndjson" {
continue
}
dbName := strings.TrimSuffix(e.Name(), ".ndjson")
snapshotDBs[dbName] = struct{}{}
srcPath := filepath.Join(targetDir, e.Name())
data, err := os.ReadFile(srcPath)
if err != nil {
return fmt.Errorf("restore %q: %w", dbName, err)
}
dstPath, err := store.storePath(dbName)
if err != nil {
return fmt.Errorf("restore %q: %w", dbName, err)
}
if err := os.WriteFile(dstPath, data, 0o640); err != nil {
return fmt.Errorf("restore %q: %w", dbName, err)
}
}
localDBs, err := store.AllStores()
if err != nil {
return err
}
for _, db := range localDBs {
if _, ok := snapshotDBs[db]; ok {
continue
}
p, err := store.storePath(db)
if err != nil {
return err
}
if err := os.Remove(p); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("remove store '%s': %w", db, err)
}
}
return nil
}
func wipeAllStores(store *Store) error {
dbs, err := store.AllStores()
if err != nil {