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 }