diff --git a/cmd/init.go b/cmd/init.go index 07c64a9..764e247 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -45,11 +45,11 @@ func init() { } func vcsInit(cmd *cobra.Command, args []string) error { - repoDir, err := vcsRepoRoot() + store := &Store{} + repoDir, err := store.path() if err != nil { return err } - store := &Store{} clean, err := cmd.Flags().GetBool("clean") if err != nil { diff --git a/cmd/sync.go b/cmd/sync.go index b5b696e..e325701 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -24,7 +24,6 @@ package cmd import ( "fmt" - "strings" "time" "github.com/spf13/cobra" @@ -54,45 +53,7 @@ func sync(manual bool) error { return err } - var ahead int - if remoteInfo.Ref != "" { - if manual || config.Git.AutoFetch { - if err := runGit(repoDir, "fetch", "--prune"); err != nil { - return err - } - } - remoteAhead, behind, err := repoAheadBehind(repoDir, remoteInfo.Ref) - if err != nil { - ahead = 1 // ref doesn't exist yet; just push - } else { - ahead = remoteAhead - if behind > 0 { - if ahead > 0 { - return fmt.Errorf("repo diverged from remote (ahead %d, behind %d); resolve manually", ahead, behind) - } - fmt.Printf("remote has %d commit(s) not present locally; discard local changes and pull? (y/n)\n", behind) - var confirm string - if _, err := fmt.Scanln(&confirm); err != nil { - return fmt.Errorf("cannot continue sync: %w", err) - } - if strings.ToLower(confirm) != "y" { - return fmt.Errorf("aborted sync") - } - dirty, err := repoHasChanges(repoDir) - if err != nil { - return err - } - if dirty { - stashMsg := fmt.Sprintf("pda sync: %s", time.Now().UTC().Format(time.RFC3339)) - if err := runGit(repoDir, "stash", "push", "-u", "-m", stashMsg); err != nil { - return err - } - } - return pullRemote(repoDir, remoteInfo) - } - } - } - + // Commit local changes first so nothing is lost. if err := runGit(repoDir, "add", "-A"); err != nil { return err } @@ -100,29 +61,54 @@ func sync(manual bool) error { if err != nil { return err } - madeCommit := false - if !changed { - if manual { - fmt.Println("no changes to commit") - } - } else { + if changed { msg := fmt.Sprintf("sync: %s", time.Now().UTC().Format(time.RFC3339)) if err := runGit(repoDir, "commit", "-m", msg); err != nil { return err } - madeCommit = true + } else if manual { + fmt.Println("no changes to commit") } + + if remoteInfo.Ref == "" { + if manual { + fmt.Println("no remote configured; skipping push") + } + return nil + } + + // Fetch remote state. + if manual || config.Git.AutoFetch { + if err := runGit(repoDir, "fetch", "--prune"); err != nil { + return err + } + } + + // Rebase local commits onto remote if behind. + ahead, behind, err := repoAheadBehind(repoDir, remoteInfo.Ref) + if err != nil { + // Remote ref doesn't exist yet (first push). + ahead = 1 + } else if behind > 0 { + if err := pullRemote(repoDir, remoteInfo); err != nil { + return err + } + ahead, _, err = repoAheadBehind(repoDir, remoteInfo.Ref) + if err != nil { + return err + } + } + + // Push if ahead. if manual || config.Git.AutoPush { - if remoteInfo.Ref == "" { - if manual { - fmt.Println("no remote configured; skipping push") - } - } else if madeCommit || ahead > 0 { + if ahead > 0 { return pushRemote(repoDir, remoteInfo) - } else if manual { + } + if manual { fmt.Println("nothing to push") } } + return nil } @@ -130,5 +116,8 @@ func autoSync() error { if !config.Git.AutoCommit { return nil } + if _, err := ensureVCSInitialized(); err != nil { + return nil + } return sync(false) } diff --git a/cmd/vcs.go b/cmd/vcs.go index 9a82706..ecb1f45 100644 --- a/cmd/vcs.go +++ b/cmd/vcs.go @@ -1,7 +1,6 @@ package cmd import ( - "bytes" "fmt" "io" "os" @@ -11,12 +10,8 @@ import ( "strings" ) -func vcsRepoRoot() (string, error) { - return (&Store{}).path() -} - func ensureVCSInitialized() (string, error) { - repoDir, err := vcsRepoRoot() + repoDir, err := (&Store{}).path() if err != nil { return "", err } @@ -118,16 +113,6 @@ func repoAheadBehind(dir, ref string) (int, int, error) { return ahead, behind, nil } -func repoHasChanges(dir string) (bool, error) { - cmd := exec.Command("git", "status", "--porcelain") - cmd.Dir = dir - out, err := cmd.Output() - if err != nil { - return false, err - } - return len(bytes.TrimSpace(out)) > 0, nil -} - func repoHasStagedChanges(dir string) (bool, error) { cmd := exec.Command("git", "diff", "--cached", "--quiet") cmd.Dir = dir @@ -143,26 +128,16 @@ func repoHasStagedChanges(dir string) (bool, error) { func pullRemote(dir string, info gitRemoteInfo) error { if info.HasUpstream { - return runGit(dir, "pull", "--ff-only") + return runGit(dir, "pull", "--rebase") } - if info.Remote != "" && info.Branch != "" { - fmt.Printf("running: git pull --ff-only %s %s\n", info.Remote, info.Branch) - return runGit(dir, "pull", "--ff-only", info.Remote, info.Branch) - } - fmt.Println("no remote configured; skipping pull") - return nil + return runGit(dir, "pull", "--rebase", info.Remote, info.Branch) } func pushRemote(dir string, info gitRemoteInfo) error { if info.HasUpstream { return runGit(dir, "push") } - if info.Remote != "" && info.Branch != "" { - fmt.Printf("running: git push -u %s %s\n", info.Remote, info.Branch) - return runGit(dir, "push", "-u", info.Remote, info.Branch) - } - fmt.Println("no remote configured; skipping push") - return nil + return runGit(dir, "push", "-u", info.Remote, info.Branch) } func repoHasUpstream(dir string) (bool, error) {