mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 04:10:51 +08:00
fix(config): avoid writing known defaults during merge
This commit is contained in:
@@ -1098,8 +1098,13 @@ func getOrCreateMapValue(mapNode *yaml.Node, key string) *yaml.Node {
|
|||||||
|
|
||||||
// mergeMappingPreserve merges keys from src into dst mapping node while preserving
|
// mergeMappingPreserve merges keys from src into dst mapping node while preserving
|
||||||
// key order and comments of existing keys in dst. New keys are only added if their
|
// key order and comments of existing keys in dst. New keys are only added if their
|
||||||
// value is non-zero to avoid polluting the config with defaults.
|
// value is non-zero and not a known default to avoid polluting the config with defaults.
|
||||||
func mergeMappingPreserve(dst, src *yaml.Node) {
|
func mergeMappingPreserve(dst, src *yaml.Node, path ...[]string) {
|
||||||
|
var currentPath []string
|
||||||
|
if len(path) > 0 {
|
||||||
|
currentPath = path[0]
|
||||||
|
}
|
||||||
|
|
||||||
if dst == nil || src == nil {
|
if dst == nil || src == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1113,13 +1118,14 @@ func mergeMappingPreserve(dst, src *yaml.Node) {
|
|||||||
sk := src.Content[i]
|
sk := src.Content[i]
|
||||||
sv := src.Content[i+1]
|
sv := src.Content[i+1]
|
||||||
idx := findMapKeyIndex(dst, sk.Value)
|
idx := findMapKeyIndex(dst, sk.Value)
|
||||||
|
childPath := appendPath(currentPath, sk.Value)
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
// Merge into existing value node (always update, even to zero values)
|
// Merge into existing value node (always update, even to zero values)
|
||||||
dv := dst.Content[idx+1]
|
dv := dst.Content[idx+1]
|
||||||
mergeNodePreserve(dv, sv)
|
mergeNodePreserve(dv, sv, childPath)
|
||||||
} else {
|
} else {
|
||||||
// New key: only add if value is non-zero to avoid polluting config with defaults
|
// New key: only add if value is non-zero and not a known default
|
||||||
if isZeroValueNode(sv) {
|
if isKnownDefaultValue(childPath, sv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dst.Content = append(dst.Content, deepCopyNode(sk), deepCopyNode(sv))
|
dst.Content = append(dst.Content, deepCopyNode(sk), deepCopyNode(sv))
|
||||||
@@ -1130,7 +1136,12 @@ func mergeMappingPreserve(dst, src *yaml.Node) {
|
|||||||
// mergeNodePreserve merges src into dst for scalars, mappings and sequences while
|
// mergeNodePreserve merges src into dst for scalars, mappings and sequences while
|
||||||
// reusing destination nodes to keep comments and anchors. For sequences, it updates
|
// reusing destination nodes to keep comments and anchors. For sequences, it updates
|
||||||
// in-place by index.
|
// in-place by index.
|
||||||
func mergeNodePreserve(dst, src *yaml.Node) {
|
func mergeNodePreserve(dst, src *yaml.Node, path ...[]string) {
|
||||||
|
var currentPath []string
|
||||||
|
if len(path) > 0 {
|
||||||
|
currentPath = path[0]
|
||||||
|
}
|
||||||
|
|
||||||
if dst == nil || src == nil {
|
if dst == nil || src == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1139,7 +1150,7 @@ func mergeNodePreserve(dst, src *yaml.Node) {
|
|||||||
if dst.Kind != yaml.MappingNode {
|
if dst.Kind != yaml.MappingNode {
|
||||||
copyNodeShallow(dst, src)
|
copyNodeShallow(dst, src)
|
||||||
}
|
}
|
||||||
mergeMappingPreserve(dst, src)
|
mergeMappingPreserve(dst, src, currentPath)
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
// Preserve explicit null style if dst was null and src is empty sequence
|
// Preserve explicit null style if dst was null and src is empty sequence
|
||||||
if dst.Kind == yaml.ScalarNode && dst.Tag == "!!null" && len(src.Content) == 0 {
|
if dst.Kind == yaml.ScalarNode && dst.Tag == "!!null" && len(src.Content) == 0 {
|
||||||
@@ -1162,7 +1173,7 @@ func mergeNodePreserve(dst, src *yaml.Node) {
|
|||||||
dst.Content[i] = deepCopyNode(src.Content[i])
|
dst.Content[i] = deepCopyNode(src.Content[i])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mergeNodePreserve(dst.Content[i], src.Content[i])
|
mergeNodePreserve(dst.Content[i], src.Content[i], currentPath)
|
||||||
if dst.Content[i] != nil && src.Content[i] != nil &&
|
if dst.Content[i] != nil && src.Content[i] != nil &&
|
||||||
dst.Content[i].Kind == yaml.MappingNode && src.Content[i].Kind == yaml.MappingNode {
|
dst.Content[i].Kind == yaml.MappingNode && src.Content[i].Kind == yaml.MappingNode {
|
||||||
pruneMissingMapKeys(dst.Content[i], src.Content[i])
|
pruneMissingMapKeys(dst.Content[i], src.Content[i])
|
||||||
@@ -1204,6 +1215,56 @@ func findMapKeyIndex(mapNode *yaml.Node, key string) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendPath appends a key to the path, returning a new slice to avoid modifying the original.
|
||||||
|
func appendPath(path []string, key string) []string {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return []string{key}
|
||||||
|
}
|
||||||
|
newPath := make([]string, len(path)+1)
|
||||||
|
copy(newPath, path)
|
||||||
|
newPath[len(path)] = key
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// isKnownDefaultValue returns true if the given node at the specified path
|
||||||
|
// represents a known default value that should not be written to the config file.
|
||||||
|
// This prevents non-zero defaults from polluting the config.
|
||||||
|
func isKnownDefaultValue(path []string, node *yaml.Node) bool {
|
||||||
|
// First check if it's a zero value
|
||||||
|
if isZeroValueNode(node) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match known non-zero defaults by exact dotted path.
|
||||||
|
if len(path) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := strings.Join(path, ".")
|
||||||
|
|
||||||
|
// Check string defaults
|
||||||
|
if node.Kind == yaml.ScalarNode && node.Tag == "!!str" {
|
||||||
|
switch fullPath {
|
||||||
|
case "pprof.addr":
|
||||||
|
return node.Value == DefaultPprofAddr
|
||||||
|
case "remote-management.panel-github-repository":
|
||||||
|
return node.Value == DefaultPanelGitHubRepository
|
||||||
|
case "routing.strategy":
|
||||||
|
return node.Value == "round-robin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check integer defaults
|
||||||
|
if node.Kind == yaml.ScalarNode && node.Tag == "!!int" {
|
||||||
|
switch fullPath {
|
||||||
|
case "error-logs-max-files":
|
||||||
|
return node.Value == "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// isZeroValueNode returns true if the YAML node represents a zero/default value
|
// isZeroValueNode returns true if the YAML node represents a zero/default value
|
||||||
// that should not be written as a new key to preserve config cleanliness.
|
// that should not be written as a new key to preserve config cleanliness.
|
||||||
// For mappings and sequences, recursively checks if all children are zero values.
|
// For mappings and sequences, recursively checks if all children are zero values.
|
||||||
|
|||||||
Reference in New Issue
Block a user