Files
beads/cmd/bd/vc.go
quartz 94581ab233 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>
2026-01-17 01:55:16 -08:00

207 lines
5.6 KiB
Go

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)
}