feat(sync): add per-field merge strategies for conflict resolution
Implements configurable per-field merge strategies (hq-ew1mbr.11):
- Add FieldStrategy type with strategies: newest, max, union, manual
- Add conflict.fields config section for per-field overrides
- compaction_level defaults to "max" (highest value wins)
- estimated_minutes defaults to "manual" (flags for user resolution)
- labels defaults to "union" (set merge)
Manual conflicts are displayed during sync with resolution options:
bd sync --ours / --theirs, or bd resolve <id> <field> <value>
Config example:
conflict:
strategy: newest
fields:
compaction_level: max
estimated_minutes: manual
labels: union
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
e0dc3a37c3
commit
9a9704b451
@@ -574,16 +574,67 @@ func GetSyncConfig() SyncConfig {
|
||||
|
||||
// ConflictConfig holds the conflict resolution configuration.
|
||||
type ConflictConfig struct {
|
||||
Strategy ConflictStrategy // newest, ours, theirs, manual
|
||||
Strategy ConflictStrategy // newest, ours, theirs, manual (default for all fields)
|
||||
Fields map[string]FieldStrategy // Per-field strategy overrides
|
||||
}
|
||||
|
||||
// GetConflictConfig returns the current conflict resolution configuration.
|
||||
func GetConflictConfig() ConflictConfig {
|
||||
return ConflictConfig{
|
||||
Strategy: GetConflictStrategy(),
|
||||
Fields: GetFieldStrategies(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetFieldStrategies retrieves per-field conflict resolution strategies from config.
|
||||
// Returns a map of field name to strategy (e.g., {"labels": "union", "compaction_level": "max"}).
|
||||
// Invalid strategies are logged and skipped.
|
||||
//
|
||||
// Config key: conflict.fields
|
||||
// Example:
|
||||
//
|
||||
// conflict:
|
||||
// strategy: newest
|
||||
// fields:
|
||||
// compaction_level: max
|
||||
// labels: union
|
||||
// waiters: union
|
||||
// estimated_minutes: manual
|
||||
func GetFieldStrategies() map[string]FieldStrategy {
|
||||
result := make(map[string]FieldStrategy)
|
||||
if v == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
// Get the raw map from config
|
||||
fieldsMap := v.GetStringMapString("conflict.fields")
|
||||
if fieldsMap == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
for field, strategyStr := range fieldsMap {
|
||||
strategy := FieldStrategy(strings.ToLower(strings.TrimSpace(strategyStr)))
|
||||
if !validFieldStrategies[strategy] {
|
||||
logConfigWarning("Warning: invalid conflict.fields.%s strategy %q (valid: %s), skipping\n",
|
||||
field, strategyStr, strings.Join(ValidFieldStrategies(), ", "))
|
||||
continue
|
||||
}
|
||||
result[field] = strategy
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetFieldStrategy returns the merge strategy for a specific field.
|
||||
// Returns the per-field strategy if configured, otherwise returns "newest" (default).
|
||||
func GetFieldStrategy(field string) FieldStrategy {
|
||||
fields := GetFieldStrategies()
|
||||
if strategy, ok := fields[field]; ok {
|
||||
return strategy
|
||||
}
|
||||
return FieldStrategyNewest // Default
|
||||
}
|
||||
|
||||
// FederationConfig holds the federation (Dolt remote) configuration.
|
||||
type FederationConfig struct {
|
||||
Remote string // dolthub://org/beads, gs://bucket/beads, s3://bucket/beads
|
||||
|
||||
Reference in New Issue
Block a user