Skip to content

Fix shallow merge overwriting same-section entries in multi-document YAML#2430

Merged
Siddharth2207 merged 1 commit intomainfrom
2026-02-03-deep-merge-yaml-documents
Feb 3, 2026
Merged

Fix shallow merge overwriting same-section entries in multi-document YAML#2430
Siddharth2207 merged 1 commit intomainfrom
2026-02-03-deep-merge-yaml-documents

Conversation

@findolor
Copy link
Copy Markdown
Collaborator

@findolor findolor commented Feb 3, 2026

Motivation

When merging multiple YAML documents, entries under the same top-level section were being silently lost. For example, if two documents each defined a different deployer under deployers:, only the second document's deployer would survive because the shallow merge replaced the entire hash at each top-level key.

Solution

Implement recursive deep merging via deep_merge_hash() function. When both the base and incoming documents have a hash at the same key, the function recursively merges them instead of overwriting. Non-hash values and mixed types still overwrite as before.

Key changes:

  • Added deep_merge_hash(base, incoming) recursive function
  • Replaced shallow insert loop in emit_documents with deep merge call
  • Added test test_emit_deep_merges_hash_sections to verify merge behavior

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • New Features

    • YAML document merging now supports deep merging of nested hash sections, allowing nested configuration structures from multiple documents to be properly combined rather than overwritten.
  • Tests

    • Added tests to verify deep merge behavior for nested YAML structures, ensuring all configuration entries are correctly preserved during the merge process.

…YAML

Previously, when merging multiple YAML documents, entries under the same
top-level section (e.g., two different deployers under `deployers:`) would
be lost because the shallow merge simply replaced the entire hash. This
implements deep recursive merging so that different entries under the same
section are properly combined.
@findolor findolor self-assigned this Feb 3, 2026
@findolor findolor requested review from 0xgleb and hardyjosh February 3, 2026 13:59
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 3, 2026

Walkthrough

Introduces a private deep_merge_hash helper function to recursively merge YAML hash structures across documents. The implementation replaces simple key-by-key insertion logic to support nested hash merging, where matching hash keys are merged recursively while other values overwrite. Includes a corresponding unit test.

Changes

Cohort / File(s) Summary
YAML Emitter Enhancement
crates/settings/src/yaml/emitter.rs
Added deep_merge_hash helper function for recursive YAML hash merging; updated document emission logic to use deep merge instead of simple insertion; included unit test test_emit_deep_merges_hash_sections to verify nested entries merge correctly across documents.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing shallow merge overwriting entries in multi-document YAML, which directly aligns with the PR's primary objective of introducing deep merging.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2026-02-03-deep-merge-yaml-documents

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Failure to add the new IP will result in interrupted reviews.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/settings/src/yaml/emitter.rs`:
- Around line 558-584: Replace the loose contains-based assertions in
test_emit_deep_merges_hash_sections with an insta snapshot assertion: call
emit_documents(&[get_document(yaml1), get_document(yaml2)]) as before, unwrap
the output string, and use insta::assert_snapshot!(output) (or similar) to
capture the entire merged YAML; if insta isn't already a dev-dependency add it
to Cargo.toml dev-deps and create the expected snapshot file via running tests
to record the correct merged output. Ensure you reference the existing test
function test_emit_deep_merges_hash_sections and the helpers emit_documents and
get_document when making the change.

Comment on lines +558 to +584
#[test]
fn test_emit_deep_merges_hash_sections() {
let yaml1 = r#"
networks:
mainnet:
rpcs:
- https://eth.llamarpc.com
chain-id: 1
deployers:
deployer1:
network: mainnet
address: 0x0000000000000000000000000000000000000001
"#;
let yaml2 = r#"
deployers:
deployer2:
network: mainnet
address: 0x0000000000000000000000000000000000000002
"#;
let result = emit_documents(&[get_document(yaml1), get_document(yaml2)]);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("deployer1:"), "deployer1 should be present");
assert!(output.contains("deployer2:"), "deployer2 should be present");
assert!(output.contains("0x0000000000000000000000000000000000000001"));
assert!(output.contains("0x0000000000000000000000000000000000000002"));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider an insta snapshot for the merged YAML output.

Using a snapshot would capture structure/ordering regressions more reliably than contains checks.

♻️ Possible refactor (if `insta` is already in dev-deps)
-        let output = result.unwrap();
-        assert!(output.contains("deployer1:"), "deployer1 should be present");
-        assert!(output.contains("deployer2:"), "deployer2 should be present");
-        assert!(output.contains("0x0000000000000000000000000000000000000001"));
-        assert!(output.contains("0x0000000000000000000000000000000000000002"));
+        let output = result.unwrap();
+        insta::assert_snapshot!(output);

As per coding guidelines: Prefer insta snapshots for Rust testing.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn test_emit_deep_merges_hash_sections() {
let yaml1 = r#"
networks:
mainnet:
rpcs:
- https://eth.llamarpc.com
chain-id: 1
deployers:
deployer1:
network: mainnet
address: 0x0000000000000000000000000000000000000001
"#;
let yaml2 = r#"
deployers:
deployer2:
network: mainnet
address: 0x0000000000000000000000000000000000000002
"#;
let result = emit_documents(&[get_document(yaml1), get_document(yaml2)]);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("deployer1:"), "deployer1 should be present");
assert!(output.contains("deployer2:"), "deployer2 should be present");
assert!(output.contains("0x0000000000000000000000000000000000000001"));
assert!(output.contains("0x0000000000000000000000000000000000000002"));
}
#[test]
fn test_emit_deep_merges_hash_sections() {
let yaml1 = r#"
networks:
mainnet:
rpcs:
- https://eth.llamarpc.com
chain-id: 1
deployers:
deployer1:
network: mainnet
address: 0x0000000000000000000000000000000000000001
"#;
let yaml2 = r#"
deployers:
deployer2:
network: mainnet
address: 0x0000000000000000000000000000000000000002
"#;
let result = emit_documents(&[get_document(yaml1), get_document(yaml2)]);
assert!(result.is_ok());
let output = result.unwrap();
insta::assert_snapshot!(output);
}
🤖 Prompt for AI Agents
In `@crates/settings/src/yaml/emitter.rs` around lines 558 - 584, Replace the
loose contains-based assertions in test_emit_deep_merges_hash_sections with an
insta snapshot assertion: call emit_documents(&[get_document(yaml1),
get_document(yaml2)]) as before, unwrap the output string, and use
insta::assert_snapshot!(output) (or similar) to capture the entire merged YAML;
if insta isn't already a dev-dependency add it to Cargo.toml dev-deps and create
the expected snapshot file via running tests to record the correct merged
output. Ensure you reference the existing test function
test_emit_deep_merges_hash_sections and the helpers emit_documents and
get_document when making the change.

@Siddharth2207 Siddharth2207 merged commit 1913d31 into main Feb 3, 2026
19 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 3, 2026

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 3, 2026

The author of this PR is on the CodeRabbit Free Plan. In order to use the Chat feature, please upgrade the PR author to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants