refactor(massive simplification of vcs now that we're using ndjson natively):
This commit is contained in:
parent
84c55311d1
commit
cb441b112c
4 changed files with 61 additions and 203 deletions
14
cmd/git.go
14
cmd/git.go
|
|
@ -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,
|
||||
|
|
|
|||
96
cmd/init.go
96
cmd/init.go
|
|
@ -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,66 +55,74 @@ 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)
|
||||
}
|
||||
|
||||
dbs, err := store.AllStores()
|
||||
if err == nil && len(dbs) > 0 {
|
||||
fmt.Printf("remove all existing stores? (y/n)\n")
|
||||
var confirm string
|
||||
if _, err := fmt.Scanln(&confirm); err != nil {
|
||||
return fmt.Errorf("cannot clean stores: %w", err)
|
||||
}
|
||||
if strings.ToLower(confirm) != "y" {
|
||||
return fmt.Errorf("aborted cleaning stores")
|
||||
}
|
||||
if err := wipeAllStores(store); err != nil {
|
||||
return fmt.Errorf("cannot clean stores: %w", err)
|
||||
if hasRemote {
|
||||
dbs, err := store.AllStores()
|
||||
if err == nil && len(dbs) > 0 {
|
||||
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)
|
||||
}
|
||||
if strings.ToLower(confirm) != "y" {
|
||||
return fmt.Errorf("aborted cleaning stores")
|
||||
}
|
||||
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 {
|
||||
remote := args[0]
|
||||
fmt.Printf("running: git clone %s %s\n", remote, repoDir)
|
||||
if err := runGit("", "clone", remote, repoDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("running: git init\n")
|
||||
if err := runGit(repoDir, "init"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(gitDir); err == nil {
|
||||
fmt.Println("vcs already initialised; use --clean to reinitialise")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeGitignore(repoDir); err != nil {
|
||||
return err
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return restoreAllSnapshots(store, repoDir)
|
||||
return writeGitignore(repoDir)
|
||||
}
|
||||
|
|
|
|||
11
cmd/sync.go
11
cmd/sync.go
|
|
@ -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)
|
||||
|
|
|
|||
143
cmd/vcs.go
143
cmd/vcs.go
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue