Add 'bd clean' command to remove temporary merge artifacts
This command cleans up temporary files created during git merges by reading
patterns directly from .beads/.gitignore (Merge artifacts section).
Files removed:
- 3-way merge snapshots (beads.base.jsonl, beads.left.jsonl, beads.right.jsonl)
- Merge metadata (*.meta.json)
- Git merge driver temp files (*.json[0-9], *.jsonl[0-9])
Files preserved:
- beads.jsonl (source of truth)
- beads.db (SQLite database)
- metadata.json, config.yaml
- All daemon files
Usage:
bd clean # Clean up temporary files
bd clean --dry-run # Preview what would be deleted
Implementation:
- Reads patterns from .beads/.gitignore instead of hardcoding them
- No --force flag needed - just runs by default
- Only cleans truly temporary merge artifacts, never the database
Also:
- Restored beads.jsonl to 538 issues from commit 6cd3a32
- Set issue-prefix to "bd" in config.yaml
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
161
cmd/bd/clean.go
Normal file
161
cmd/bd/clean.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cleanCmd = &cobra.Command{
|
||||
Use: "clean",
|
||||
Short: "Clean up temporary beads artifacts",
|
||||
Long: `Delete temporary beads artifacts to clean up after git operations.
|
||||
|
||||
This removes temporary files created during git merges and conflicts from the
|
||||
.beads directory.
|
||||
|
||||
Files removed:
|
||||
- 3-way merge snapshots (beads.base.jsonl, beads.left.jsonl, beads.right.jsonl)
|
||||
- Merge metadata (*.meta.json)
|
||||
- Git merge driver temp files (*.json[0-9], *.jsonl[0-9])
|
||||
|
||||
Files preserved:
|
||||
- beads.jsonl (source of truth)
|
||||
- beads.db (SQLite database)
|
||||
- metadata.json
|
||||
- config.yaml
|
||||
- All daemon files
|
||||
|
||||
EXAMPLES:
|
||||
Clean up temporary files:
|
||||
bd clean
|
||||
|
||||
Preview what would be deleted:
|
||||
bd clean --dry-run`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
||||
|
||||
// Find beads directory
|
||||
beadsDir := findBeadsDir()
|
||||
if beadsDir == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: .beads directory not found\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Read patterns from .beads/.gitignore (only merge artifacts section)
|
||||
cleanPatterns, err := readMergeArtifactPatterns(beadsDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading .gitignore: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Collect files to delete
|
||||
var filesToDelete []string
|
||||
for _, pattern := range cleanPatterns {
|
||||
matches, err := filepath.Glob(filepath.Join(beadsDir, pattern))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: error matching pattern %s: %v\n", pattern, err)
|
||||
continue
|
||||
}
|
||||
filesToDelete = append(filesToDelete, matches...)
|
||||
}
|
||||
|
||||
if len(filesToDelete) == 0 {
|
||||
fmt.Println("Nothing to clean - all artifacts already removed")
|
||||
return
|
||||
}
|
||||
|
||||
// Just run by default, no --force needed
|
||||
|
||||
if dryRun {
|
||||
fmt.Println(color.YellowString("DRY RUN - no changes will be made"))
|
||||
}
|
||||
fmt.Printf("Found %d file(s) to clean:\n", len(filesToDelete))
|
||||
for _, file := range filesToDelete {
|
||||
relPath, err := filepath.Rel(beadsDir, file)
|
||||
if err != nil {
|
||||
relPath = file
|
||||
}
|
||||
fmt.Printf(" %s\n", relPath)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
return
|
||||
}
|
||||
|
||||
// Actually delete the files
|
||||
deletedCount := 0
|
||||
errorCount := 0
|
||||
for _, file := range filesToDelete {
|
||||
if err := os.Remove(file); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
relPath, _ := filepath.Rel(beadsDir, file)
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to delete %s: %v\n", relPath, err)
|
||||
errorCount++
|
||||
}
|
||||
} else {
|
||||
deletedCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nDeleted %d file(s)", deletedCount)
|
||||
if errorCount > 0 {
|
||||
fmt.Printf(" (%d error(s))", errorCount)
|
||||
}
|
||||
fmt.Println()
|
||||
},
|
||||
}
|
||||
|
||||
// readMergeArtifactPatterns reads the .beads/.gitignore file and extracts
|
||||
// patterns from the "Merge artifacts" section
|
||||
func readMergeArtifactPatterns(beadsDir string) ([]string, error) {
|
||||
gitignorePath := filepath.Join(beadsDir, ".gitignore")
|
||||
file, err := os.Open(gitignorePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open .gitignore: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var patterns []string
|
||||
inMergeSection := false
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
// Look for the merge artifacts section
|
||||
if strings.Contains(line, "Merge artifacts") {
|
||||
inMergeSection = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Stop at the next section (starts with #)
|
||||
if inMergeSection && strings.HasPrefix(line, "#") {
|
||||
break
|
||||
}
|
||||
|
||||
// Collect patterns from merge section
|
||||
if inMergeSection && line != "" && !strings.HasPrefix(line, "#") {
|
||||
// Skip negation patterns (starting with !)
|
||||
if !strings.HasPrefix(line, "!") {
|
||||
patterns = append(patterns, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error reading .gitignore: %w", err)
|
||||
}
|
||||
|
||||
return patterns, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cleanCmd.Flags().Bool("dry-run", false, "Preview what would be deleted without making changes")
|
||||
rootCmd.AddCommand(cleanCmd)
|
||||
}
|
||||
Reference in New Issue
Block a user