205 lines
5.0 KiB
Go
205 lines
5.0 KiB
Go
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
|
|
}
|