Implement Milestone 1
This commit is contained in:
204
internal/commands/undo.go
Normal file
204
internal/commands/undo.go
Normal file
@ -0,0 +1,204 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.dws.rip/DWS/onyx/internal/core"
|
||||
"git.dws.rip/DWS/onyx/internal/storage"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewUndoCmd creates the undo command
|
||||
func NewUndoCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "undo",
|
||||
Short: "Undo the last operation",
|
||||
Long: `Undo the last operation by restoring the repository to its previous state.
|
||||
|
||||
This command reads the last entry from the oplog and restores all refs
|
||||
and working directory state to what they were before the operation.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: runUndo,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUndo(cmd *cobra.Command, args []string) error {
|
||||
// Get current directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current directory: %w", err)
|
||||
}
|
||||
|
||||
// Check if we're in an Onyx repository
|
||||
if !core.IsOnyxRepo(cwd) {
|
||||
return fmt.Errorf("not an onyx repository (or any parent up to mount point)")
|
||||
}
|
||||
|
||||
// Open the repository
|
||||
repo, err := core.Open(cwd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open repository: %w", err)
|
||||
}
|
||||
defer repo.Close()
|
||||
|
||||
// Open oplog reader
|
||||
oplogPath := filepath.Join(repo.GetOnyxPath(), "oplog")
|
||||
reader := storage.NewOplogReader(oplogPath)
|
||||
|
||||
// Check if oplog is empty
|
||||
isEmpty, err := reader.IsEmpty()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check oplog: %w", err)
|
||||
}
|
||||
if isEmpty {
|
||||
return fmt.Errorf("nothing to undo")
|
||||
}
|
||||
|
||||
// Read the last entry
|
||||
lastEntry, err := reader.ReadLastEntry()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read last entry: %w", err)
|
||||
}
|
||||
|
||||
// Check if we have state_before to restore
|
||||
if lastEntry.StateBefore == nil {
|
||||
return fmt.Errorf("cannot undo: last operation has no state_before")
|
||||
}
|
||||
|
||||
// Show what we're undoing
|
||||
fmt.Printf("Undoing: %s - %s\n", lastEntry.Operation, lastEntry.Description)
|
||||
|
||||
// Create state capture to restore the state
|
||||
stateCapture := storage.NewStateCapture(repo.GetGitRepo())
|
||||
|
||||
// Restore the state
|
||||
err = stateCapture.RestoreState(lastEntry.StateBefore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restore state: %w", err)
|
||||
}
|
||||
|
||||
// Log the undo operation
|
||||
txn, err := core.NewTransaction(repo)
|
||||
if err != nil {
|
||||
// Don't fail if we can't create transaction, state is already restored
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to log undo to oplog: %v\n", err)
|
||||
} else {
|
||||
defer txn.Close()
|
||||
|
||||
metadata := map[string]string{
|
||||
"undone_entry_id": fmt.Sprintf("%d", lastEntry.ID),
|
||||
"undone_operation": lastEntry.Operation,
|
||||
}
|
||||
|
||||
err = txn.ExecuteWithTransactionAndMetadata(
|
||||
"undo",
|
||||
fmt.Sprintf("Undid operation: %s", lastEntry.Operation),
|
||||
metadata,
|
||||
func() error {
|
||||
// The actual undo has already been performed above
|
||||
// This function is just to capture the state after undo
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to log undo: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Show what changed
|
||||
stateAfter, _ := stateCapture.CaptureState()
|
||||
if stateAfter != nil {
|
||||
differences := stateCapture.CompareStates(stateAfter, lastEntry.StateBefore)
|
||||
if len(differences) > 0 {
|
||||
fmt.Println("\nChanges:")
|
||||
for ref, change := range differences {
|
||||
fmt.Printf(" %s: %s\n", ref, change)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nUndo complete!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// UndoToEntry undoes to a specific entry ID
|
||||
func UndoToEntry(repo *core.OnyxRepository, entryID uint64) error {
|
||||
oplogPath := filepath.Join(repo.GetOnyxPath(), "oplog")
|
||||
reader := storage.NewOplogReader(oplogPath)
|
||||
|
||||
// Read the target entry
|
||||
entry, err := reader.ReadEntry(entryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read entry %d: %w", entryID, err)
|
||||
}
|
||||
|
||||
if entry.StateBefore == nil {
|
||||
return fmt.Errorf("entry %d has no state_before to restore", entryID)
|
||||
}
|
||||
|
||||
// Restore the state
|
||||
stateCapture := storage.NewStateCapture(repo.GetGitRepo())
|
||||
err = stateCapture.RestoreState(entry.StateBefore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restore state: %w", err)
|
||||
}
|
||||
|
||||
// Log the undo operation
|
||||
txn, err := core.NewTransaction(repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create transaction: %w", err)
|
||||
}
|
||||
defer txn.Close()
|
||||
|
||||
metadata := map[string]string{
|
||||
"undone_to_entry_id": fmt.Sprintf("%d", entryID),
|
||||
"undone_operation": entry.Operation,
|
||||
}
|
||||
|
||||
err = txn.ExecuteWithTransactionAndMetadata(
|
||||
"undo",
|
||||
fmt.Sprintf("Undid to entry %d: %s", entryID, entry.Operation),
|
||||
metadata,
|
||||
func() error {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to log undo: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUndoStack shows the undo stack
|
||||
func ListUndoStack(repo *core.OnyxRepository) error {
|
||||
oplogPath := filepath.Join(repo.GetOnyxPath(), "oplog")
|
||||
reader := storage.NewOplogReader(oplogPath)
|
||||
|
||||
entries, err := reader.GetUndoStack()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get undo stack: %w", err)
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
fmt.Println("Nothing to undo")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Undo stack (most recent first):")
|
||||
for i, entry := range entries {
|
||||
fmt.Printf("%d. [%d] %s: %s (%s)\n",
|
||||
i+1,
|
||||
entry.ID,
|
||||
entry.Operation,
|
||||
entry.Description,
|
||||
entry.Timestamp.Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user