bash-merge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,900 @@
1
+ | 📍 NOTE |
2
+ | --- |
3
+ | RubyGems (the [GitHub org](https://github.com/rubygems/), not the website) [suffered](https://joel.drapper.me/p/ruby-central-security-measures/) a [hostile takeover](https://pup-e.com/blog/goodbye-rubygems/) in September 2025. |
4
+ | Ultimately [4 maintainers](https://www.reddit.com/r/ruby/s/gOk42POCaV) were [hard removed](https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q) and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
+ | It is a [complicated story](https://joel.drapper.me/p/ruby-central-takeover/) which is difficult to [parse quickly](https://joel.drapper.me/p/ruby-central-fact-check/). |
6
+ | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196) and [bundler](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md), and those [policies were not followed](https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/). |
7
+ | I'm adding notes like this to gems because I [don't condone theft](https://joel.drapper.me/p/ruby-central/) of repositories or gems from their rightful owners. |
8
+ | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
9
+ | Disenfranchised former-maintainers have started [gem.coop](https://gem.coop). |
10
+ | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
11
+ | The ["Technology for Humans: Joel Draper"](https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6) podcast episode by [reinteractive](https://reinteractive.com/ruby-on-rails) is the most cogent summary I'm aware of. |
12
+ | See [here](https://github.com/gem-coop/gem.coop/issues/12), [here](https://gem.coop) and [here](https://martinemde.com/2025/10/05/announcing-gem-coop.html) for more info on what comes next. |
13
+ | What I'm doing: A (WIP) proposal for [bundler/gem scopes](https://github.com/galtzo-floss/bundle-namespace), and a (WIP) proposal for a federated [gem server](https://github.com/galtzo-floss/gem-server). |
14
+
15
+ [rubygems-org]: https://github.com/rubygems/
16
+ [draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/
17
+ [draper-takeover]: https://joel.drapper.me/p/ruby-central-takeover/
18
+ [ellen-takeover]: https://pup-e.com/blog/goodbye-rubygems/
19
+ [simi-removed]: https://www.reddit.com/r/ruby/s/gOk42POCaV
20
+ [martin-removed]: https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q
21
+ [draper-lies]: https://joel.drapper.me/p/ruby-central-fact-check/
22
+ [draper-theft]: https://joel.drapper.me/p/ruby-central/
23
+ [reinteractive]: https://reinteractive.com/ruby-on-rails
24
+ [gem-coop]: https://gem.coop
25
+ [gem-naming]: https://github.com/gem-coop/gem.coop/issues/12
26
+ [martin-ann]: https://martinemde.com/2025/10/05/announcing-gem-coop.html
27
+ [gem-scopes]: https://github.com/galtzo-floss/bundle-namespace
28
+ [gem-server]: https://github.com/galtzo-floss/gem-server
29
+ [reinteractive-podcast]: https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6
30
+ [bundler-maint-policy]: https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md
31
+ [rubygems-maint-policy]: https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196
32
+ [policy-fail]: https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/
33
+
34
+ [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg)](https://discord.gg/3qme4XHNKN) [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5](https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg)](https://www.ruby-lang.org/) [![kettle-rb Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg)](https://github.com/kettle-rb)
35
+
36
+ [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg
37
+ [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN
38
+ [🖼️ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg
39
+ [🖼️ruby-lang]: https://www.ruby-lang.org/
40
+ [🖼️kettle-rb-i]: https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg
41
+ [🖼️kettle-rb]: https://github.com/kettle-rb
42
+
43
+ # ☯️ Bash::Merge
44
+
45
+ [![Version](https://img.shields.io/gem/v/bash-merge.svg)](https://bestgems.org/gems/bash-merge) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/kettle-rb/bash-merge.svg)](http://github.com/kettle-rb/bash-merge/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT) [![Downloads Rank](https://img.shields.io/gem/rd/bash-merge.svg)](https://bestgems.org/gems/bash-merge) [![Open Source Helpers](https://www.codetriage.com/kettle-rb/bash-merge/badges/users.svg)](https://www.codetriage.com/kettle-rb/bash-merge) [![CodeCov Test Coverage](https://codecov.io/gh/kettle-rb/bash-merge/graph/badge.svg)](https://codecov.io/gh/kettle-rb/bash-merge) [![Coveralls Test Coverage](https://coveralls.io/repos/github/kettle-rb/bash-merge/badge.svg?branch=main)](https://coveralls.io/github/kettle-rb/bash-merge?branch=main) [![QLTY Test Coverage](https://qlty.sh/gh/kettle-rb/projects/bash-merge/coverage.svg)](https://qlty.sh/gh/kettle-rb/projects/bash-merge/metrics/code?sort=coverageRating) [![QLTY Maintainability](https://qlty.sh/gh/kettle-rb/projects/bash-merge/maintainability.svg)](https://qlty.sh/gh/kettle-rb/projects/bash-merge) [![CI Heads](https://github.com/kettle-rb/bash-merge/actions/workflows/heads.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/heads.yml) [![CI Runtime Dependencies @ HEAD](https://github.com/kettle-rb/bash-merge/actions/workflows/dep-heads.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/dep-heads.yml) [![CI Current](https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml) [![CI Truffle Ruby](https://github.com/kettle-rb/bash-merge/actions/workflows/truffle.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/truffle.yml) [![Deps Locked](https://github.com/kettle-rb/bash-merge/actions/workflows/locked_deps.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/locked_deps.yml) [![Deps Unlocked](https://github.com/kettle-rb/bash-merge/actions/workflows/unlocked_deps.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/unlocked_deps.yml) [![CI Supported](https://github.com/kettle-rb/bash-merge/actions/workflows/supported.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/supported.yml) [![CI Test Coverage](https://github.com/kettle-rb/bash-merge/actions/workflows/coverage.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/coverage.yml) [![CI Style](https://github.com/kettle-rb/bash-merge/actions/workflows/style.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/style.yml) [![CodeQL](https://github.com/kettle-rb/bash-merge/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/security/code-scanning) [![Apache SkyWalking Eyes License Compatibility Check](https://github.com/kettle-rb/bash-merge/actions/workflows/license-eye.yml/badge.svg)](https://github.com/kettle-rb/bash-merge/actions/workflows/license-eye.yml)
46
+
47
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know](https://discord.gg/3qme4XHNKN), as I may have missed the [discord notification](https://discord.gg/3qme4XHNKN).
48
+
49
+ -----
50
+ `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
51
+
52
+ [![OpenCollective Backers](https://opencollective.com/kettle-rb/backers/badge.svg?style=flat)](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors](https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat)](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github](https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github)](https://github.com/sponsors/pboling) [![Liberapay Goal Progress](https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat)](https://liberapay.com/pboling/donate) [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal)](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat)](https://www.buymeacoffee.com/pboling) [![Donate on Polar](https://img.shields.io/badge/polar-donate-a51611.svg?style=flat)](https://polar.sh/pboling) [![Donate at ko-fi.com](https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat)](https://ko-fi.com/O5O86SNP4)
53
+
54
+ ## 🌻 Synopsis
55
+
56
+ Bash::Merge is a standalone Ruby module that intelligently merges two versions of a Bash script using tree-sitter AST analysis. It's like a smart "git merge" specifically designed for shell scripts. Built on top of [ast-merge](https://github.com/kettle-rb/ast-merge), it shares the same architecture as [prism-merge](https://github.com/kettle-rb/prism-merge) for Ruby source files.
57
+
58
+ ### Key Features
59
+
60
+ - **Tree-Sitter Powered**: Uses tree-sitter-bash for accurate AST parsing
61
+ - **Script-Aware**: Understands Bash syntax including functions, variables, and commands
62
+ - **Intelligent**: Matches functions and variable assignments by name
63
+ - **Comment-Preserving**: Comments are preserved in their context
64
+ - **Shebang Handling**: Properly handles `#!/bin/bash` and similar shebangs
65
+ - **Freeze Block Support**: Respects freeze markers (default: `bash-merge:freeze` / `bash-merge:unfreeze`) for merge control - customizable to match your project's conventions
66
+ - **Full Provenance**: Tracks origin of every node
67
+ - **Standalone**: Minimal dependencies - just `ast-merge` and `ruby_tree_sitter`
68
+ - **Customizable**:
69
+ - `signature_generator` - callable custom signature generators
70
+ - `preference` - setting of `:template`, `:destination`, or a Hash for per-node-type preferences
71
+ - `node_splitter` - Hash mapping node types to callables for per-node-type merge customization (see [ast-merge](https://github.com/kettle-rb/ast-merge) docs)
72
+ - `add_template_only_nodes` - setting to retain nodes that do not exist in destination
73
+ - `freeze_token` - customize freeze block markers (default: `"bash-merge"`)
74
+ ### Supported Node Types
75
+
76
+ | Node Type | Signature Format | Matching Behavior |
77
+ | --- | --- | --- |
78
+ | Function Definition | `[:function, name]` | Functions match by name |
79
+ | Variable Assignment | `[:assignment, name]` | Variables match by name |
80
+ | Command | `[:command, name, args...]` | Commands match by name and arguments |
81
+ | Comment | `[:comment, text]` | Comments preserved in context |
82
+ | Pipeline | `[:pipeline, commands...]` | Pipelines match by command sequence |
83
+ | If Statement | `[:if, condition_sig]` | Conditionals match by condition signature |
84
+ | For Loop | `[:for, variable]` | For loops match by loop variable |
85
+ | While Loop | `[:while, condition_sig]` | While loops match by condition signature |
86
+
87
+ ### Example
88
+
89
+ ``` ruby
90
+ require "bash/merge"
91
+
92
+ template = File.read("template.sh")
93
+ destination = File.read("destination.sh")
94
+
95
+ merger = Bash::Merge::SmartMerger.new(template, destination)
96
+ result = merger.merge
97
+
98
+ File.write("merged.sh", result.to_bash)
99
+ ```
100
+
101
+
102
+ ### The `*-merge` Gem Family
103
+
104
+ The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree_haver][tree_haver], which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
105
+
106
+ | Gem | Format | Parser Backend(s) | Description |
107
+ |------------------------------------------|----------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
108
+ | [tree_haver][tree_haver] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
109
+ | [ast-merge][ast-merge] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
110
+ | [prism-merge][prism-merge] | Ruby | [Prism][prism] | Smart merge for Ruby source files |
111
+ | [psych-merge][psych-merge] | YAML | [Psych][psych] | Smart merge for YAML files |
112
+ | [json-merge][json-merge] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
113
+ | [jsonc-merge][jsonc-merge] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
114
+ | [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
115
+ | [rbs-merge][rbs-merge] | RBS | [RBS][rbs] | Smart merge for Ruby type signatures |
116
+ | [dotenv-merge][dotenv-merge] | Dotenv | internal | Smart merge for `.env` files |
117
+ | [toml-merge][toml-merge] | TOML | [Citrus + toml-rb][toml-rb] (default, via tree_haver), [tree-sitter-toml][ts-toml] (via tree_haver) | Smart merge for TOML files |
118
+ | [markdown-merge][markdown-merge] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
119
+ | [markly-merge][markly-merge] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
120
+ | [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
121
+
122
+ **Example implementations** for the gem templating use case:
123
+
124
+ | Gem | Purpose | Description |
125
+ | --- | --- | --- |
126
+ | [kettle-dev](https://github.com/kettle-rb/kettle-dev) | Gem Development | Gem templating tool using `*-merge` gems |
127
+ | [kettle-jem](https://github.com/kettle-rb/kettle-jem) | Gem Templating | Gem template library with smart merge support |
128
+
129
+ [tree_haver]: https://github.com/kettle-rb/tree_haver
130
+ [ast-merge]: https://github.com/kettle-rb/ast-merge
131
+ [prism-merge]: https://github.com/kettle-rb/prism-merge
132
+ [psych-merge]: https://github.com/kettle-rb/psych-merge
133
+ [json-merge]: https://github.com/kettle-rb/json-merge
134
+ [jsonc-merge]: https://github.com/kettle-rb/jsonc-merge
135
+ [bash-merge]: https://github.com/kettle-rb/bash-merge
136
+ [rbs-merge]: https://github.com/kettle-rb/rbs-merge
137
+ [dotenv-merge]: https://github.com/kettle-rb/dotenv-merge
138
+ [toml-merge]: https://github.com/kettle-rb/toml-merge
139
+ [markdown-merge]: https://github.com/kettle-rb/markdown-merge
140
+ [markly-merge]: https://github.com/kettle-rb/markly-merge
141
+ [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
142
+ [kettle-dev]: https://github.com/kettle-rb/kettle-dev
143
+ [kettle-jem]: https://github.com/kettle-rb/kettle-jem
144
+ [prism]: https://github.com/ruby/prism
145
+ [psych]: https://github.com/ruby/psych
146
+ [ts-json]: https://github.com/tree-sitter/tree-sitter-json
147
+ [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
148
+ [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
149
+ [rbs]: https://github.com/ruby/rbs
150
+ [toml-rb]: https://github.com/emancu/toml-rb
151
+ [markly]: https://github.com/ioquatix/markly
152
+ [commonmarker]: https://github.com/gjtorikian/commonmarker
153
+
154
+
155
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
156
+ [dotenv]: https://github.com/bkeepers/dotenv
157
+
158
+ ## 💡 Info you can shake a stick at
159
+
160
+ | Tokens to Remember | [![Gem name](https://img.shields.io/badge/name-bash--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red)](https://bestgems.org/gems/bash-merge) [![Gem namespace](https://img.shields.io/badge/namespace-Bash::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white)](https://github.com/kettle-rb/bash-merge) |
161
+ | --- | --- |
162
+ | Works with JRuby | [![JRuby 10.0 Compat](https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml) [![JRuby HEAD Compat](https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue)](https://github.com/kettle-rb/bash-merge/actions/workflows/heads.yml) |
163
+ | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat](https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink)](https://github.com/kettle-rb/bash-merge/actions/workflows/truffle.yml) [![Truffle Ruby 24.1 Compat](https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml) |
164
+ | Works with MRI Ruby 3 | [![Ruby 3.2 Compat](https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white)](https://github.com/kettle-rb/bash-merge/actions/workflows/supported.yml) [![Ruby 3.3 Compat](https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white)](https://github.com/kettle-rb/bash-merge/actions/workflows/supported.yml) [![Ruby 3.4 Compat](https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml) [![Ruby HEAD Compat](https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue)](https://github.com/kettle-rb/bash-merge/actions/workflows/heads.yml) |
165
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends](https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white)](https://app.daily.dev/squads/rubyfriends) [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN) [![Get help from me on Upwork](https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white)](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share) [![Get help from me on Codementor](https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github) |
166
+ | Source | [![Source on GitLab.com](https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange)](https://gitlab.com/kettle-rb/bash-merge/) [![Source on CodeBerg.org](https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue)](https://codeberg.org/kettle-rb/bash-merge) [![Source on Github.com](https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green)](https://github.com/kettle-rb/bash-merge) [![The best SHA: dQw4w9WgXcQ\!](https://img.shields.io/badge/KLOC-4.308-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue)](https://www.youtube.com/watch?v=dQw4w9WgXcQ) |
167
+ | Documentation | [![Current release on RubyDoc.info](https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white)](http://rubydoc.info/gems/bash-merge) [![YARD on Galtzo.com](https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white)](https://bash-merge.galtzo.com) [![Maintainer Blog](https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange)](http://www.railsbling.com/tags/bash-merge) [![GitLab Wiki](https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white)](https://gitlab.com/kettle-rb/bash-merge/-/wikis/home) [![GitHub Wiki](https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/kettle-rb/bash-merge/wiki) |
168
+ | Compliance | [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT) [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes](https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache)](https://dev.to/galtzo/how-to-check-license-compatibility-41h0) [![📄ilo-declaration-img](https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat)](https://www.ilo.org/declaration/lang--en/index.htm) [![Security Policy](https://img.shields.io/badge/security-policy-259D6C.svg?style=flat)](SECURITY.md) [![Contributor Covenant 2.1](https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg)](CODE_OF_CONDUCT.md) [![SemVer 2.0.0](https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat)](https://semver.org/spec/v2.0.0.html) |
169
+ | Style | [![Enforced Code Style Linter](https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white)](https://github.com/rubocop-lts/rubocop-lts) [![Keep-A-Changelog 1.0.0](https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat)](https://keepachangelog.com/en/1.0.0/) [![Gitmoji Commits](https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square)](https://gitmoji.dev) [![Compatibility appraised by: appraisal2](https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white)](https://github.com/appraisal-rb/appraisal2) |
170
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn](https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling)](http://www.linkedin.com/in/peterboling) [![Follow Me on Ruby.Social](https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo)](https://ruby.social/@galtzo) [![Follow Me on Bluesky](https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white)](https://bsky.app/profile/galtzo.com) [![Contact Maintainer](https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red)](http://www.railsbling.com/contact) [![My technical writing](https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white)](https://dev.to/galtzo) |
171
+ | `...` 💖 | [![Find Me on WellFound:](https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound)](https://wellfound.com/u/peter-boling) [![Find Me on CrunchBase](https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase)](https://www.crunchbase.com/person/peter-boling) [![My LinkTree](https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree)](https://linktr.ee/galtzo) [![More About Me](https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white)](https://about.me/peter.boling) [🧊](https://codeberg.org/pboling) [🐙](https://github.org/pboling) [🛖](https://sr.ht/~galtzo/) [🧪](https://gitlab.com/pboling) |
172
+
173
+ ### Compatibility
174
+
175
+ Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
176
+
177
+ | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
178
+ | --- | --- |
179
+ | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2](https://github.com/appraisal-rb/appraisal2) ✨ |
180
+
181
+ ### Federated DVCS
182
+
183
+ <details markdown="1">
184
+ <summary>Find this repo on federated forges (Coming soon!)</summary>
185
+
186
+ | Federated [DVCS](https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/) Repository | Status | Issues | PRs | Wiki | CI | Discussions |
187
+ | --- | --- | --- | --- | --- | --- | --- |
188
+ | 🧪 [kettle-rb/bash-merge on GitLab](https://gitlab.com/kettle-rb/bash-merge/) | The Truth | [💚](https://gitlab.com/kettle-rb/bash-merge/-/issues) | [💚](https://gitlab.com/kettle-rb/bash-merge/-/merge_requests) | [💚](https://gitlab.com/kettle-rb/bash-merge/-/wikis/home) | 🐭 Tiny Matrix | ➖ |
189
+ | 🧊 [kettle-rb/bash-merge on CodeBerg](https://codeberg.org/kettle-rb/bash-merge) | An Ethical Mirror ([Donate](https://donate.codeberg.org/)) | [💚](https://codeberg.org/kettle-rb/bash-merge/issues) | [💚](https://codeberg.org/kettle-rb/bash-merge/pulls) | ➖ | ⭕️ No Matrix | ➖ |
190
+ | 🐙 [kettle-rb/bash-merge on GitHub](https://github.com/kettle-rb/bash-merge) | Another Mirror | [💚](https://github.com/kettle-rb/bash-merge/issues) | [💚](https://github.com/kettle-rb/bash-merge/pulls) | [💚](https://github.com/kettle-rb/bash-merge/wiki) | 💯 Full Matrix | [💚](https://github.com/kettle-rb/bash-merge/discussions) |
191
+ | 🎮️ [Discord Server](https://discord.gg/3qme4XHNKN) | [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN) | [Let's](https://discord.gg/3qme4XHNKN) | [talk](https://discord.gg/3qme4XHNKN) | [about](https://discord.gg/3qme4XHNKN) | [this](https://discord.gg/3qme4XHNKN) | [library\!](https://discord.gg/3qme4XHNKN) |
192
+
193
+ </details>
194
+
195
+ [gh-discussions]: https://github.com/kettle-rb/bash-merge/discussions
196
+
197
+ ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/bash-merge)](https://tidelift.com/subscription/pkg/rubygems-bash-merge?utm_source=rubygems-bash-merge&utm_medium=referral&utm_campaign=readme)
198
+
199
+ Available as part of the Tidelift Subscription.
200
+
201
+ <details markdown="1">
202
+ <summary>Need enterprise-level guarantees?</summary>
203
+
204
+ The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
205
+
206
+ [![Get help from me on Tidelift](https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white)](https://tidelift.com/subscription/pkg/rubygems-bash-merge?utm_source=rubygems-bash-merge&utm_medium=referral&utm_campaign=readme)
207
+
208
+ - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
209
+ - 💡Tidelift is part of [Sonar](https://blog.tidelift.com/tidelift-joins-sonar)
210
+ - 💡Tidelift pays maintainers to maintain the software you depend on\!<br/>📊`@`Pointy Haired Boss: An [enterprise support](https://tidelift.com/subscription/pkg/rubygems-bash-merge?utm_source=rubygems-bash-merge&utm_medium=referral&utm_campaign=readme) subscription is "[never gonna let you down](https://www.youtube.com/watch?v=dQw4w9WgXcQ)", and *supports* open source maintainers
211
+ Alternatively:
212
+
213
+ - [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
214
+ - [![Get help from me on Upwork](https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white)](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share)
215
+ - [![Get help from me on Codementor](https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github)
216
+ </details>
217
+
218
+ ## ✨ Installation
219
+
220
+ Install the gem and add to the application's Gemfile by executing:
221
+
222
+ ``` console
223
+ bundle add bash-merge
224
+ ```
225
+
226
+ If bundler is not being used to manage dependencies, install the gem by executing:
227
+
228
+ ``` console
229
+ gem install bash-merge
230
+ ```
231
+
232
+ ### 🔒 Secure Installation
233
+
234
+ <details markdown="1">
235
+ <summary>For Medium or High Security Installations</summary>
236
+
237
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512](https://gitlab.com/kettle-rb/bash-merge/-/tree/main/checksums) checksums by
238
+ [stone\_checksums](https://github.com/galtzo-floss/stone_checksums). Be sure the gem you install hasn’t been tampered with
239
+ by following the instructions below.
240
+
241
+ Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
242
+
243
+ ``` console
244
+ gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
245
+ ```
246
+
247
+ You only need to do that once. Then proceed to install with:
248
+
249
+ ``` console
250
+ gem install bash-merge -P HighSecurity
251
+ ```
252
+
253
+ The `HighSecurity` trust profile will verify signed gems, and not allow the installation of unsigned dependencies.
254
+
255
+ If you want to up your security game full-time:
256
+
257
+ ``` console
258
+ bundle config set --global trust-policy MediumSecurity
259
+ ```
260
+
261
+ `MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed.
262
+
263
+ NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
264
+
265
+ </details>
266
+
267
+ ### 🌳 Tree-Sitter Parser Installation
268
+
269
+ This gem requires the `tree-sitter-bash` parser library to be installed on your system.
270
+ The parser is a native shared library (`.so` on Linux, `.dylib` on macOS) that provides
271
+ Bash syntax parsing capabilities.
272
+
273
+ #### Option 1: Pre-built Binaries (Recommended)
274
+
275
+ Download pre-built parsers from [Faveod/tree-sitter-parsers](https://github.com/Faveod/tree-sitter-parsers/releases):
276
+
277
+ **Linux (x64):**
278
+ ``` console
279
+ # Download and extract
280
+ curl -Lo parsers.tar.gz https://github.com/Faveod/tree-sitter-parsers/releases/download/v4.10/tree-sitter-parsers-4.10-linux-x64.tar.gz
281
+ tar -xzf parsers.tar.gz
282
+
283
+ # Install system-wide (requires sudo)
284
+ sudo cp libtree-sitter-bash.so /usr/lib/
285
+ sudo ldconfig
286
+
287
+ # Or install locally and set environment variable
288
+ mkdir -p ~/.local/lib/tree-sitter
289
+ cp libtree-sitter-bash.so ~/.local/lib/tree-sitter/
290
+ export TREE_SITTER_BASH_PATH="$HOME/.local/lib/tree-sitter/libtree-sitter-bash.so"
291
+ ```
292
+
293
+ **Debian/Ubuntu (amd64):**
294
+ ``` console
295
+ curl -Lo parsers.deb https://github.com/Faveod/tree-sitter-parsers/releases/download/v4.10/tree-sitter-parsers-4.10-amd64.deb
296
+ sudo dpkg -i parsers.deb
297
+ ```
298
+
299
+ **macOS (Apple Silicon):**
300
+ ``` console
301
+ curl -Lo parsers.tar.gz https://github.com/Faveod/tree-sitter-parsers/releases/download/v4.10/tree-sitter-parsers-4.10-macos-arm64.tar.gz
302
+ tar -xzf parsers.tar.gz
303
+
304
+ # Install to a location and set environment variable
305
+ mkdir -p ~/.local/lib/tree-sitter
306
+ cp libtree-sitter-bash.dylib ~/.local/lib/tree-sitter/
307
+ export TREE_SITTER_BASH_PATH="$HOME/.local/lib/tree-sitter/libtree-sitter-bash.dylib"
308
+ ```
309
+
310
+ #### Option 2: Build from Source
311
+
312
+ ``` console
313
+ git clone https://github.com/tree-sitter/tree-sitter-bash.git
314
+ cd tree-sitter-bash
315
+ make
316
+ sudo cp libtree-sitter-bash.so /usr/lib/ # Linux
317
+ # or
318
+ sudo cp libtree-sitter-bash.dylib /usr/local/lib/ # macOS
319
+ ```
320
+
321
+ #### Option 3: Package Manager
322
+
323
+ Some package managers provide tree-sitter parsers:
324
+
325
+ **Fedora Atomic (Silverblue, Kinoite, Bazzite, Aurora, etc.):**
326
+ ``` console
327
+ # Install via rpm-ostree (requires reboot)
328
+ rpm-ostree install libtree-sitter-bash
329
+
330
+ # The library will be installed to /usr/lib64/libtree-sitter-bash.so
331
+ # You may need to set the environment variable:
332
+ export TREE_SITTER_BASH_PATH="/usr/lib64/libtree-sitter-bash.so"
333
+ ```
334
+
335
+ **Fedora (traditional):**
336
+ ``` console
337
+ sudo dnf install libtree-sitter-bash
338
+ ```
339
+
340
+ **Arch Linux:**
341
+ ``` console
342
+ # Check AUR for tree-sitter-bash
343
+ yay -S tree-sitter-bash
344
+ ```
345
+
346
+ #### Environment Variable
347
+
348
+ If the parser is not in a standard location (`/usr/lib/`, `/usr/lib64/`, `/usr/local/lib/`),
349
+ set the `TREE_SITTER_BASH_PATH` environment variable to point to the parser library:
350
+
351
+ ``` console
352
+ export TREE_SITTER_BASH_PATH="/path/to/libtree-sitter-bash.so"
353
+ ```
354
+
355
+ **Note:** Some distributions install the library with a version number suffix
356
+ (e.g., `libtree-sitter-bash.so.14` instead of `libtree-sitter-bash.so`).
357
+ If the gem can't find the parser, check for versioned files and either:
358
+ - Set `TREE_SITTER_BASH_PATH` to the full versioned path, or
359
+ - Create a symlink: `sudo ln -s /usr/lib64/libtree-sitter-bash.so.14 /usr/lib64/libtree-sitter-bash.so`
360
+ Add this to your shell profile (`.bashrc`, `.zshrc`, etc.) for persistence.
361
+
362
+ ### 💎 Ruby Interface Gems
363
+
364
+ In addition to the tree-sitter parser library, you need a Ruby gem that provides
365
+ bindings to tree-sitter. Choose **one** of the following based on your Ruby implementation:
366
+
367
+ | Gem | Ruby Support | Description |
368
+ | --- | --- | --- |
369
+ | [ruby\_tree\_sitter](https://github.com/Faveod/ruby_tree_sitter) | MRI only | C extension bindings (recommended for MRI) |
370
+ | [tree\_stump](https://github.com/nickstenning/tree_stump) | MRI (maybe JRuby) | Rust-based bindings via Rutie |
371
+ | [ffi](https://github.com/ffi/ffi) | MRI, JRuby, TruffleRuby | Generic FFI bindings (used by tree\_haver's FFI backend) |
372
+
373
+ [ruby_tree_sitter]: https://github.com/Faveod/ruby_tree_sitter
374
+ [tree_stump]: https://github.com/nickstenning/tree_stump
375
+ [ffi]: https://github.com/ffi/ffi
376
+
377
+ #### For MRI Ruby (Recommended)
378
+
379
+ ``` console
380
+ gem install ruby_tree_sitter
381
+ ```
382
+
383
+ Or add to your Gemfile:
384
+
385
+ ``` ruby
386
+ gem "ruby_tree_sitter", "~> 2.0"
387
+ ```
388
+
389
+ #### For JRuby or TruffleRuby
390
+
391
+ ``` console
392
+ gem install ffi
393
+ ```
394
+
395
+ Or add to your Gemfile:
396
+
397
+ ``` ruby
398
+ gem "ffi"
399
+ ```
400
+
401
+ The `tree_haver` gem (a dependency of bash-merge) will automatically detect and use
402
+ the appropriate backend based on which gems are available.
403
+
404
+ **Note:** The `ruby_tree_sitter` gem only compiles on MRI Ruby. For JRuby or TruffleRuby,
405
+ you must use the FFI backend.
406
+
407
+ ## ⚙️ Configuration
408
+
409
+ ``` ruby
410
+ merger = Bash::Merge::SmartMerger.new(
411
+ template_content,
412
+ dest_content,
413
+ # Which version to prefer when nodes match
414
+ # :destination (default) - keep destination code
415
+ # :template - use template code
416
+ preference: :destination,
417
+
418
+ # Whether to add template-only nodes to the result
419
+ # false (default) - only include nodes that exist in destination
420
+ # true - include all template nodes (functions, variables, etc.)
421
+ add_template_only_nodes: false,
422
+
423
+ # Token for freeze block markers
424
+ # Default: "bash-merge"
425
+ # Looks for: # bash-merge:freeze / # bash-merge:unfreeze
426
+ freeze_token: "bash-merge",
427
+
428
+ # Custom signature generator (optional)
429
+ # Receives a node, returns a signature array or nil
430
+ signature_generator: ->(node) { [:function, node.name] if node.type == :function_definition },
431
+ )
432
+ ```
433
+
434
+ ## 🔧 Basic Usage
435
+
436
+ ### Simple Merge
437
+
438
+ ``` ruby
439
+ require "bash/merge"
440
+
441
+ # Template defines the structure
442
+ template = <<~BASH
443
+ #!/bin/bash
444
+
445
+ # Configuration
446
+ APP_NAME="myapp"
447
+ DEBUG=false
448
+
449
+ # Main function
450
+ main() {
451
+ echo "Starting $APP_NAME"
452
+ setup
453
+ run
454
+ }
455
+
456
+ setup() {
457
+ echo "Setting up..."
458
+ }
459
+
460
+ run() {
461
+ echo "Running..."
462
+ }
463
+
464
+ main "$@"
465
+ BASH
466
+
467
+ # Destination has customizations
468
+ destination = <<~BASH
469
+ #!/bin/bash
470
+
471
+ # Configuration
472
+ APP_NAME="myapp-custom"
473
+ DEBUG=true
474
+ LOG_FILE="/var/log/myapp.log"
475
+
476
+ # Main function with custom logging
477
+ main() {
478
+ echo "Starting $APP_NAME" | tee -a "$LOG_FILE"
479
+ setup
480
+ run
481
+ }
482
+
483
+ setup() {
484
+ echo "Custom setup..."
485
+ }
486
+
487
+ main "$@"
488
+ BASH
489
+
490
+ merger = Bash::Merge::SmartMerger.new(template, destination)
491
+ result = merger.merge
492
+ puts result.to_bash
493
+ ```
494
+
495
+ ### Using Freeze Blocks
496
+
497
+ Freeze blocks protect sections from being overwritten during merge:
498
+
499
+ ``` bash
500
+ #!/bin/bash
501
+
502
+ # Configuration
503
+ APP_NAME="myapp"
504
+
505
+ # bash-merge:freeze Custom credentials
506
+ DB_USER="production_user"
507
+ DB_PASS="super_secret_password"
508
+ API_KEY="my_production_api_key"
509
+ # bash-merge:unfreeze
510
+
511
+ # Standard functions
512
+ main() {
513
+ echo "Starting $APP_NAME"
514
+ }
515
+
516
+ main "$@"
517
+ ```
518
+
519
+ Content between `# bash-merge:freeze` and `# bash-merge:unfreeze` markers is preserved from the destination file, regardless of what the template contains.
520
+
521
+ ### Adding Template-Only Nodes
522
+
523
+ ``` ruby
524
+ merger = Bash::Merge::SmartMerger.new(
525
+ template,
526
+ destination,
527
+ add_template_only_nodes: true,
528
+ )
529
+ result = merger.merge
530
+ # Result includes functions/variables from template that don't exist in destination
531
+ ```
532
+
533
+ ## 🦷 FLOSS Funding
534
+
535
+ While kettle-rb tools are free software and will always be, the project would benefit immensely from some funding.
536
+ Raising a monthly budget of... "dollars" would make the project more sustainable.
537
+
538
+ We welcome both individual and corporate sponsors\! We also offer a
539
+ wide array of funding channels to account for your preferences
540
+ (although currently [Open Collective](https://opencollective.com/kettle-rb) is our preferred funding platform).
541
+
542
+ **If you're working in a company that's making significant use of kettle-rb tools we'd
543
+ appreciate it if you suggest to your company to become a kettle-rb sponsor.**
544
+
545
+ You can support the development of kettle-rb tools via
546
+ [GitHub Sponsors](https://github.com/sponsors/pboling),
547
+ [Liberapay](https://liberapay.com/pboling/donate),
548
+ [PayPal](https://www.paypal.com/paypalme/peterboling),
549
+ [Open Collective](https://opencollective.com/kettle-rb)
550
+ and [Tidelift](https://tidelift.com/subscription/pkg/rubygems-bash-merge?utm_source=rubygems-bash-merge&utm_medium=referral&utm_campaign=readme).
551
+
552
+ | 📍 NOTE |
553
+ | --- |
554
+ | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
555
+
556
+ ### Open Collective for Individuals
557
+
558
+ Support us with a monthly donation and help us continue our activities. \[[Become a backer](https://opencollective.com/kettle-rb#backer)\]
559
+
560
+ NOTE: [kettle-readme-backers](https://github.com/kettle-rb/bash-merge/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
561
+
562
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
563
+ No backers yet. Be the first\!
564
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
565
+
566
+ ### Open Collective for Organizations
567
+
568
+ Become a sponsor and get your logo on our README on GitHub with a link to your site. \[[Become a sponsor](https://opencollective.com/kettle-rb#sponsor)\]
569
+
570
+ NOTE: [kettle-readme-backers](https://github.com/kettle-rb/bash-merge/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
571
+
572
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
573
+ No sponsors yet. Be the first\!
574
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
575
+
576
+ [kettle-readme-backers]: https://github.com/kettle-rb/bash-merge/blob/main/exe/kettle-readme-backers
577
+
578
+ ### Another way to support open-source
579
+
580
+ I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
581
+
582
+ If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`.
583
+
584
+ I’m developing a new library, [floss\_funding](https://github.com/galtzo-floss/floss_funding), designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
585
+
586
+ **[Floss-Funding.dev](https://floss-funding.dev): 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
587
+
588
+ [![OpenCollective Backers](https://opencollective.com/kettle-rb/backers/badge.svg?style=flat)](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors](https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat)](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github](https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github)](https://github.com/sponsors/pboling) [![Liberapay Goal Progress](https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat)](https://liberapay.com/pboling/donate) [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal)](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat)](https://www.buymeacoffee.com/pboling) [![Donate on Polar](https://img.shields.io/badge/polar-donate-a51611.svg?style=flat)](https://polar.sh/pboling) [![Donate to my FLOSS efforts at ko-fi.com](https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat)](https://ko-fi.com/O5O86SNP4) [![Donate to my FLOSS efforts using Patreon](https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat)](https://patreon.com/galtzo)
589
+
590
+ ## 🔐 Security
591
+
592
+ See [SECURITY.md](SECURITY.md).
593
+
594
+ ## 🤝 Contributing
595
+
596
+ If you need some ideas of where to help, you could work on adding more code coverage,
597
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues](https://github.com/kettle-rb/bash-merge/issues), or [PRs](https://github.com/kettle-rb/bash-merge/pulls),
598
+ or use the gem and think about how it could be better.
599
+
600
+ We [![Keep A Changelog](https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat)](https://keepachangelog.com/en/1.0.0/) so if you make changes, remember to update it.
601
+
602
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for more detailed instructions.
603
+
604
+ ### 🚀 Release Instructions
605
+
606
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
607
+
608
+ ### Code Coverage
609
+
610
+ [![Coverage Graph](https://codecov.io/gh/kettle-rb/bash-merge/graphs/tree.svg)](https://codecov.io/gh/kettle-rb/bash-merge)
611
+
612
+ [![Coveralls Test Coverage](https://coveralls.io/repos/github/kettle-rb/bash-merge/badge.svg?branch=main)](https://coveralls.io/github/kettle-rb/bash-merge?branch=main)
613
+
614
+ [![QLTY Test Coverage](https://qlty.sh/gh/kettle-rb/projects/bash-merge/coverage.svg)](https://qlty.sh/gh/kettle-rb/projects/bash-merge/metrics/code?sort=coverageRating)
615
+
616
+ ### 🪇 Code of Conduct
617
+
618
+ Everyone interacting with this project's codebases, issue trackers,
619
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1](https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg)](CODE_OF_CONDUCT.md).
620
+
621
+ ## 🌈 Contributors
622
+
623
+ [![Contributors](https://contrib.rocks/image?repo=kettle-rb/bash-merge)](https://github.com/kettle-rb/bash-merge/graphs/contributors)
624
+
625
+ Made with [contributors-img](https://contrib.rocks).
626
+
627
+ Also see GitLab Contributors: <https://gitlab.com/kettle-rb/bash-merge/-/graphs/main>
628
+
629
+ <details>
630
+ <summary>⭐️ Star History</summary>
631
+
632
+ <a href="https://star-history.com/#kettle-rb/bash-merge&Date">
633
+ <picture>
634
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kettle-rb/bash-merge&type=Date&theme=dark" />
635
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kettle-rb/bash-merge&type=Date" />
636
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kettle-rb/bash-merge&type=Date" />
637
+ </picture>
638
+ </a>
639
+
640
+ </details>
641
+
642
+ ## 📌 Versioning
643
+
644
+ This Library adheres to [![Semantic Versioning 2.0.0](https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat)](https://semver.org/spec/v2.0.0.html).
645
+ Violations of this scheme should be reported as bugs.
646
+ Specifically, if a minor or patch version is released that breaks backward compatibility,
647
+ a new version should be immediately released that restores compatibility.
648
+ Breaking changes to the public API will only be introduced with new major versions.
649
+
650
+ > dropping support for a platform is both obviously and objectively a breaking change <br/>
651
+ > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716](https://github.com/semver/semver/issues/716#issuecomment-869336139)
652
+
653
+ I understand that policy doesn't work universally ("exceptions to every rule\!"),
654
+ but it is the policy here.
655
+ As such, in many cases it is good to specify a dependency on this library using
656
+ the [Pessimistic Version Constraint](http://guides.rubygems.org/patterns/#pessimistic-version-constraint) with two digits of precision.
657
+
658
+ For example:
659
+
660
+ ``` ruby
661
+ spec.add_dependency("bash-merge", "~> 1.0")
662
+ ```
663
+
664
+ <details markdown="1">
665
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
666
+
667
+ SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
668
+ is a *breaking change* to an API, and for that reason the bike shedding is endless.
669
+
670
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
671
+ read this article from the creator of SemVer:
672
+
673
+ - ["Major Version Numbers are Not Sacred"](https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html)
674
+ </details>
675
+
676
+ See [CHANGELOG.md](CHANGELOG.md) for a list of releases.
677
+
678
+ ## 📄 License
679
+
680
+ The gem is available as open source under the terms of
681
+ the [MIT License](LICENSE.txt) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT).
682
+ See [LICENSE.txt](LICENSE.txt) for the official [Copyright Notice](https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year).
683
+
684
+ ### © Copyright
685
+
686
+ <ul>
687
+ <li>
688
+ Copyright (c) 2025 Peter H. Boling, of
689
+ <a href="https://discord.gg/3qme4XHNKN">
690
+ Galtzo.com
691
+ <picture>
692
+ <img src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px-blank.svg" alt="Galtzo.com Logo (Wordless) by Aboling0, CC BY-SA 4.0" width="24">
693
+ </picture>
694
+ </a>, and bash-merge contributors.
695
+ </li>
696
+ </ul>
697
+
698
+ ## 🤑 A request for help
699
+
700
+ Maintainers have teeth and need to pay their dentists.
701
+ After getting laid off in an RIF in March, and encountering difficulty finding a new one,
702
+ I began spending most of my time building open source tools.
703
+ I'm hoping to be able to pay for my kids' health insurance this month,
704
+ so if you value the work I am doing, I need your support.
705
+ Please consider sponsoring me or the project.
706
+
707
+ To join the community or get help 👇️ Join the Discord.
708
+
709
+ [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
710
+
711
+ To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
712
+
713
+ [![Sponsor kettle-rb/bash-merge on Open Source Collective](https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge)](https://opencollective.com/kettle-rb) 💌 [![Sponsor me on GitHub Sponsors](https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github)](https://github.com/sponsors/pboling) 💌 [![Sponsor me on Liberapay](https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611)](https://liberapay.com/pboling/donate) 💌 [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A)](https://www.paypal.com/paypalme/peterboling)
714
+
715
+ ### Please give the project a star ⭐ ♥.
716
+
717
+ Thanks for RTFM. ☺️
718
+
719
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
720
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
721
+ [⛳liberapay]: https://liberapay.com/pboling/donate
722
+ [🖇osc-all-img]: https://img.shields.io/opencollective/all/kettle-rb
723
+ [🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/kettle-rb
724
+ [🖇osc-backers-img]: https://img.shields.io/opencollective/backers/kettle-rb
725
+ [🖇osc-backers]: https://opencollective.com/kettle-rb#backer
726
+ [🖇osc-backers-i]: https://opencollective.com/kettle-rb/backers/badge.svg?style=flat
727
+ [🖇osc-sponsors]: https://opencollective.com/kettle-rb#sponsor
728
+ [🖇osc-sponsors-i]: https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat
729
+ [🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge
730
+ [🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/kettle-rb?style=for-the-badge
731
+ [🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/kettle-rb?style=for-the-badge
732
+ [🖇osc]: https://opencollective.com/kettle-rb
733
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
734
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
735
+ [🖇sponsor]: https://github.com/sponsors/pboling
736
+ [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
737
+ [🖇polar]: https://polar.sh/pboling
738
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
739
+ [🖇kofi]: https://ko-fi.com/O5O86SNP4
740
+ [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
741
+ [🖇patreon]: https://patreon.com/galtzo
742
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
743
+ [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
744
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
745
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
746
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
747
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
748
+ [🖇floss-funding.dev]: https://floss-funding.dev
749
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
750
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
751
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
752
+ [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
753
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
754
+
755
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
756
+ [⛳️gem-namespace]: https://github.com/kettle-rb/bash-merge
757
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Bash::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
758
+ [⛳️gem-name]: https://bestgems.org/gems/bash-merge
759
+ [⛳️name-img]: https://img.shields.io/badge/name-bash--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
760
+ [⛳️tag-img]: https://img.shields.io/github/tag/kettle-rb/bash-merge.svg
761
+ [⛳️tag]: http://github.com/kettle-rb/bash-merge/releases
762
+ [🚂maint-blog]: http://www.railsbling.com/tags/bash-merge
763
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
764
+ [🚂maint-contact]: http://www.railsbling.com/contact
765
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
766
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
767
+ [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
768
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
769
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
770
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
771
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
772
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
773
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
774
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
775
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
776
+ [💖🌳linktree]: https://linktr.ee/galtzo
777
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
778
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
779
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
780
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
781
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
782
+ [💖🧊berg]: https://codeberg.org/pboling
783
+ [💖🐙hub]: https://github.org/pboling
784
+ [💖🛖hut]: https://sr.ht/~galtzo/
785
+ [💖🧪lab]: https://gitlab.com/pboling
786
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
787
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
788
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
789
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
790
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-bash-merge?utm_source=rubygems-bash-merge&utm_medium=referral&utm_campaign=readme
791
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
792
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
793
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
794
+ [🚂railsbling]: http://www.railsbling.com
795
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
796
+ [📜src-gl]: https://gitlab.com/kettle-rb/bash-merge/
797
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
798
+ [📜src-cb]: https://codeberg.org/kettle-rb/bash-merge
799
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
800
+ [📜src-gh]: https://github.com/kettle-rb/bash-merge
801
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
802
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
803
+ [📜gl-wiki]: https://gitlab.com/kettle-rb/bash-merge/-/wikis/home
804
+ [📜gh-wiki]: https://github.com/kettle-rb/bash-merge/wiki
805
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
806
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
807
+ [👽dl-rank]: https://bestgems.org/gems/bash-merge
808
+ [👽dl-ranki]: https://img.shields.io/gem/rd/bash-merge.svg
809
+ [👽oss-help]: https://www.codetriage.com/kettle-rb/bash-merge
810
+ [👽oss-helpi]: https://www.codetriage.com/kettle-rb/bash-merge/badges/users.svg
811
+ [👽version]: https://bestgems.org/gems/bash-merge
812
+ [👽versioni]: https://img.shields.io/gem/v/bash-merge.svg
813
+ [🏀qlty-mnt]: https://qlty.sh/gh/kettle-rb/projects/bash-merge
814
+ [🏀qlty-mnti]: https://qlty.sh/gh/kettle-rb/projects/bash-merge/maintainability.svg
815
+ [🏀qlty-cov]: https://qlty.sh/gh/kettle-rb/projects/bash-merge/metrics/code?sort=coverageRating
816
+ [🏀qlty-covi]: https://qlty.sh/gh/kettle-rb/projects/bash-merge/coverage.svg
817
+ [🏀codecov]: https://codecov.io/gh/kettle-rb/bash-merge
818
+ [🏀codecovi]: https://codecov.io/gh/kettle-rb/bash-merge/graph/badge.svg
819
+ [🏀coveralls]: https://coveralls.io/github/kettle-rb/bash-merge?branch=main
820
+ [🏀coveralls-img]: https://coveralls.io/repos/github/kettle-rb/bash-merge/badge.svg?branch=main
821
+ [🖐codeQL]: https://github.com/kettle-rb/bash-merge/security/code-scanning
822
+ [🖐codeQL-img]: https://github.com/kettle-rb/bash-merge/actions/workflows/codeql-analysis.yml/badge.svg
823
+ [🚎2-cov-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/coverage.yml
824
+ [🚎2-cov-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/coverage.yml/badge.svg
825
+ [🚎3-hd-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/heads.yml
826
+ [🚎3-hd-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/heads.yml/badge.svg
827
+ [🚎5-st-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/style.yml
828
+ [🚎5-st-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/style.yml/badge.svg
829
+ [🚎6-s-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/supported.yml
830
+ [🚎6-s-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/supported.yml/badge.svg
831
+ [🚎9-t-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/truffle.yml
832
+ [🚎9-t-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/truffle.yml/badge.svg
833
+ [🚎11-c-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml
834
+ [🚎11-c-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg
835
+ [🚎12-crh-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/dep-heads.yml
836
+ [🚎12-crh-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/dep-heads.yml/badge.svg
837
+ [🚎13-🔒️-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/locked_deps.yml
838
+ [🚎13-🔒️-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/locked_deps.yml/badge.svg
839
+ [🚎14-🔓️-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/unlocked_deps.yml
840
+ [🚎14-🔓️-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/unlocked_deps.yml/badge.svg
841
+ [🚎15-🪪-wf]: https://github.com/kettle-rb/bash-merge/actions/workflows/license-eye.yml
842
+ [🚎15-🪪-wfi]: https://github.com/kettle-rb/bash-merge/actions/workflows/license-eye.yml/badge.svg
843
+ [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
844
+ [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
845
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
846
+ [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
847
+ [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
848
+ [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
849
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
850
+ [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
851
+ [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
852
+ [🤝gh-issues]: https://github.com/kettle-rb/bash-merge/issues
853
+ [🤝gh-pulls]: https://github.com/kettle-rb/bash-merge/pulls
854
+ [🤝gl-issues]: https://gitlab.com/kettle-rb/bash-merge/-/issues
855
+ [🤝gl-pulls]: https://gitlab.com/kettle-rb/bash-merge/-/merge_requests
856
+ [🤝cb-issues]: https://codeberg.org/kettle-rb/bash-merge/issues
857
+ [🤝cb-pulls]: https://codeberg.org/kettle-rb/bash-merge/pulls
858
+ [🤝cb-donate]: https://donate.codeberg.org/
859
+ [🤝contributing]: CONTRIBUTING.md
860
+ [🏀codecov-g]: https://codecov.io/gh/kettle-rb/bash-merge/graphs/tree.svg
861
+ [🖐contrib-rocks]: https://contrib.rocks
862
+ [🖐contributors]: https://github.com/kettle-rb/bash-merge/graphs/contributors
863
+ [🖐contributors-img]: https://contrib.rocks/image?repo=kettle-rb/bash-merge
864
+ [🚎contributors-gl]: https://gitlab.com/kettle-rb/bash-merge/-/graphs/main
865
+ [🪇conduct]: CODE_OF_CONDUCT.md
866
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
867
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
868
+ [📌semver]: https://semver.org/spec/v2.0.0.html
869
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
870
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
871
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
872
+ [📌changelog]: CHANGELOG.md
873
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
874
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
875
+ [📌gitmoji]: https://gitmoji.dev
876
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
877
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
878
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.109-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
879
+ [🔐security]: SECURITY.md
880
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
881
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
882
+ [📄license]: LICENSE.txt
883
+ [📄license-ref]: https://opensource.org/licenses/MIT
884
+ [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
885
+ [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
886
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
887
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
888
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
889
+ [🚎yard-current]: http://rubydoc.info/gems/bash-merge
890
+ [🚎yard-head]: https://bash-merge.galtzo.com
891
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
892
+ [💎SHA_checksums]: https://gitlab.com/kettle-rb/bash-merge/-/tree/main/checksums
893
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
894
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
895
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
896
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
897
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
898
+
899
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
900
+ [dotenv]: https://github.com/bkeepers/dotenv