diff --git a/cmd/restore.go b/cmd/restore.go new file mode 100644 index 0000000..42a0f08 --- /dev/null +++ b/cmd/restore.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "git.kausm.in/kaustubh/autosaved/core" + "github.com/spf13/cobra" +) + +var restoreCmd = &cobra.Command{ + Use: "restore commit-hash", + Short: "Restores the state of a repository to a previous checkpoint (commit). Input is commit hash that we want to restore", + Long: `Restores the state of the repository to a previous state, +whose commit Hash is given as an argument`, + Args: cobra.ExactArgs(1), + Run: restore, +} + +func restore(cmd *cobra.Command, args []string) { + repoPath := "." + asdRepo, err := core.AsdRepoFromGitRepoPath(repoPath, getMinSeconds()) + checkError(err) + + hashString := args[0] + + err = asdRepo.RestoreByCommitHash(hashString) + checkError(err) + + asdFmt.Successf("Restored successfully\n") +} diff --git a/cmd/root.go b/cmd/root.go index 77eb993..274e640 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,7 +22,7 @@ var globalViper = viper.GetViper() // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "autosaved", + Use: "asdi", Short: "Never lose your work. Code without worrying", Long: `autosaved, pronounced autosave-d (for autosave daemon) is a utility written in Go to autosave progress on code projects. @@ -68,6 +68,8 @@ func init() { rootCmd.AddCommand(watchCmd) rootCmd.AddCommand(listCmd) + + rootCmd.AddCommand(restoreCmd) } func initConfig() { diff --git a/core/core.go b/core/core.go index 633dd0e..64a5a01 100644 --- a/core/core.go +++ b/core/core.go @@ -1,9 +1,12 @@ package core import ( + "bufio" "errors" "fmt" "log" + "os" + "strings" "time" "github.com/fatih/color" @@ -28,6 +31,8 @@ var ( ErrNothingToSave = errors.New("nothing to save") ErrAutosavedBranchNotCreated = errors.New("autosaved branch for current branch hasn't been created yet") ErrUserUnbornHead = errors.New("autosaved cannot continue with an unborn head. please make an initial commit and try again") + ErrInvalidHash = errors.New("the hash submitted is not a valid hash") + ErrUserDidNotConfirm = errors.New("user didn't confirm yes") ) type AsdRepository struct { @@ -251,9 +256,13 @@ func (asd *AsdRepository) ShouldSave() (bool, string, error) { return false, "", err } + if shouldSave == false { + return false, reason2, nil + } + if reason2 != "" { if reason != "" { - reason = reason + " and" + reason2 + reason = reason + " and " + reason2 } else { reason = reason2 } @@ -448,7 +457,90 @@ func (asd *AsdRepository) List(limit int) error { return nil } -func (asd *AsdRepository) ListOnCurrentBranch(limit int) error { +func (asd *AsdRepository) RestoreByCommitHash(hashString string) error { + if !plumbing.IsHash(hashString) { + return ErrInvalidHash + } + + hash := plumbing.NewHash(hashString) + + color.New(color.FgCyan).Printf("\nTip: you can run `git diff HEAD..%s` to confirm your changes\n", hash.String()) + + questionString := color.New(color.FgYellow).Sprintf(`Are you sure you want to restore to checkpoint %s?`, hashString[:6]) + + if askForConfirmation(questionString) { + return asd.restoreCheckpoint(hash) + } else { + return ErrUserDidNotConfirm + } +} + +func askForConfirmation(s string) bool { + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Printf("%s [y/n]: ", s) + + response, err := reader.ReadString('\n') + if err != nil { + log.Fatal(err) + } + + response = strings.ToLower(strings.TrimSpace(response)) + + if response == "y" || response == "yes" { + return true + } else if response == "n" || response == "no" { + return false + } + } +} + +func (asd *AsdRepository) restoreCheckpoint(commit plumbing.Hash) error { + r := asd.Repository + w, err := r.Worktree() + if err != nil { + return err + } + + // make note of the current head ref + head, err := r.Head() + if err != nil { + if err == plumbing.ErrReferenceNotFound { + err = ErrUserUnbornHead + return err + } + + log.Printf("error: %v\n", err) + return err + } + + // force checkout to that commit + coOpts := git.CheckoutOptions{ + Hash: commit, + Force: true, + } + err = w.Checkout(&coOpts) + if err != nil { + return err + } + + // git reset soft + resetOpts := git.ResetOptions{ + Mode: git.SoftReset, + Commit: head.Hash(), + } + err = w.Reset(&resetOpts) + if err != nil { + return err + } + + // git checkout to head with keep + err = checkoutWithKeep(w, head.Name()) + if err != nil { + return err + } + return nil }