zip-merge 7.0.0 → 7.1.1
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.md +13 -0
- data/README.md +307 -0
- data/lib/zip/merge/backend.rb +48 -0
- data/lib/zip/merge/version.rb +5 -3
- data/lib/zip/merge.rb +160 -78
- data/lib/zip-merge.rb +7 -1
- data/sig/zip/merge.rbs +8 -0
- data.tar.gz.sig +0 -0
- metadata +264 -13
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a9683e713ef24a65e74b003579e4c54bf369859b740dcbd6f56ef788942e858
|
|
4
|
+
data.tar.gz: 13488970a1b826964bd9bc478c1e85c301e37f88c561127883f04a3bdf419662
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2d0d142fe3be5c9c838bbe288e5b5ffaeb3a94742bf9a66a1fc7bfc7e6739d903cf469a41786cdfc1a1f30ca7f20edda28877b14a6a78c6345a66aac6c5dc678
|
|
7
|
+
data.tar.gz: b114ba117f07c46042de4ff98048825e3390b6157ca5e0fa96b8106311bd3e2ee972c9a93c7cb85099d1b5b8fa13e33a350ac89bcda26da9a8ef01449e13a214
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
This project is made available under the following licenses.
|
|
4
|
+
Choose the option that best fits your use case:
|
|
5
|
+
|
|
6
|
+
- [AGPL-3.0-only](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/AGPL-3.0-only.md)
|
|
7
|
+
- [PolyForm-Small-Business-1.0.0](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/PolyForm-Small-Business-1.0.0.md)
|
|
8
|
+
|
|
9
|
+
If none of the above licenses fit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
|
|
10
|
+
|
|
11
|
+
## Copyright Notice
|
|
12
|
+
|
|
13
|
+
- Required Notice: Copyright (c) 2026 Peter H. Boling
|
data/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
<a href="https://github.com/structuredmerge"><img alt="structuredmerge Logo by GitHub" src="https://github.com/structuredmerge.png?size=192" width="14%" align="right"/></a>
|
|
2
|
+
|
|
3
|
+
# ☯️ Zip::Merge
|
|
4
|
+
|
|
5
|
+
[![Version][👽versioni]][👽version] [![Ruby Users Forum][✉️ruby-forum-top-img]][✉️ruby-forum] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only OR PolyForm-Small-Business-1.0.0][📄license-img]][📄license] [![Total downloads][👽dl-ranki]][👽dl-rank] [![CI Current][🚎11-c-wfi]][🚎11-c-wf]
|
|
6
|
+
|
|
7
|
+
`if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know on Discord][✉️discord-invite] or [RubyForum][✉️ruby-forum], as I may have missed the notification.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
`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.
|
|
12
|
+
|
|
13
|
+
[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
|
|
14
|
+
|
|
15
|
+
<details markdown="1">
|
|
16
|
+
<summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
|
|
17
|
+
|
|
18
|
+
I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
|
|
19
|
+
|
|
20
|
+
</details>
|
|
21
|
+
|
|
22
|
+
## 🌻 Synopsis <a href="https://discord.gg/3qme4XHNKN"><img alt="Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px.svg" width="8%" align="right"/></a> <a href="https://ruby-toolbox.com"><img alt="ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5" src="https://logos.galtzo.com/assets/images/ruby-lang/avatar-128px.svg" width="8%" align="right"/></a>
|
|
23
|
+
|
|
24
|
+
`Zip::Merge` plans ZIP archive merges by member path and raw byte ranges. It is
|
|
25
|
+
a Kaitai-family binary AST integration in the StructuredMerge stack: ZIP
|
|
26
|
+
inventories are represented with TreeHaver ZIP report nodes, and analysis enters
|
|
27
|
+
through a `parser_for(:zip)` Kaitai path. It depends on `binary-merge` for the
|
|
28
|
+
shared binary-family substrate: preservation reports, unsafe diagnostics,
|
|
29
|
+
byte-range ownership, and renderer planning vocabulary.
|
|
30
|
+
It identifies safe preservation cases, unsafe entries, members that need
|
|
31
|
+
structured nested merges, and archive metadata that must be rewritten by a
|
|
32
|
+
renderer.
|
|
33
|
+
|
|
34
|
+
### Key Features
|
|
35
|
+
|
|
36
|
+
- Central directory and local header inventory parsing.
|
|
37
|
+
- Kaitai-family binary AST reporting through TreeHaver ZIP structures.
|
|
38
|
+
- Shared binary-family report and diagnostic helpers from `binary-merge`.
|
|
39
|
+
- Member decisions for preserve, add, delete, rewrite, delegate, and reject operations.
|
|
40
|
+
- Nested family dispatch for structured archive members.
|
|
41
|
+
- Raw-preserving renderer for safe unchanged members.
|
|
42
|
+
- Checksum and central-directory update reporting.
|
|
43
|
+
|
|
44
|
+
## 💡 Info you can shake a stick at
|
|
45
|
+
|
|
46
|
+
| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
|
|
47
|
+
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
48
|
+
| Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
|
|
49
|
+
| Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Get help from RubyForum][✉️ruby-forum-img]][✉️ruby-forum] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼🏫expsup-upwork-img]][👨🏼🏫expsup-upwork] [![Get help from me on Codementor][👨🏼🏫expsup-codementor-img]][👨🏼🏫expsup-codementor] |
|
|
50
|
+
| Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on GitHub.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
|
|
51
|
+
| Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
|
|
52
|
+
| Compliance | [![License: AGPL-3.0-only OR PolyForm-Small-Business-1.0.0][📄license-img]][📄license] [![Apache license compatibility: Category X][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
|
|
53
|
+
| Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
|
|
54
|
+
| Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼♂️devto-img]][💖💁🏼♂️devto] |
|
|
55
|
+
| `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼♂️aboutme-img]][💖💁🏼♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
|
|
56
|
+
|
|
57
|
+
### Compatibility
|
|
58
|
+
|
|
59
|
+
Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
|
|
60
|
+
CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
|
|
61
|
+
This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
|
|
62
|
+
may be higher than the gem's runtime compatibility floor when legacy Rubies are
|
|
63
|
+
not practical for the current toolchain.
|
|
64
|
+
|
|
65
|
+
<a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/kettle-dev/avatar-128px.svg" width="14%" align="right"/></a>
|
|
66
|
+
|
|
67
|
+
The _amazing_ test matrix is powered by the kettle-dev stack.
|
|
68
|
+
|
|
69
|
+
<details markdown="1">
|
|
70
|
+
<summary>How kettle-dev manages complexity in tests</summary>
|
|
71
|
+
|
|
72
|
+
| Gem | Source | Role | Total downloads |
|
|
73
|
+
|-----|--------|------|---------------------|
|
|
74
|
+
| [appraisal2](https://clickgems.clickhouse.com/dashboard/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [](https://clickgems.clickhouse.com/dashboard/appraisal2) |
|
|
75
|
+
| [appraisal2-rubocop](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) |
|
|
76
|
+
| [kettle-dev](https://clickgems.clickhouse.com/dashboard/kettle-dev) | [GitHub](https://github.com/kettle-dev/kettle-dev) | development, release, and CI workflow tooling | [](https://clickgems.clickhouse.com/dashboard/kettle-dev) |
|
|
77
|
+
| [kettle-jem](https://clickgems.clickhouse.com/dashboard/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [](https://clickgems.clickhouse.com/dashboard/kettle-jem) |
|
|
78
|
+
| [kettle-soup-cover](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) | [GitHub](https://github.com/kettle-dev/kettle-soup-cover) | SimpleCov coverage policy and reporting | [](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) |
|
|
79
|
+
| [kettle-test](https://clickgems.clickhouse.com/dashboard/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [](https://clickgems.clickhouse.com/dashboard/kettle-test) |
|
|
80
|
+
| [rubocop-lts](https://clickgems.clickhouse.com/dashboard/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [](https://clickgems.clickhouse.com/dashboard/rubocop-lts) |
|
|
81
|
+
| [turbo_tests2](https://clickgems.clickhouse.com/dashboard/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [](https://clickgems.clickhouse.com/dashboard/turbo_tests2) |
|
|
82
|
+
|
|
83
|
+
</details>
|
|
84
|
+
|
|
85
|
+
## ✨ Installation
|
|
86
|
+
|
|
87
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
88
|
+
|
|
89
|
+
```console
|
|
90
|
+
bundle add zip-merge
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
94
|
+
|
|
95
|
+
```console
|
|
96
|
+
gem install zip-merge
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ⚙️ Configuration
|
|
100
|
+
|
|
101
|
+
`Zip::Merge` works from parsed inventories rather than global settings.
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
ancestor = Zip::Merge.parse_zip_inventory(ancestor_bytes)
|
|
105
|
+
current = Zip::Merge.parse_zip_inventory(current_bytes)
|
|
106
|
+
incoming = Zip::Merge.parse_zip_inventory(incoming_bytes)
|
|
107
|
+
plan = Zip::Merge.plan_zip_merge(ancestor, current, incoming)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Rendering requires caller-supplied member bytes for added, rewritten, or delegated members.
|
|
111
|
+
|
|
112
|
+
## 🔧 Basic Usage
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
require "zip/merge"
|
|
116
|
+
|
|
117
|
+
zip_parser = TreeHaver.parser_for(:zip, backend_type: :kaitai)
|
|
118
|
+
ancestor = zip_parser.parse(File.binread("base.zip"))
|
|
119
|
+
current = zip_parser.parse(File.binread("ours.zip"))
|
|
120
|
+
incoming = zip_parser.parse(File.binread("theirs.zip"))
|
|
121
|
+
|
|
122
|
+
plan = Zip::Merge.plan_zip_merge(ancestor, current, incoming)
|
|
123
|
+
plan.member_decisions.each do |decision|
|
|
124
|
+
puts "#{decision.operation}: #{decision.normalized_path}"
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 🔐 Security
|
|
129
|
+
|
|
130
|
+
See [SECURITY.md][🔐security].
|
|
131
|
+
|
|
132
|
+
## 🤝 Contributing
|
|
133
|
+
|
|
134
|
+
If you need some ideas of where to help, you could work on adding more code coverage,
|
|
135
|
+
or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
|
|
136
|
+
or use the gem and think about how it could be better.
|
|
137
|
+
|
|
138
|
+
We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
|
|
139
|
+
|
|
140
|
+
See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
|
|
141
|
+
|
|
142
|
+
### Code Coverage
|
|
143
|
+
|
|
144
|
+
<details markdown="1">
|
|
145
|
+
<summary>Coverage service badges</summary>
|
|
146
|
+
|
|
147
|
+
</details>
|
|
148
|
+
|
|
149
|
+
## 📌 Versioning
|
|
150
|
+
|
|
151
|
+
This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
|
|
152
|
+
For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
|
|
153
|
+
|
|
154
|
+
For example:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
spec.add_dependency("zip-merge", "~> 7.0")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
<details markdown="1">
|
|
161
|
+
<summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
|
|
162
|
+
|
|
163
|
+
Dropping support for a platform can be a breaking change for affected users.
|
|
164
|
+
If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
|
|
165
|
+
|
|
166
|
+
To get a better understanding of how SemVer is intended to work over a project's lifetime,
|
|
167
|
+
read this article from the creator of SemVer:
|
|
168
|
+
|
|
169
|
+
- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
|
|
170
|
+
|
|
171
|
+
</details>
|
|
172
|
+
|
|
173
|
+
See [CHANGELOG.md][📌changelog] for a list of releases.
|
|
174
|
+
|
|
175
|
+
## 📄 License
|
|
176
|
+
|
|
177
|
+
The gem is available under the following licenses: [AGPL-3.0-only](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/AGPL-3.0-only.md), [PolyForm-Small-Business-1.0.0](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/PolyForm-Small-Business-1.0.0.md).
|
|
178
|
+
See [LICENSE.md][📄license] for details.
|
|
179
|
+
|
|
180
|
+
If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
|
|
181
|
+
|
|
182
|
+
[⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
|
|
183
|
+
[⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
|
|
184
|
+
[⛳liberapay]: https://liberapay.com/pboling/donate
|
|
185
|
+
[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
|
|
186
|
+
[🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
|
|
187
|
+
[🖇sponsor]: https://github.com/sponsors/pboling
|
|
188
|
+
[🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
|
|
189
|
+
[🖇kofi]: https://ko-fi.com/pboling
|
|
190
|
+
[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
|
|
191
|
+
[🖇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
|
|
192
|
+
[🖇buyme]: https://www.buymeacoffee.com/pboling
|
|
193
|
+
[🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
|
|
194
|
+
[🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
|
|
195
|
+
[🖇paypal]: https://www.paypal.com/paypalme/peterboling
|
|
196
|
+
[🖇floss-funding.dev]: https://floss-funding.dev
|
|
197
|
+
[🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
|
|
198
|
+
[✉️discord-invite]: https://discord.gg/3qme4XHNKN
|
|
199
|
+
[✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
|
|
200
|
+
[✉️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
|
|
201
|
+
[✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
|
|
202
|
+
[✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=flat&logo=discourse&label=Ruby%20Users%20Forum
|
|
203
|
+
[✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=for-the-badge&logo=discourse&label=Ruby%20Users%20Forum
|
|
204
|
+
[✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
|
|
205
|
+
[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
|
|
206
|
+
[⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/zip-merge
|
|
207
|
+
[⛳️namespace-img]: https://img.shields.io/badge/namespace-Zip::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
|
|
208
|
+
[⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/zip-merge
|
|
209
|
+
[⛳️name-img]: https://img.shields.io/badge/name-zip--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
|
|
210
|
+
[⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
|
|
211
|
+
[⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
|
|
212
|
+
[🚂maint-blog]: http://www.railsbling.com/tags/zip-merge
|
|
213
|
+
[🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
|
|
214
|
+
[🚂maint-contact]: http://www.railsbling.com/contact
|
|
215
|
+
[🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
|
|
216
|
+
[💖🖇linkedin]: http://www.linkedin.com/in/peterboling
|
|
217
|
+
[💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
|
|
218
|
+
[💖✌️wellfound]: https://wellfound.com/u/peter-boling
|
|
219
|
+
[💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
|
|
220
|
+
[💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
|
|
221
|
+
[💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
|
|
222
|
+
[💖🐘ruby-mast]: https://ruby.social/@galtzo
|
|
223
|
+
[💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
|
|
224
|
+
[💖🦋bluesky]: https://bsky.app/profile/galtzo.com
|
|
225
|
+
[💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
|
|
226
|
+
[💖🌳linktree]: https://linktr.ee/galtzo
|
|
227
|
+
[💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
|
|
228
|
+
[💖💁🏼♂️devto]: https://dev.to/galtzo
|
|
229
|
+
[💖💁🏼♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
|
|
230
|
+
[💖💁🏼♂️aboutme]: https://about.me/peter.boling
|
|
231
|
+
[💖💁🏼♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
|
|
232
|
+
[💖🧊berg]: https://codeberg.org/pboling
|
|
233
|
+
[💖🐙hub]: https://github.org/pboling
|
|
234
|
+
[💖🛖hut]: https://sr.ht/~galtzo/
|
|
235
|
+
[💖🧪lab]: https://gitlab.com/pboling
|
|
236
|
+
[👨🏼🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
|
|
237
|
+
[👨🏼🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
|
|
238
|
+
[👨🏼🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
|
|
239
|
+
[👨🏼🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
|
|
240
|
+
[🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-zip-merge?utm_source=rubygems-zip-merge&utm_medium=referral&utm_campaign=readme
|
|
241
|
+
[🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
|
|
242
|
+
[🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
|
|
243
|
+
[💁🏼♂️peterboling]: http://www.peterboling.com
|
|
244
|
+
[🚂railsbling]: http://www.railsbling.com
|
|
245
|
+
[📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
|
|
246
|
+
[📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/zip-merge
|
|
247
|
+
[📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
|
|
248
|
+
[📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/zip-merge
|
|
249
|
+
[📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
|
|
250
|
+
[📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/zip-merge
|
|
251
|
+
[📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
|
|
252
|
+
[📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
|
|
253
|
+
[📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
|
|
254
|
+
[📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
|
|
255
|
+
[📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
|
|
256
|
+
[📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
|
|
257
|
+
[👽dl-rank]: https://clickgems.clickhouse.com/dashboard/zip-merge
|
|
258
|
+
[👽dl-ranki]: https://img.shields.io/gem/dt/zip-merge.svg
|
|
259
|
+
[👽version]: https://clickgems.clickhouse.com/dashboard/zip-merge
|
|
260
|
+
[👽versioni]: https://img.shields.io/gem/v/zip-merge.svg
|
|
261
|
+
[🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
|
|
262
|
+
[🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
|
|
263
|
+
[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
|
|
264
|
+
[🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
|
|
265
|
+
[🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
|
|
266
|
+
[🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
|
|
267
|
+
[🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
|
|
268
|
+
[🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
|
|
269
|
+
[🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
|
|
270
|
+
[🤝cb-donate]: https://donate.codeberg.org/
|
|
271
|
+
[🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
|
|
272
|
+
[🖐contrib-rocks]: https://contrib.rocks
|
|
273
|
+
[🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
|
|
274
|
+
[🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
|
|
275
|
+
[🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
|
|
276
|
+
[🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
|
|
277
|
+
[📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
|
|
278
|
+
[📌semver]: https://semver.org/spec/v2.0.0.html
|
|
279
|
+
[📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
|
|
280
|
+
[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
|
|
281
|
+
[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
|
|
282
|
+
[📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
|
|
283
|
+
[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
|
|
284
|
+
[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
|
|
285
|
+
[📌gitmoji]: https://gitmoji.dev
|
|
286
|
+
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
287
|
+
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
288
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.247-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
289
|
+
[🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
|
|
290
|
+
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
291
|
+
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
292
|
+
[📄license]: LICENSE.md
|
|
293
|
+
[📄license-ref]: LICENSE.md
|
|
294
|
+
[📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
|
|
295
|
+
[📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
|
|
296
|
+
[📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
|
|
297
|
+
[📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
|
|
298
|
+
[📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
|
|
299
|
+
[🚎yard-current]: http://rubydoc.info/gems/zip-merge
|
|
300
|
+
[🚎yard-head]: https://zip-merge.galtzo.com
|
|
301
|
+
[💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
|
|
302
|
+
[💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
|
|
303
|
+
[💎rlts]: https://github.com/rubocop-lts/rubocop-lts
|
|
304
|
+
[💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
|
|
305
|
+
[💎appraisal2]: https://github.com/appraisal-rb/appraisal2
|
|
306
|
+
[💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
|
|
307
|
+
[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zip
|
|
4
|
+
# StructuredMerge ZIP archive merge planning and rendering helpers.
|
|
5
|
+
module Merge
|
|
6
|
+
BACKEND_REFERENCE = TreeHaver::KAITAI_STRUCT_BACKEND
|
|
7
|
+
|
|
8
|
+
module Backend
|
|
9
|
+
# TreeHaver language wrapper for ZIP archives parsed through Kaitai-style
|
|
10
|
+
# binary AST inventory.
|
|
11
|
+
class Language < TreeHaver::Base::Language
|
|
12
|
+
def initialize(name = :zip)
|
|
13
|
+
super(name, backend: :kaitai, options: {})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.zip
|
|
17
|
+
new(:zip)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Parser adapter that exposes the ZIP inventory as the TreeHaver
|
|
22
|
+
# `parser_for(:zip)` Kaitai backend surface.
|
|
23
|
+
class Parser < TreeHaver::Base::Parser
|
|
24
|
+
def parse(source)
|
|
25
|
+
raise 'Language not set' unless language
|
|
26
|
+
|
|
27
|
+
Zip::Merge.parse_zip_inventory(source)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse_string(_old_tree, source)
|
|
31
|
+
parse(source)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.register_backend!
|
|
37
|
+
TreeHaver::BackendRegistry.register(BACKEND_REFERENCE)
|
|
38
|
+
TreeHaver.register_language(
|
|
39
|
+
:zip,
|
|
40
|
+
backend_module: Backend,
|
|
41
|
+
backend_type: :kaitai,
|
|
42
|
+
gem_name: 'zip-merge',
|
|
43
|
+
contract: :kaitai
|
|
44
|
+
)
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/zip/merge/version.rb
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Zip
|
|
4
4
|
module Merge
|
|
5
|
+
# Version namespace for this gem.
|
|
5
6
|
module Version
|
|
6
|
-
|
|
7
|
+
# Current gem version.
|
|
8
|
+
VERSION = '7.1.1'
|
|
7
9
|
end
|
|
8
|
-
|
|
9
|
-
VERSION = Version::VERSION
|
|
10
|
+
# Current gem version exposed at the traditional constant location.
|
|
11
|
+
VERSION = Version::VERSION # Traditional Constant Location
|
|
10
12
|
end
|
|
11
13
|
end
|
data/lib/zip/merge.rb
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require_relative
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'zlib'
|
|
5
|
+
require 'tree_haver'
|
|
6
|
+
require_relative 'merge/version'
|
|
7
7
|
|
|
8
8
|
module Zip
|
|
9
9
|
module Merge
|
|
10
10
|
LOCAL = 0x04034b50
|
|
11
11
|
CENTRAL = 0x02014b50
|
|
12
12
|
EOCD = 0x06054b50
|
|
13
|
-
DOS_EPOCH = [0, 0, 0x21, 0].pack(
|
|
13
|
+
DOS_EPOCH = [0, 0, 0x21, 0].pack('C*')
|
|
14
14
|
|
|
15
15
|
RenderError = Class.new(StandardError) do
|
|
16
16
|
attr_reader :diagnostic
|
|
@@ -32,19 +32,21 @@ module Zip
|
|
|
32
32
|
TreeHaver::ZipArchiveEntry.new(
|
|
33
33
|
path: name,
|
|
34
34
|
normalized_path: normalize_zip_path(name),
|
|
35
|
-
directory: name.end_with?(
|
|
35
|
+
directory: name.end_with?('/'),
|
|
36
36
|
compression: compression_name(record[:method]),
|
|
37
37
|
compressed_size: record[:compressed_size],
|
|
38
38
|
uncompressed_size: record[:uncompressed_size],
|
|
39
|
-
crc32:
|
|
39
|
+
crc32: '%08x' % record[:crc32],
|
|
40
40
|
local_header_range: TreeHaver::ByteRange.new(start_byte: record[:local_offset], end_byte: local[:data_start]),
|
|
41
|
-
data_range: TreeHaver::ByteRange.new(start_byte: local[:data_start],
|
|
41
|
+
data_range: TreeHaver::ByteRange.new(start_byte: local[:data_start],
|
|
42
|
+
end_byte: local[:data_start] + record[:compressed_size]),
|
|
42
43
|
central_directory_range: record[:range]
|
|
43
44
|
)
|
|
44
45
|
end.sort_by { |entry| entry.local_header_range.start_byte }
|
|
45
46
|
|
|
46
47
|
TreeHaver::ZipFamilyReport.new(
|
|
47
|
-
archive: TreeHaver::ZipArchiveInfo.new(format:
|
|
48
|
+
archive: TreeHaver::ZipArchiveInfo.new(format: 'zip', schema: 'zip.ksy', entry_count: entries.length,
|
|
49
|
+
central_directory_range: central[:range]),
|
|
48
50
|
entries: entries,
|
|
49
51
|
member_decisions: [],
|
|
50
52
|
unsafe_entries: unsafe_entries(entries, central[:records]),
|
|
@@ -71,59 +73,70 @@ module Zip
|
|
|
71
73
|
incoming_entry = incoming_entries[path]
|
|
72
74
|
if unsafe_by_path[path]
|
|
73
75
|
unsafe = unsafe_by_path[path]
|
|
74
|
-
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation:
|
|
76
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'reject',
|
|
77
|
+
disposition: 'unsafe', reason: unsafe.reason)
|
|
75
78
|
report.merge_report.diagnostics << diagnostic(unsafe.category, schema_path(path), unsafe.reason)
|
|
76
79
|
elsif current_entry.nil? && incoming_entry
|
|
77
|
-
decision(report, path,
|
|
80
|
+
decision(report, path, 'add', 'requires_renderer', 'member exists only in incoming archive')
|
|
78
81
|
elsif current_entry && incoming_entry.nil?
|
|
79
|
-
decision(report, path,
|
|
80
|
-
elsif ancestor_entry && same_entry?(current_entry,
|
|
81
|
-
|
|
82
|
+
decision(report, path, 'delete', 'requires_renderer', 'member was removed from incoming archive')
|
|
83
|
+
elsif ancestor_entry && same_entry?(current_entry,
|
|
84
|
+
ancestor_entry) && same_entry?(incoming_entry, ancestor_entry)
|
|
85
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'preserve',
|
|
86
|
+
disposition: 'safe', reason: 'member is unchanged from ancestor')
|
|
82
87
|
report.merge_report.preserved_ranges.concat([current_entry.local_header_range, current_entry.data_range])
|
|
83
88
|
elsif (family = nested_family(path))
|
|
84
|
-
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation:
|
|
85
|
-
|
|
89
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'delegate',
|
|
90
|
+
disposition: 'requires_renderer', nested_family: family, reason: 'structured member can be merged by a nested family before ZIP rendering')
|
|
91
|
+
report.merge_report.nested_dispatches << TreeHaver::BinaryNestedDispatch.new(
|
|
92
|
+
schema_path: "#{schema_path(path)}/data", family: family, status: 'planned'
|
|
93
|
+
)
|
|
86
94
|
report.merge_report.rewritten_nodes << schema_path(path)
|
|
87
95
|
report.merge_report.checksum_updates << "#{schema_path(path)}/crc32"
|
|
88
96
|
else
|
|
89
|
-
decision(report, path,
|
|
97
|
+
decision(report, path, 'rewrite', 'requires_renderer', 'member bytes or metadata changed')
|
|
90
98
|
report.merge_report.checksum_updates << "#{schema_path(path)}/crc32"
|
|
91
99
|
end
|
|
92
100
|
report.merge_report.matched_schema_paths << schema_path(path)
|
|
93
101
|
end
|
|
94
102
|
unless report.merge_report.rewritten_nodes.empty? && report.merge_report.checksum_updates.empty?
|
|
95
|
-
report.merge_report.rewritten_nodes <<
|
|
96
|
-
report.merge_report.checksum_updates.concat([
|
|
103
|
+
report.merge_report.rewritten_nodes << '/central_directory'
|
|
104
|
+
report.merge_report.checksum_updates.concat(['/central_directory/size', '/central_directory/offset'])
|
|
97
105
|
end
|
|
98
106
|
report
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
def render_with_raw_preservation(source:, plan:, member_bytes: {}, compression: 0)
|
|
102
|
-
|
|
110
|
+
unless [
|
|
111
|
+
0, 8
|
|
112
|
+
].include?(compression)
|
|
113
|
+
raise render_error('unsupported_compression', '/render/options/compression',
|
|
114
|
+
'unsupported raw-preserving compression method')
|
|
115
|
+
end
|
|
103
116
|
|
|
104
117
|
source = source.b
|
|
105
118
|
source_inventory = parse_zip_inventory(source)
|
|
106
119
|
central = scan_central_directory(source)
|
|
107
120
|
source_entries = entries_by_path(source_inventory.entries)
|
|
108
121
|
raw_ranges = raw_local_record_ranges(source, source_entries)
|
|
109
|
-
output = +
|
|
122
|
+
output = +''.b
|
|
110
123
|
central_records = []
|
|
111
124
|
entries = entries_by_path(plan.entries)
|
|
112
125
|
plan.member_decisions.each do |member|
|
|
113
126
|
entry = entries[member.normalized_path]
|
|
114
127
|
case member.operation
|
|
115
|
-
when
|
|
116
|
-
raise render_error(
|
|
117
|
-
when
|
|
128
|
+
when 'reject'
|
|
129
|
+
raise render_error('rejected_member', schema_path(member.normalized_path), member.reason)
|
|
130
|
+
when 'delete'
|
|
118
131
|
next
|
|
119
|
-
when
|
|
132
|
+
when 'preserve'
|
|
120
133
|
source_entry = source_entries.fetch(member.normalized_path)
|
|
121
134
|
validate_raw_preserve_entry!(source, central, source_entry)
|
|
122
135
|
range = raw_ranges.fetch(member.normalized_path)
|
|
123
136
|
offset = output.bytesize
|
|
124
137
|
output << source.byteslice(range.start_byte...range.end_byte)
|
|
125
138
|
central_records << central_record_from_entry(source_entry, offset)
|
|
126
|
-
when
|
|
139
|
+
when 'add', 'rewrite', 'delegate'
|
|
127
140
|
content = member_bytes.fetch(member.normalized_path)
|
|
128
141
|
rendered, record = rendered_local_record(entry, content.b, compression, output.bytesize)
|
|
129
142
|
output << rendered
|
|
@@ -138,12 +151,14 @@ module Zip
|
|
|
138
151
|
output << eocd_record(central_records.length, central_size, central_start)
|
|
139
152
|
report = parse_zip_inventory(output)
|
|
140
153
|
merge_report = plan.merge_report
|
|
141
|
-
merge_report.preserved_ranges = plan.member_decisions.filter_map
|
|
154
|
+
merge_report.preserved_ranges = plan.member_decisions.filter_map do |member|
|
|
155
|
+
raw_ranges[member.normalized_path] if member.operation == 'preserve'
|
|
156
|
+
end
|
|
142
157
|
[output, report, merge_report]
|
|
143
158
|
end
|
|
144
159
|
|
|
145
160
|
def new_stored_zip(entries)
|
|
146
|
-
output = +
|
|
161
|
+
output = +''.b
|
|
147
162
|
central = []
|
|
148
163
|
entries.keys.sort.each do |name|
|
|
149
164
|
rendered, record = rendered_local_record(path_entry(name, entries[name]), entries[name].b, 0, output.bytesize)
|
|
@@ -157,34 +172,38 @@ module Zip
|
|
|
157
172
|
end
|
|
158
173
|
|
|
159
174
|
def empty_report
|
|
160
|
-
|
|
175
|
+
Binary::Merge.preservation_report(format: 'zip', schema: 'zip.ksy', matched_schema_paths: [],
|
|
176
|
+
preserved_ranges: [])
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
def scan_central_directory(source)
|
|
164
180
|
eocd = source.bytesize - 22
|
|
165
|
-
eocd -= 1 while eocd >= 0 && source.byteslice(eocd, 4).unpack1(
|
|
166
|
-
raise
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
eocd -= 1 while eocd >= 0 && source.byteslice(eocd, 4).unpack1('V') != EOCD
|
|
182
|
+
raise 'missing ZIP end of central directory' if eocd.negative?
|
|
183
|
+
|
|
184
|
+
size = source.byteslice(eocd + 12, 4).unpack1('V')
|
|
185
|
+
offset = source.byteslice(eocd + 16, 4).unpack1('V')
|
|
186
|
+
comment_length = source.byteslice(eocd + 20, 2).unpack1('v')
|
|
170
187
|
records = {}
|
|
171
188
|
cursor = offset
|
|
172
189
|
while cursor < offset + size
|
|
173
|
-
raise
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
raise 'unexpected central directory record' unless source.byteslice(cursor, 4).unpack1('V') == CENTRAL
|
|
191
|
+
|
|
192
|
+
name_len = source.byteslice(cursor + 28, 2).unpack1('v')
|
|
193
|
+
extra_len = source.byteslice(cursor + 30, 2).unpack1('v')
|
|
194
|
+
comment_len = source.byteslice(cursor + 32, 2).unpack1('v')
|
|
177
195
|
name = source.byteslice(cursor + 46, name_len)
|
|
178
196
|
records[name] = {
|
|
179
|
-
range: TreeHaver::ByteRange.new(start_byte: cursor,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
range: TreeHaver::ByteRange.new(start_byte: cursor,
|
|
198
|
+
end_byte: cursor + 46 + name_len + extra_len + comment_len),
|
|
199
|
+
flags: source.byteslice(cursor + 8, 2).unpack1('v'),
|
|
200
|
+
method: source.byteslice(cursor + 10, 2).unpack1('v'),
|
|
201
|
+
crc32: source.byteslice(cursor + 16, 4).unpack1('V'),
|
|
202
|
+
compressed_size: source.byteslice(cursor + 20, 4).unpack1('V'),
|
|
203
|
+
uncompressed_size: source.byteslice(cursor + 24, 4).unpack1('V'),
|
|
185
204
|
extra_length: extra_len,
|
|
186
205
|
comment_length: comment_len,
|
|
187
|
-
local_offset: source.byteslice(cursor + 42, 4).unpack1(
|
|
206
|
+
local_offset: source.byteslice(cursor + 42, 4).unpack1('V')
|
|
188
207
|
}
|
|
189
208
|
cursor = records[name][:range].end_byte
|
|
190
209
|
end
|
|
@@ -194,32 +213,67 @@ module Zip
|
|
|
194
213
|
def scan_local_headers(source, records)
|
|
195
214
|
records.transform_values do |record|
|
|
196
215
|
cursor = record[:local_offset]
|
|
197
|
-
raise
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
raise 'unexpected ZIP local header' unless source.byteslice(cursor, 4).unpack1('V') == LOCAL
|
|
217
|
+
|
|
218
|
+
name_len = source.byteslice(cursor + 26, 2).unpack1('v')
|
|
219
|
+
extra_len = source.byteslice(cursor + 28, 2).unpack1('v')
|
|
200
220
|
{ data_start: cursor + 30 + name_len + extra_len, extra_length: extra_len }
|
|
201
221
|
end
|
|
202
222
|
end
|
|
203
223
|
|
|
204
224
|
def validate_raw_preserve_entry!(source, central, entry)
|
|
205
|
-
|
|
225
|
+
if central[:archive_comment]
|
|
226
|
+
raise render_error('archive_comment', '/archive/comment',
|
|
227
|
+
'raw-preserving ZIP renderer does not yet preserve archive comments')
|
|
228
|
+
end
|
|
229
|
+
|
|
206
230
|
record = central[:records].fetch(entry.path)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
231
|
+
unless (record[:flags] & 0x1).zero?
|
|
232
|
+
raise render_error('encrypted_member', schema_path(entry.normalized_path),
|
|
233
|
+
"raw-preserving ZIP renderer rejects encrypted member #{entry.normalized_path}")
|
|
234
|
+
end
|
|
235
|
+
unless [
|
|
236
|
+
0, 8
|
|
237
|
+
].include?(record[:method])
|
|
238
|
+
raise render_error('unsupported_compression', schema_path(entry.normalized_path),
|
|
239
|
+
"raw-preserving ZIP renderer rejects unsupported compression #{entry.compression.inspect}")
|
|
240
|
+
end
|
|
241
|
+
unless record[:extra_length].zero?
|
|
242
|
+
raise render_error('central_directory_extra_field', schema_path(entry.normalized_path),
|
|
243
|
+
"raw-preserving ZIP renderer does not yet preserve central-directory extra fields for #{entry.normalized_path}")
|
|
244
|
+
end
|
|
245
|
+
unless record[:comment_length].zero?
|
|
246
|
+
raise render_error('member_comment', schema_path(entry.normalized_path),
|
|
247
|
+
"raw-preserving ZIP renderer does not yet preserve member comments for #{entry.normalized_path}")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
local_extra = source.byteslice(entry.local_header_range.start_byte + 28, 2).unpack1('v')
|
|
251
|
+
return if local_extra.zero?
|
|
252
|
+
|
|
253
|
+
raise render_error('local_header_extra_field', schema_path(entry.normalized_path),
|
|
254
|
+
"raw-preserving ZIP renderer does not yet preserve local extra fields for #{entry.normalized_path}")
|
|
213
255
|
end
|
|
214
256
|
|
|
215
257
|
def unsafe_entries(entries, records)
|
|
216
258
|
seen = {}
|
|
217
259
|
entries.flat_map do |entry|
|
|
218
260
|
list = []
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
261
|
+
if escapes_root?(entry.path)
|
|
262
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
263
|
+
category: 'path_traversal', reason: 'entry escapes the archive root')
|
|
264
|
+
end
|
|
265
|
+
if seen[entry.normalized_path] && seen[entry.normalized_path] != entry.path
|
|
266
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
267
|
+
category: 'duplicate_normalized_path', reason: 'normalized path collides with an existing entry')
|
|
268
|
+
end
|
|
269
|
+
unless (records[entry.path][:flags] & 0x1).zero?
|
|
270
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
271
|
+
category: 'encrypted_member', reason: 'encrypted member cannot be rendered by the default provider')
|
|
272
|
+
end
|
|
273
|
+
if signing_sensitive?(entry.normalized_path)
|
|
274
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
275
|
+
category: 'signing_sensitive_member', reason: 'signature-bearing member mutation is not enabled')
|
|
276
|
+
end
|
|
223
277
|
seen[entry.normalized_path] = entry.path
|
|
224
278
|
list
|
|
225
279
|
end
|
|
@@ -228,57 +282,79 @@ module Zip
|
|
|
228
282
|
def rendered_local_record(entry, content, method, offset)
|
|
229
283
|
payload = method == 8 ? Zlib::Deflate.deflate(content) : content
|
|
230
284
|
crc = Zlib.crc32(content)
|
|
231
|
-
header = [LOCAL, 20, 0,
|
|
232
|
-
|
|
285
|
+
header = [LOCAL, 20, 0,
|
|
286
|
+
method].pack('Vvvv') + DOS_EPOCH + [crc, payload.bytesize, content.bytesize, entry.path.bytesize,
|
|
287
|
+
0].pack('VVVvv') + entry.path
|
|
288
|
+
[header + payload,
|
|
289
|
+
{ name: entry.path, method: method, crc32: crc, compressed_size: payload.bytesize, uncompressed_size: content.bytesize,
|
|
290
|
+
offset: offset, flags: 0 }]
|
|
233
291
|
end
|
|
234
292
|
|
|
235
293
|
def central_directory_record(record)
|
|
236
|
-
[CENTRAL, 20, 20, record[:flags],
|
|
294
|
+
[CENTRAL, 20, 20, record[:flags],
|
|
295
|
+
record[:method]].pack('Vvvvv') + DOS_EPOCH + [record[:crc32], record[:compressed_size], record[:uncompressed_size],
|
|
296
|
+
record[:name].bytesize, 0, 0, 0, 0, 0, record[:offset]].pack('VVVvvvvvVV') + record[:name]
|
|
237
297
|
end
|
|
238
298
|
|
|
239
299
|
def eocd_record(entries, size, offset)
|
|
240
|
-
[EOCD, 0, 0, entries, entries, size, offset, 0].pack(
|
|
300
|
+
[EOCD, 0, 0, entries, entries, size, offset, 0].pack('VvvvvVVv')
|
|
241
301
|
end
|
|
242
302
|
|
|
243
|
-
def raw_local_record_ranges(
|
|
303
|
+
def raw_local_record_ranges(_source, entries)
|
|
244
304
|
ordered = entries.values.sort_by { |entry| entry.local_header_range.start_byte }
|
|
245
305
|
ordered.each_with_index.to_h do |entry, index|
|
|
246
306
|
end_byte = ordered[index + 1]&.local_header_range&.start_byte || entry.central_directory_range.start_byte
|
|
247
|
-
[entry.normalized_path,
|
|
307
|
+
[entry.normalized_path,
|
|
308
|
+
TreeHaver::ByteRange.new(start_byte: entry.local_header_range.start_byte, end_byte: end_byte)]
|
|
248
309
|
end
|
|
249
310
|
end
|
|
250
311
|
|
|
251
312
|
def central_record_from_entry(entry, offset)
|
|
252
|
-
{ name: entry.path, method: entry.compression ==
|
|
313
|
+
{ name: entry.path, method: entry.compression == 'deflate' ? 8 : 0, crc32: entry.crc32.to_i(16),
|
|
314
|
+
compressed_size: entry.compressed_size, uncompressed_size: entry.uncompressed_size, offset: offset, flags: 0 }
|
|
253
315
|
end
|
|
254
316
|
|
|
255
317
|
def path_entry(name, content)
|
|
256
|
-
TreeHaver::ZipArchiveEntry.new(path: name, normalized_path: normalize_zip_path(name),
|
|
318
|
+
TreeHaver::ZipArchiveEntry.new(path: name, normalized_path: normalize_zip_path(name),
|
|
319
|
+
directory: name.end_with?('/'), compression: 'stored', compressed_size: content.bytesize, uncompressed_size: content.bytesize, crc32: '%08x' % Zlib.crc32(content), local_header_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), data_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), central_directory_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0))
|
|
257
320
|
end
|
|
258
321
|
|
|
259
322
|
def decision(report, path, operation, disposition, reason)
|
|
260
|
-
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: operation,
|
|
323
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: operation,
|
|
324
|
+
disposition: disposition, reason: reason)
|
|
261
325
|
report.merge_report.rewritten_nodes << schema_path(path)
|
|
262
326
|
end
|
|
263
327
|
|
|
264
328
|
def normalize_zip_path(path)
|
|
265
|
-
path.tr(
|
|
329
|
+
path.tr('\\', '/').split('/').each_with_object([]) do |part, stack|
|
|
330
|
+
unless part == '.'
|
|
331
|
+
part == '..' ? stack.pop : stack << part
|
|
332
|
+
end
|
|
333
|
+
end.join('/')
|
|
266
334
|
end
|
|
267
335
|
|
|
268
336
|
def compression_name(method)
|
|
269
337
|
case method
|
|
270
|
-
when 0 then
|
|
271
|
-
when 8 then
|
|
338
|
+
when 0 then 'stored'
|
|
339
|
+
when 8 then 'deflate'
|
|
272
340
|
else "method-#{method}"
|
|
273
341
|
end
|
|
274
342
|
end
|
|
275
343
|
|
|
276
344
|
def escapes_root?(path)
|
|
277
|
-
path.start_with?(
|
|
345
|
+
path.start_with?('/') || path.tr('\\', '/').split('/').then do |parts|
|
|
346
|
+
depth = 0
|
|
347
|
+
parts.any? do |part|
|
|
348
|
+
part == '..' ? (depth -= 1) : (depth += 1 unless part == '.')
|
|
349
|
+
depth.negative?
|
|
350
|
+
end
|
|
351
|
+
end
|
|
278
352
|
end
|
|
279
353
|
|
|
280
354
|
def signing_sensitive?(path)
|
|
281
|
-
path.upcase.start_with?(
|
|
355
|
+
path.upcase.start_with?('META-INF/') && ['.RSA', '.DSA', '.EC', '.SF'].any? do |suffix|
|
|
356
|
+
path.upcase.end_with?(suffix)
|
|
357
|
+
end
|
|
282
358
|
end
|
|
283
359
|
|
|
284
360
|
def same_entry?(left, right)
|
|
@@ -290,10 +366,11 @@ module Zip
|
|
|
290
366
|
end
|
|
291
367
|
|
|
292
368
|
def nested_family(path)
|
|
293
|
-
return
|
|
294
|
-
return
|
|
295
|
-
return
|
|
296
|
-
|
|
369
|
+
return 'markdown' if path.match?(/\.m(?:d|arkdown)\z/i)
|
|
370
|
+
return 'json' if path.end_with?('.json')
|
|
371
|
+
return 'yaml' if path.match?(/\.ya?ml\z/i)
|
|
372
|
+
|
|
373
|
+
'xml' if path.end_with?('.xml')
|
|
297
374
|
end
|
|
298
375
|
|
|
299
376
|
def schema_path(path)
|
|
@@ -301,7 +378,7 @@ module Zip
|
|
|
301
378
|
end
|
|
302
379
|
|
|
303
380
|
def diagnostic(category, schema_path, message)
|
|
304
|
-
|
|
381
|
+
Binary::Merge.unsafe_diagnostic(schema_path: schema_path, byte_range: nil, message: message, category: category)
|
|
305
382
|
end
|
|
306
383
|
|
|
307
384
|
def render_error(category, schema_path, message)
|
|
@@ -309,3 +386,8 @@ module Zip
|
|
|
309
386
|
end
|
|
310
387
|
end
|
|
311
388
|
end
|
|
389
|
+
|
|
390
|
+
require 'binary/merge'
|
|
391
|
+
require_relative 'merge/backend'
|
|
392
|
+
|
|
393
|
+
Zip::Merge.register_backend!
|
data/lib/zip-merge.rb
CHANGED
data/sig/zip/merge.rbs
ADDED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zip-merge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: exe
|
|
9
9
|
cert_chain:
|
|
10
10
|
- |
|
|
11
11
|
-----BEGIN CERTIFICATE-----
|
|
@@ -37,46 +37,297 @@ cert_chain:
|
|
|
37
37
|
-----END CERTIFICATE-----
|
|
38
38
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
39
39
|
dependencies:
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: binary-merge
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - '='
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 7.1.1
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - '='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 7.1.1
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: tree_haver
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
43
57
|
requirements:
|
|
44
58
|
- - '='
|
|
45
59
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 7.
|
|
60
|
+
version: 7.1.1
|
|
47
61
|
type: :runtime
|
|
48
62
|
prerelease: false
|
|
49
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
64
|
requirements:
|
|
51
65
|
- - '='
|
|
52
66
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 7.
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
version: 7.1.1
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: version_gem
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.1'
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 1.1.15
|
|
78
|
+
type: :runtime
|
|
79
|
+
prerelease: false
|
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - "~>"
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '1.1'
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 1.1.15
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: kettle-dev
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '2.5'
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: 2.5.17
|
|
98
|
+
type: :development
|
|
99
|
+
prerelease: false
|
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - "~>"
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '2.5'
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 2.5.17
|
|
108
|
+
- !ruby/object:Gem::Dependency
|
|
109
|
+
name: bundler-audit
|
|
110
|
+
requirement: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - "~>"
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: 0.9.3
|
|
115
|
+
type: :development
|
|
116
|
+
prerelease: false
|
|
117
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - "~>"
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: 0.9.3
|
|
122
|
+
- !ruby/object:Gem::Dependency
|
|
123
|
+
name: rake
|
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - "~>"
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '13.0'
|
|
129
|
+
type: :development
|
|
130
|
+
prerelease: false
|
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - "~>"
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '13.0'
|
|
136
|
+
- !ruby/object:Gem::Dependency
|
|
137
|
+
name: require_bench
|
|
138
|
+
requirement: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - "~>"
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '1.0'
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 1.0.4
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1.0'
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: 1.0.4
|
|
156
|
+
- !ruby/object:Gem::Dependency
|
|
157
|
+
name: anonymous_loader
|
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - "~>"
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '0.1'
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: 0.1.3
|
|
166
|
+
type: :development
|
|
167
|
+
prerelease: false
|
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - "~>"
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0.1'
|
|
173
|
+
- - ">="
|
|
174
|
+
- !ruby/object:Gem::Version
|
|
175
|
+
version: 0.1.3
|
|
176
|
+
- !ruby/object:Gem::Dependency
|
|
177
|
+
name: appraisal2
|
|
178
|
+
requirement: !ruby/object:Gem::Requirement
|
|
179
|
+
requirements:
|
|
180
|
+
- - "~>"
|
|
181
|
+
- !ruby/object:Gem::Version
|
|
182
|
+
version: '3.2'
|
|
183
|
+
- - ">="
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: 3.2.0
|
|
186
|
+
type: :development
|
|
187
|
+
prerelease: false
|
|
188
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
189
|
+
requirements:
|
|
190
|
+
- - "~>"
|
|
191
|
+
- !ruby/object:Gem::Version
|
|
192
|
+
version: '3.2'
|
|
193
|
+
- - ">="
|
|
194
|
+
- !ruby/object:Gem::Version
|
|
195
|
+
version: 3.2.0
|
|
196
|
+
- !ruby/object:Gem::Dependency
|
|
197
|
+
name: kettle-test
|
|
198
|
+
requirement: !ruby/object:Gem::Requirement
|
|
199
|
+
requirements:
|
|
200
|
+
- - "~>"
|
|
201
|
+
- !ruby/object:Gem::Version
|
|
202
|
+
version: '2.0'
|
|
203
|
+
- - ">="
|
|
204
|
+
- !ruby/object:Gem::Version
|
|
205
|
+
version: 2.0.19
|
|
206
|
+
type: :development
|
|
207
|
+
prerelease: false
|
|
208
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - "~>"
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '2.0'
|
|
213
|
+
- - ">="
|
|
214
|
+
- !ruby/object:Gem::Version
|
|
215
|
+
version: 2.0.19
|
|
216
|
+
- !ruby/object:Gem::Dependency
|
|
217
|
+
name: turbo_tests2
|
|
218
|
+
requirement: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - "~>"
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '3.2'
|
|
223
|
+
- - ">="
|
|
224
|
+
- !ruby/object:Gem::Version
|
|
225
|
+
version: 3.2.4
|
|
226
|
+
type: :development
|
|
227
|
+
prerelease: false
|
|
228
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
229
|
+
requirements:
|
|
230
|
+
- - "~>"
|
|
231
|
+
- !ruby/object:Gem::Version
|
|
232
|
+
version: '3.2'
|
|
233
|
+
- - ">="
|
|
234
|
+
- !ruby/object:Gem::Version
|
|
235
|
+
version: 3.2.4
|
|
236
|
+
- !ruby/object:Gem::Dependency
|
|
237
|
+
name: ruby-progressbar
|
|
238
|
+
requirement: !ruby/object:Gem::Requirement
|
|
239
|
+
requirements:
|
|
240
|
+
- - "~>"
|
|
241
|
+
- !ruby/object:Gem::Version
|
|
242
|
+
version: '1.13'
|
|
243
|
+
type: :development
|
|
244
|
+
prerelease: false
|
|
245
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
246
|
+
requirements:
|
|
247
|
+
- - "~>"
|
|
248
|
+
- !ruby/object:Gem::Version
|
|
249
|
+
version: '1.13'
|
|
250
|
+
- !ruby/object:Gem::Dependency
|
|
251
|
+
name: stone_checksums
|
|
252
|
+
requirement: !ruby/object:Gem::Requirement
|
|
253
|
+
requirements:
|
|
254
|
+
- - "~>"
|
|
255
|
+
- !ruby/object:Gem::Version
|
|
256
|
+
version: '1.0'
|
|
257
|
+
- - ">="
|
|
258
|
+
- !ruby/object:Gem::Version
|
|
259
|
+
version: 1.0.8
|
|
260
|
+
type: :development
|
|
261
|
+
prerelease: false
|
|
262
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
263
|
+
requirements:
|
|
264
|
+
- - "~>"
|
|
265
|
+
- !ruby/object:Gem::Version
|
|
266
|
+
version: '1.0'
|
|
267
|
+
- - ">="
|
|
268
|
+
- !ruby/object:Gem::Version
|
|
269
|
+
version: 1.0.8
|
|
270
|
+
- !ruby/object:Gem::Dependency
|
|
271
|
+
name: gitmoji-regex
|
|
272
|
+
requirement: !ruby/object:Gem::Requirement
|
|
273
|
+
requirements:
|
|
274
|
+
- - "~>"
|
|
275
|
+
- !ruby/object:Gem::Version
|
|
276
|
+
version: '2.0'
|
|
277
|
+
- - ">="
|
|
278
|
+
- !ruby/object:Gem::Version
|
|
279
|
+
version: 2.0.11
|
|
280
|
+
type: :development
|
|
281
|
+
prerelease: false
|
|
282
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
283
|
+
requirements:
|
|
284
|
+
- - "~>"
|
|
285
|
+
- !ruby/object:Gem::Version
|
|
286
|
+
version: '2.0'
|
|
287
|
+
- - ">="
|
|
288
|
+
- !ruby/object:Gem::Version
|
|
289
|
+
version: 2.0.11
|
|
290
|
+
description: "☯️ Portable ZIP inventory, planning, nested dispatch, and raw-preservation
|
|
291
|
+
rendering helpers for Structured Merge."
|
|
56
292
|
email:
|
|
57
|
-
-
|
|
293
|
+
- floss@galtzo.com
|
|
58
294
|
executables: []
|
|
59
295
|
extensions: []
|
|
60
296
|
extra_rdoc_files: []
|
|
61
297
|
files:
|
|
298
|
+
- LICENSE.md
|
|
299
|
+
- README.md
|
|
62
300
|
- lib/zip-merge.rb
|
|
63
301
|
- lib/zip/merge.rb
|
|
302
|
+
- lib/zip/merge/backend.rb
|
|
64
303
|
- lib/zip/merge/version.rb
|
|
304
|
+
- sig/zip/merge.rbs
|
|
65
305
|
homepage: https://github.com/structuredmerge/structuredmerge-ruby
|
|
66
306
|
licenses:
|
|
67
307
|
- AGPL-3.0-only
|
|
68
308
|
- PolyForm-Small-Business-1.0.0
|
|
69
309
|
metadata:
|
|
70
310
|
homepage_uri: https://structuredmerge.org
|
|
71
|
-
source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.
|
|
72
|
-
changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.
|
|
311
|
+
source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.1
|
|
312
|
+
changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.1/CHANGELOG.md
|
|
73
313
|
bug_tracker_uri: https://github.com/structuredmerge/structuredmerge-ruby/issues
|
|
74
|
-
documentation_uri: https://www.rubydoc.info/gems/zip-merge/7.
|
|
314
|
+
documentation_uri: https://www.rubydoc.info/gems/zip-merge/7.1.1
|
|
75
315
|
funding_uri: https://github.com/sponsors/pboling
|
|
76
316
|
wiki_uri: https://github.com/structuredmerge/structuredmerge-ruby/wiki
|
|
317
|
+
news_uri: https://www.railsbling.com/tags/zip-merge
|
|
77
318
|
discord_uri: https://discord.gg/3qme4XHNKN
|
|
319
|
+
mailing_list_uri: https://www.rubyforum.org/tag/structuredmerge
|
|
78
320
|
rubygems_mfa_required: 'true'
|
|
79
|
-
rdoc_options:
|
|
321
|
+
rdoc_options:
|
|
322
|
+
- "--title"
|
|
323
|
+
- zip-merge - ☯️ Structured Merge ZIP merge planning and rendering helpers for Ruby
|
|
324
|
+
- "--main"
|
|
325
|
+
- README.md
|
|
326
|
+
- "--exclude"
|
|
327
|
+
- "^sig/"
|
|
328
|
+
- "--line-numbers"
|
|
329
|
+
- "--inline-source"
|
|
330
|
+
- "--quiet"
|
|
80
331
|
require_paths:
|
|
81
332
|
- lib
|
|
82
333
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
@@ -90,7 +341,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
341
|
- !ruby/object:Gem::Version
|
|
91
342
|
version: '0'
|
|
92
343
|
requirements: []
|
|
93
|
-
rubygems_version: 4.0.
|
|
344
|
+
rubygems_version: 4.0.17
|
|
94
345
|
specification_version: 4
|
|
95
|
-
summary: Structured Merge ZIP merge planning and rendering helpers for Ruby
|
|
346
|
+
summary: "☯️ Structured Merge ZIP merge planning and rendering helpers for Ruby"
|
|
96
347
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|