feat(storage): add VersionedStorage interface with history/diff/branch operations
Extends Storage interface with Dolt-specific version control capabilities: - New VersionedStorage interface in storage/versioned.go with: - History queries: History(), AsOf(), Diff() - Branch operations: Branch(), Merge(), CurrentBranch(), ListBranches() - Commit operations: Commit(), GetCurrentCommit() - Conflict resolution: GetConflicts(), ResolveConflicts() - Helper types: HistoryEntry, DiffEntry, Conflict - DoltStore implements VersionedStorage interface - New CLI commands: - bd history <id> - Show issue version history - bd diff <from> <to> - Show changes between commits/branches - bd branch [name] - List or create branches - bd vc merge <branch> - Merge branch to current - bd vc commit -m <msg> - Create a commit - bd vc status - Show current branch/commit - Added --as-of flag to bd show for time-travel queries - IsVersioned() helper for graceful SQLite backend detection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
gastown/crew/dennis
parent
a7cd9136d8
commit
94581ab233
206
cmd/bd/vc.go
Normal file
206
cmd/bd/vc.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/beads/internal/storage"
|
||||
"github.com/steveyegge/beads/internal/ui"
|
||||
)
|
||||
|
||||
var vcCmd = &cobra.Command{
|
||||
Use: "vc",
|
||||
GroupID: "sync",
|
||||
Short: "Version control operations (requires Dolt backend)",
|
||||
Long: `Version control operations for the beads database.
|
||||
|
||||
These commands require the Dolt storage backend. They provide git-like
|
||||
version control for your issue data, including branching, merging, and
|
||||
viewing history.
|
||||
|
||||
Note: 'bd history', 'bd diff', and 'bd branch' also work for quick access.
|
||||
This subcommand provides additional operations like merge and commit.`,
|
||||
}
|
||||
|
||||
var vcMergeStrategy string
|
||||
|
||||
var vcMergeCmd = &cobra.Command{
|
||||
Use: "merge <branch>",
|
||||
Short: "Merge a branch into the current branch",
|
||||
Long: `Merge the specified branch into the current branch.
|
||||
|
||||
If there are merge conflicts, they will be reported. You can resolve
|
||||
conflicts with --strategy.
|
||||
|
||||
Examples:
|
||||
bd vc merge feature-xyz # Merge feature-xyz into current branch
|
||||
bd vc merge feature-xyz --strategy ours # Merge, preferring our changes on conflict
|
||||
bd vc merge feature-xyz --strategy theirs # Merge, preferring their changes on conflict`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := rootCtx
|
||||
branchName := args[0]
|
||||
|
||||
// Check if storage supports versioning
|
||||
vs, ok := storage.AsVersioned(store)
|
||||
if !ok {
|
||||
FatalErrorRespectJSON("merge requires Dolt backend (current backend does not support versioning)")
|
||||
}
|
||||
|
||||
// Perform merge
|
||||
conflicts, err := vs.Merge(ctx, branchName)
|
||||
if err != nil {
|
||||
FatalErrorRespectJSON("failed to merge branch: %v", err)
|
||||
}
|
||||
|
||||
// Handle conflicts
|
||||
if len(conflicts) > 0 {
|
||||
if vcMergeStrategy != "" {
|
||||
// Auto-resolve conflicts with specified strategy
|
||||
for _, conflict := range conflicts {
|
||||
table := conflict.Field // Field contains table name from GetConflicts
|
||||
if table == "" {
|
||||
table = "issues" // Default to issues table
|
||||
}
|
||||
if err := vs.ResolveConflicts(ctx, table, vcMergeStrategy); err != nil {
|
||||
FatalErrorRespectJSON("failed to resolve conflicts: %v", err)
|
||||
}
|
||||
}
|
||||
if jsonOutput {
|
||||
outputJSON(map[string]interface{}{
|
||||
"merged": branchName,
|
||||
"conflicts": len(conflicts),
|
||||
"resolved_with": vcMergeStrategy,
|
||||
})
|
||||
return
|
||||
}
|
||||
fmt.Printf("Merged %s with %d conflicts resolved using '%s' strategy\n",
|
||||
ui.RenderAccent(branchName), len(conflicts), vcMergeStrategy)
|
||||
return
|
||||
}
|
||||
|
||||
// Report conflicts without auto-resolution
|
||||
if jsonOutput {
|
||||
outputJSON(map[string]interface{}{
|
||||
"merged": branchName,
|
||||
"conflicts": conflicts,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s Merge completed with conflicts:\n\n", ui.RenderAccent("!!"))
|
||||
for _, conflict := range conflicts {
|
||||
fmt.Printf(" - %s\n", conflict.Field)
|
||||
}
|
||||
fmt.Printf("\nResolve conflicts with: bd vc merge %s --strategy [ours|theirs]\n\n", branchName)
|
||||
return
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputJSON(map[string]interface{}{
|
||||
"merged": branchName,
|
||||
"conflicts": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully merged %s\n", ui.RenderAccent(branchName))
|
||||
},
|
||||
}
|
||||
|
||||
var vcCommitMessage string
|
||||
|
||||
var vcCommitCmd = &cobra.Command{
|
||||
Use: "commit",
|
||||
Short: "Create a commit with all staged changes",
|
||||
Long: `Create a new Dolt commit with all current changes.
|
||||
|
||||
Examples:
|
||||
bd vc commit -m "Added new feature issues"
|
||||
bd vc commit --message "Fixed priority on several issues"`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := rootCtx
|
||||
|
||||
if vcCommitMessage == "" {
|
||||
FatalErrorRespectJSON("commit message is required (use -m or --message)")
|
||||
}
|
||||
|
||||
// Check if storage supports versioning
|
||||
vs, ok := storage.AsVersioned(store)
|
||||
if !ok {
|
||||
FatalErrorRespectJSON("commit requires Dolt backend (current backend does not support versioning)")
|
||||
}
|
||||
|
||||
if err := vs.Commit(ctx, vcCommitMessage); err != nil {
|
||||
FatalErrorRespectJSON("failed to commit: %v", err)
|
||||
}
|
||||
|
||||
// Get the new commit hash
|
||||
hash, err := vs.GetCurrentCommit(ctx)
|
||||
if err != nil {
|
||||
hash = "(unknown)"
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputJSON(map[string]interface{}{
|
||||
"committed": true,
|
||||
"hash": hash,
|
||||
"message": vcCommitMessage,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Created commit %s\n", ui.RenderMuted(hash[:8]))
|
||||
},
|
||||
}
|
||||
|
||||
var vcStatusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show current branch and uncommitted changes",
|
||||
Long: `Show the current branch, commit hash, and any uncommitted changes.
|
||||
|
||||
Examples:
|
||||
bd vc status`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := rootCtx
|
||||
|
||||
// Check if storage supports versioning
|
||||
vs, ok := storage.AsVersioned(store)
|
||||
if !ok {
|
||||
FatalErrorRespectJSON("status requires Dolt backend (current backend does not support versioning)")
|
||||
}
|
||||
|
||||
currentBranch, err := vs.CurrentBranch(ctx)
|
||||
if err != nil {
|
||||
FatalErrorRespectJSON("failed to get current branch: %v", err)
|
||||
}
|
||||
|
||||
currentCommit, err := vs.GetCurrentCommit(ctx)
|
||||
if err != nil {
|
||||
currentCommit = "(unknown)"
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputJSON(map[string]interface{}{
|
||||
"branch": currentBranch,
|
||||
"commit": currentCommit,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s Version Control Status\n\n", ui.RenderAccent("📊"))
|
||||
fmt.Printf(" Branch: %s\n", ui.StatusInProgressStyle.Render(currentBranch))
|
||||
fmt.Printf(" Commit: %s\n", ui.RenderMuted(currentCommit[:8]))
|
||||
fmt.Println()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
vcMergeCmd.Flags().StringVar(&vcMergeStrategy, "strategy", "", "Conflict resolution strategy: 'ours' or 'theirs'")
|
||||
vcCommitCmd.Flags().StringVarP(&vcCommitMessage, "message", "m", "", "Commit message")
|
||||
|
||||
vcCmd.AddCommand(vcMergeCmd)
|
||||
vcCmd.AddCommand(vcCommitCmd)
|
||||
vcCmd.AddCommand(vcStatusCmd)
|
||||
rootCmd.AddCommand(vcCmd)
|
||||
}
|
||||
Reference in New Issue
Block a user