gem_override_marker 0.1.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.
- checksums.yaml +7 -0
- data/.rubocop.yml +1 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.ja.md +302 -0
- data/README.md +316 -0
- data/gem_override_marker.gemspec +33 -0
- data/lib/gem_override_marker/diff_generator.rb +40 -0
- data/lib/gem_override_marker/gem_source.rb +75 -0
- data/lib/gem_override_marker/marker.rb +88 -0
- data/lib/gem_override_marker/railtie.rb +11 -0
- data/lib/gem_override_marker/registry.rb +76 -0
- data/lib/gem_override_marker/source_extractor.rb +69 -0
- data/lib/gem_override_marker/version.rb +3 -0
- data/lib/gem_override_marker.rb +79 -0
- data/lib/tasks/gem_override_marker.rake +89 -0
- data/spec/fixtures/Gemfile.lock +14 -0
- data/spec/fixtures/Gemfile.lock.mismatch +14 -0
- data/spec/fixtures/override_module.rb +12 -0
- data/spec/fixtures/repo/no_marker.rb +8 -0
- data/spec/fixtures/repo/sub/another.erb +3 -0
- data/spec/fixtures/repo/with_marker.rb +7 -0
- data/spec/fixtures/source_class.rb +14 -0
- metadata +117 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 265013f3f192df295c3802b378f3251fdfb9594d03bf3b7b274625ca5bae7545
|
|
4
|
+
data.tar.gz: 7dc4627efb1af8adb2f883d0bc306961ede5a23fedb182a88f9f131c58dd2dda
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7659a48c44abb5afe4dde1b9c6085d3fec4973e59455357ea3800753d4d0e4d5eb77340a4c5bf3814024cc477a6f5fa4b09f1cdf74f83cc65ce6c96d7552526a
|
|
7
|
+
data.tar.gz: acd6fdf55af7c6688a2ea7b2b8ee12060dc1b95ca404dd23958f9b76e66d2a631b4942d80fc553a52066a34a88b38565cea5f742f977880a02ca026bdc28a94b
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
inherit_from: ../../.rubocop.yml
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 be agile Co., Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.ja.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# gem_override_marker
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 日本語
|
|
4
|
+
|
|
5
|
+
gem の中のメソッドや view をカスタムしたいとき、本家のメソッドや view を
|
|
6
|
+
コピーして必要な箇所だけ修正することがあります。この方法は、gem のバージョン
|
|
7
|
+
アップ時にコピー元のメソッドや view が書き換わっていると、変更に気づけず
|
|
8
|
+
バグにつながります。
|
|
9
|
+
|
|
10
|
+
`gem_override_marker` は、gem のバージョンアップ時に、新しいバージョンへ
|
|
11
|
+
同等のカスタムを効率的に再適用するための手段を提供します。
|
|
12
|
+
|
|
13
|
+
## 使い方 (チュートリアル)
|
|
14
|
+
|
|
15
|
+
`greeter` という gem に `Greeter::Message#hello` というメソッドがあるとします。
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# greeter 1.0.0 の本家コード:
|
|
19
|
+
# gems/greeter-1.0.0/lib/greeter/message.rb
|
|
20
|
+
module Greeter
|
|
21
|
+
class Message
|
|
22
|
+
def hello(name)
|
|
23
|
+
"Hello, #{name}."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 1. カスタムして、マーカーを書く
|
|
30
|
+
|
|
31
|
+
この挨拶を日本語にしたい。本家を再オープンして `hello` を上書きします
|
|
32
|
+
(モンキーパッチ)。このとき、**どの gem の・どの版の・どこを基準にしたか**を
|
|
33
|
+
マーカーで1行書いておきます。
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
# app/overrides/greeter_patch.rb
|
|
37
|
+
# @gem-override greeter-1.0.0/lib/greeter/message.rb#hello
|
|
38
|
+
# @see https://github.com/your-org/your-repo/issues/42
|
|
39
|
+
module Greeter
|
|
40
|
+
class Message
|
|
41
|
+
def hello(name)
|
|
42
|
+
"こんにちは、#{name}さん。"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. マーカーが認識されているか確認
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
$ rake gem_override_marker:list
|
|
52
|
+
app/overrides/greeter_patch.rb:2 greeter-1.0.0/lib/greeter/message.rb#hello
|
|
53
|
+
|
|
54
|
+
1 marker(s)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
ついでに、本家との差分(=自分が何を変えたか)も今すぐ見られます。
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
$ rake gem_override_marker:diff
|
|
61
|
+
--- source greeter-1.0.0/lib/greeter/message.rb#hello
|
|
62
|
+
+++ override app/overrides/greeter_patch.rb#hello
|
|
63
|
+
@@ -1,3 +1,3 @@
|
|
64
|
+
def hello(name)
|
|
65
|
+
- "Hello, #{name}."
|
|
66
|
+
+ "こんにちは、#{name}さん。"
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. gem がバージョンアップした
|
|
71
|
+
|
|
72
|
+
`greeter` 2.0.0 が出て、本家の `hello` の実装が変わったとします。
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
# greeter 2.0.0 の本家コード (実装が変わった):
|
|
76
|
+
module Greeter
|
|
77
|
+
class Message
|
|
78
|
+
def hello(name)
|
|
79
|
+
greeting = "Hello"
|
|
80
|
+
"#{greeting}, #{name}! Welcome."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`Gemfile` を 2.0.0 に上げて `bundle update` します。**自分のカスタムは
|
|
87
|
+
まだ 1.0.0 ベースのまま**で、本家の `! Welcome.` のような変更は反映されて
|
|
88
|
+
いません。
|
|
89
|
+
|
|
90
|
+
### 4. 「以前の版で何を変えたか」を確認する
|
|
91
|
+
|
|
92
|
+
`bundle update` しても 1.0.0 のソースは `gems/` に残っています。マーカーは
|
|
93
|
+
まだ `greeter-1.0.0` を指しているので、`diff` は **1.0.0 の本家 vs 自分の
|
|
94
|
+
カスタム**、つまり「自分が何を変えたか」をそのまま見せてくれます。
|
|
95
|
+
|
|
96
|
+
```console
|
|
97
|
+
$ rake gem_override_marker:diff
|
|
98
|
+
--- source greeter-1.0.0/lib/greeter/message.rb#hello
|
|
99
|
+
+++ override app/overrides/greeter_patch.rb#hello
|
|
100
|
+
@@ -1,3 +1,3 @@
|
|
101
|
+
def hello(name)
|
|
102
|
+
- "Hello, #{name}."
|
|
103
|
+
+ "こんにちは、#{name}さん。"
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
→ 自分がやったのは「挨拶を日本語にした」だけだと分かります。
|
|
108
|
+
|
|
109
|
+
### 5. 新しい本家に、その変更を当て直す
|
|
110
|
+
|
|
111
|
+
本家 2.0.0 の新しい実装(`greeting` 変数・`Welcome.` の追加)を土台にして、
|
|
112
|
+
自分の変更(日本語化)だけを乗せ直します。あわせてマーカーの版を 2.0.0 に
|
|
113
|
+
更新します。
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
# app/overrides/greeter_patch.rb
|
|
117
|
+
# @gem-override greeter-2.0.0/lib/greeter/message.rb#hello
|
|
118
|
+
# @see https://github.com/your-org/your-repo/issues/42
|
|
119
|
+
module Greeter
|
|
120
|
+
class Message
|
|
121
|
+
def hello(name)
|
|
122
|
+
greeting = "こんにちは"
|
|
123
|
+
"#{greeting}、#{name}さん!ようこそ。"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
最後に、新しい本家との差分が意図通り(日本語化だけ)になっているか確認します。
|
|
130
|
+
|
|
131
|
+
```console
|
|
132
|
+
$ rake gem_override_marker:diff
|
|
133
|
+
--- source greeter-2.0.0/lib/greeter/message.rb#hello
|
|
134
|
+
+++ override app/overrides/greeter_patch.rb#hello
|
|
135
|
+
@@ -1,4 +1,4 @@
|
|
136
|
+
def hello(name)
|
|
137
|
+
- greeting = "Hello"
|
|
138
|
+
- "#{greeting}, #{name}! Welcome."
|
|
139
|
+
+ greeting = "こんにちは"
|
|
140
|
+
+ "#{greeting}、#{name}さん!ようこそ。"
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
本家 2.0.0 の `Welcome.` を取りこぼさず、自分の「日本語化」だけが差分として
|
|
145
|
+
残りました。これで追随完了です。
|
|
146
|
+
|
|
147
|
+
## 設計の考え方:「自分の差分」を新本家に塗り直す
|
|
148
|
+
|
|
149
|
+
チュートリアルのステップ 4〜5 がこのツールの肝です。提供する差分が
|
|
150
|
+
「**本家 vs 自分の上書き**」だけなのは、次の考え方を前提にしているためです。
|
|
151
|
+
|
|
152
|
+
- ✅ **自分が本家に加えた差分だけを確認し、それを新しい本家に対して当て直す**
|
|
153
|
+
- ❌ 「旧本家と新本家の差分」を上書きに取り込む、ではない
|
|
154
|
+
|
|
155
|
+
後者は本家が大規模にリファクタしていると無関係な差分が大量に出るだけで大変
|
|
156
|
+
です。自分が知るべきは「自分が何を変えたか」だけで、それを新しい土台に塗り
|
|
157
|
+
直す方がずっと簡単です。上書きが10個あっても、見るのは「自分が触った箇所」
|
|
158
|
+
だけ。本家の巨大な changelog を全部読む必要はありません。
|
|
159
|
+
|
|
160
|
+
これを成立させているのが「`bundle update` しても旧版のソースは `gems/` に残る」
|
|
161
|
+
という事実です。マーカーが旧版を指している限り、新版を入れた後でも
|
|
162
|
+
「旧版 vs 自分の上書き」を再生でき、それを見ながら新版に当て直せます。
|
|
163
|
+
|
|
164
|
+
### 本家が変わった上書きを選別する (upstream_diff)
|
|
165
|
+
|
|
166
|
+
複数の gem をまとめてバージョンアップしたとき、全ての上書きを律儀に当て直す
|
|
167
|
+
のは無駄です。上書き対象によっては本家側が一切変わっておらず、その場合は
|
|
168
|
+
マーカーの版を上げるだけで済みます。`upstream_diff` は、各マーカーの対象に
|
|
169
|
+
ついて **本家の2版間 (旧版 → 新版) の差分** を出すことで、この2グループを
|
|
170
|
+
選別します (本家 vs 自分の上書き、ではない点に注意)。
|
|
171
|
+
|
|
172
|
+
```console
|
|
173
|
+
$ rake gem_override_marker:upstream_diff
|
|
174
|
+
========================================================================
|
|
175
|
+
app/overrides/greeter_patch.rb:2 greeter-1.0.0/lib/greeter/message.rb#hello
|
|
176
|
+
========================================================================
|
|
177
|
+
--- upstream greeter-1.0.0/lib/greeter/message.rb#hello
|
|
178
|
+
+++ upstream greeter-2.0.0/lib/greeter/message.rb#hello
|
|
179
|
+
@@ -1,3 +1,4 @@
|
|
180
|
+
def hello(name)
|
|
181
|
+
- "Hello, #{name}."
|
|
182
|
+
+ greeting = "Hello"
|
|
183
|
+
+ "#{greeting}, #{name}! Welcome."
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- **変更なし** (`(no upstream change)`) → 本家はその箇所を触っていないので、
|
|
188
|
+
上書きコードはそのままにして、マーカーの版だけ上げます (例: 1.0.0 → 2.0.0)。
|
|
189
|
+
- **変更あり** → `diff` で「自分が何を変えたか」を確認し、ステップ 5 の要領で
|
|
190
|
+
新しい本家に当て直して、マーカーの版を上げます。
|
|
191
|
+
|
|
192
|
+
比較先の版は省略時 `Gemfile.lock` の版 (アップグレード後の版) を使います。
|
|
193
|
+
`to_version` を渡せば任意の版と比較できます。これは設計の考え方を崩しません
|
|
194
|
+
— 本家の差分を上書きにマージするのではなく、「どこを当て直す必要があるか」を
|
|
195
|
+
見極めるための道具です。
|
|
196
|
+
|
|
197
|
+
### マーカーの版ズレを検出する (check)
|
|
198
|
+
|
|
199
|
+
当て直した後にマーカーの版を更新し忘れると、マーカーが指す版 (旧版) と
|
|
200
|
+
`Gemfile.lock` の実際の版 (新版) がズレます。`check` はこのズレを検出し、
|
|
201
|
+
ズレがあれば終了コード 1 で落とします (CI で回せる)。マーカーを「付けた
|
|
202
|
+
まま放置」にしないための番人です。
|
|
203
|
+
|
|
204
|
+
```console
|
|
205
|
+
$ rake gem_override_marker:check
|
|
206
|
+
❌ Marker versions differ from Gemfile.lock (possible outdated overrides):
|
|
207
|
+
app/overrides/greeter_patch.rb:2
|
|
208
|
+
marker version: greeter-1.0.0
|
|
209
|
+
locked version: greeter-2.0.0
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## マーカーの書き方
|
|
213
|
+
|
|
214
|
+
上書きしているメソッドの直前 / 上書きファイルの冒頭に1行で宣言します。
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
# @gem-override spree_core-5.1.6/app/models/spree/stock/packer.rb#default_package
|
|
218
|
+
def default_package
|
|
219
|
+
# ...
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
view / partial の丸ごと上書きはメソッド名を省略し、ファイル全体を対象とします。
|
|
224
|
+
|
|
225
|
+
```erb
|
|
226
|
+
<%# @gem-override spree_storefront-5.1.6/app/views/spree/products/_cart_form.html.erb %>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
マーカー1行の形式は `{gem名}-{バージョン}/{gem内パス}#{メソッド名}` です。
|
|
230
|
+
gem 名を含むため、1リポジトリから複数 gem が配布される場合 (Spree 等) でも
|
|
231
|
+
「どの gem の版と比較するか」が一意に決まります。
|
|
232
|
+
|
|
233
|
+
ツールが処理に使うのは `@gem-override` の行だけです。なぜその上書きをしたのか、
|
|
234
|
+
関連 issue はどれか、といった補足は、続く行に自由に書けます (ツールは無視します)。
|
|
235
|
+
issue へのポインタを残すなら YARD の `@see` タグが使えます。
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
# @gem-override spree_core-5.1.6/app/models/spree/stock/packer.rb#default_package
|
|
239
|
+
# @see https://github.com/your-org/your-repo/issues/1187
|
|
240
|
+
def default_package
|
|
241
|
+
# ...
|
|
242
|
+
end
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
マーカーは「コメント記号 + 半角スペース1個ちょうど + `@gem-override`」で
|
|
246
|
+
始まる行だけを有効とします。ドキュメント中で字下げして例示したものや、文中に
|
|
247
|
+
引用したものを誤検出しないためです。
|
|
248
|
+
|
|
249
|
+
## コマンドリファレンス
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# マーカー一覧
|
|
253
|
+
rake gem_override_marker:list
|
|
254
|
+
|
|
255
|
+
# 本家 vs 上書き の差分 (マーカーの版を基準に比較)
|
|
256
|
+
rake gem_override_marker:diff
|
|
257
|
+
|
|
258
|
+
# 本家2版間の差分 (旧版 → Gemfile.lock の版、または指定した to_version)
|
|
259
|
+
rake gem_override_marker:upstream_diff
|
|
260
|
+
rake gem_override_marker:upstream_diff[,2.0.0]
|
|
261
|
+
|
|
262
|
+
# マーカーの版と Gemfile.lock の版のズレ検出 (ズレがあれば exit 1)
|
|
263
|
+
rake gem_override_marker:check
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
引数なしの `list` / `diff` / `upstream_diff` / `check` はリポジトリ全体の
|
|
267
|
+
全マーカーが対象です。引数 (selector) で対象を狙い撃ちできます。
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# ① 上書きファイルのパス → ファイル内の全マーカー
|
|
271
|
+
rake gem_override_marker:diff[app/overrides/greeter_patch.rb]
|
|
272
|
+
|
|
273
|
+
# ② パス + メソッド名 → そのメソッドだけ
|
|
274
|
+
rake gem_override_marker:diff[app/overrides/greeter_patch.rb,hello]
|
|
275
|
+
|
|
276
|
+
# ③ マーカーの論理名そのまま → 一意に狙い撃ち
|
|
277
|
+
rake gem_override_marker:diff[greeter-1.0.0/lib/greeter/message.rb#hello]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 動作の仕組み
|
|
281
|
+
|
|
282
|
+
参考までに、内部の処理は次の通りです。
|
|
283
|
+
|
|
284
|
+
- **メソッド抽出**: Ruby 3.3 標準の `prism` でソースを AST 解析し、対象メソッドの
|
|
285
|
+
`DefNode` だけを slice します。`def` から `end` を正規表現で切り出すのは
|
|
286
|
+
ネストした `end` の判別ができず不可能なため、パーサで抽出します。
|
|
287
|
+
- **インデント正規化**: 本家 (class 直下) と上書き (module 多階層) の
|
|
288
|
+
ネスト差を吸収するため、抽出後に dedent します。
|
|
289
|
+
- **diff**: `diff-lcs` で unified diff を生成します。
|
|
290
|
+
- **本家ソース解決**: 実環境の gem インストールパス
|
|
291
|
+
(`Gem.path` 配下の `gems/{gem名}-{版}/`) から解決します。`upstream_diff` は
|
|
292
|
+
同じパス解決を2つの版に対して行い (「`bundle update` しても旧版のソースは
|
|
293
|
+
`gems/` に残る」ことを利用)、同じ抽出処理で両者を diff します。
|
|
294
|
+
- **版の照合 (check)**: `Bundler::LockfileParser` で `Gemfile.lock` を読み、
|
|
295
|
+
マーカーの版と突き合わせます。`Gemfile.lock` のパスは Bundler が自動解決します。
|
|
296
|
+
|
|
297
|
+
これらは本家・上書きをテキストとして解析するだけなので、Rails の
|
|
298
|
+
`:environment` には依存せず、DB 接続やアプリ初期化を起動せずに動きます。
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT
|
data/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# gem_override_marker
|
|
2
|
+
|
|
3
|
+
English | [日本語](README.ja.md)
|
|
4
|
+
|
|
5
|
+
When you want to customize a method or view inside a gem, you often copy the
|
|
6
|
+
upstream method or view and edit just the parts you need. The problem: when the
|
|
7
|
+
gem is upgraded and the original method or view has changed, you won't notice,
|
|
8
|
+
and that silently becomes a bug.
|
|
9
|
+
|
|
10
|
+
`gem_override_marker` gives you a way to efficiently re-apply your customization
|
|
11
|
+
to the new version when a gem is upgraded.
|
|
12
|
+
|
|
13
|
+
## Tutorial
|
|
14
|
+
|
|
15
|
+
Suppose a gem `greeter` has a method `Greeter::Message#hello`.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# Upstream code of greeter 1.0.0:
|
|
19
|
+
# gems/greeter-1.0.0/lib/greeter/message.rb
|
|
20
|
+
module Greeter
|
|
21
|
+
class Message
|
|
22
|
+
def hello(name)
|
|
23
|
+
"Hello, #{name}."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 1. Customize it, and write a marker
|
|
30
|
+
|
|
31
|
+
Say you want the greeting in Japanese. You reopen the class and override `hello`
|
|
32
|
+
(a monkey patch). At this point you write a one-line **marker** declaring
|
|
33
|
+
**which gem, which version, and what you based it on**.
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
# app/overrides/greeter_patch.rb
|
|
37
|
+
# @gem-override greeter-1.0.0/lib/greeter/message.rb#hello
|
|
38
|
+
# @see https://github.com/your-org/your-repo/issues/42
|
|
39
|
+
module Greeter
|
|
40
|
+
class Message
|
|
41
|
+
def hello(name)
|
|
42
|
+
"こんにちは、#{name}さん。"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Check that the marker is recognized
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
$ rake gem_override_marker:list
|
|
52
|
+
app/overrides/greeter_patch.rb:2 greeter-1.0.0/lib/greeter/message.rb#hello
|
|
53
|
+
|
|
54
|
+
1 marker(s)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You can also see the diff against upstream (i.e. what you changed) right away.
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
$ rake gem_override_marker:diff
|
|
61
|
+
--- source greeter-1.0.0/lib/greeter/message.rb#hello
|
|
62
|
+
+++ override app/overrides/greeter_patch.rb#hello
|
|
63
|
+
@@ -1,3 +1,3 @@
|
|
64
|
+
def hello(name)
|
|
65
|
+
- "Hello, #{name}."
|
|
66
|
+
+ "こんにちは、#{name}さん。"
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. The gem gets upgraded
|
|
71
|
+
|
|
72
|
+
Suppose `greeter` 2.0.0 is released and the upstream `hello` implementation has
|
|
73
|
+
changed.
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# Upstream code of greeter 2.0.0 (implementation changed):
|
|
77
|
+
module Greeter
|
|
78
|
+
class Message
|
|
79
|
+
def hello(name)
|
|
80
|
+
greeting = "Hello"
|
|
81
|
+
"#{greeting}, #{name}! Welcome."
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
You bump `Gemfile` to 2.0.0 and run `bundle update`. **Your customization is
|
|
88
|
+
still based on 1.0.0**, so upstream changes like `! Welcome.` are not reflected.
|
|
89
|
+
|
|
90
|
+
### 4. See "what you changed in the old version"
|
|
91
|
+
|
|
92
|
+
Even after `bundle update`, the 1.0.0 source remains under `gems/`. Since the
|
|
93
|
+
marker still points to `greeter-1.0.0`, `diff` shows **upstream 1.0.0 vs your
|
|
94
|
+
customization** — that is, exactly what you changed.
|
|
95
|
+
|
|
96
|
+
```console
|
|
97
|
+
$ rake gem_override_marker:diff
|
|
98
|
+
--- source greeter-1.0.0/lib/greeter/message.rb#hello
|
|
99
|
+
+++ override app/overrides/greeter_patch.rb#hello
|
|
100
|
+
@@ -1,3 +1,3 @@
|
|
101
|
+
def hello(name)
|
|
102
|
+
- "Hello, #{name}."
|
|
103
|
+
+ "こんにちは、#{name}さん。"
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
→ You can see that all you did was "make the greeting Japanese".
|
|
108
|
+
|
|
109
|
+
### 5. Re-apply that change onto the new upstream
|
|
110
|
+
|
|
111
|
+
Take upstream 2.0.0's new implementation (the `greeting` variable and the added
|
|
112
|
+
`Welcome.`) as the base, and re-apply only your change (the Japanese greeting).
|
|
113
|
+
Also update the marker version to 2.0.0.
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
# app/overrides/greeter_patch.rb
|
|
117
|
+
# @gem-override greeter-2.0.0/lib/greeter/message.rb#hello
|
|
118
|
+
# @see https://github.com/your-org/your-repo/issues/42
|
|
119
|
+
module Greeter
|
|
120
|
+
class Message
|
|
121
|
+
def hello(name)
|
|
122
|
+
greeting = "こんにちは"
|
|
123
|
+
"#{greeting}、#{name}さん!ようこそ。"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Finally, confirm that the diff against the new upstream is as intended (just the
|
|
130
|
+
Japanese greeting).
|
|
131
|
+
|
|
132
|
+
```console
|
|
133
|
+
$ rake gem_override_marker:diff
|
|
134
|
+
--- source greeter-2.0.0/lib/greeter/message.rb#hello
|
|
135
|
+
+++ override app/overrides/greeter_patch.rb#hello
|
|
136
|
+
@@ -1,4 +1,4 @@
|
|
137
|
+
def hello(name)
|
|
138
|
+
- greeting = "Hello"
|
|
139
|
+
- "#{greeting}, #{name}! Welcome."
|
|
140
|
+
+ greeting = "こんにちは"
|
|
141
|
+
+ "#{greeting}、#{name}さん!ようこそ。"
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Upstream 2.0.0's `Welcome.` is preserved, and only your "Japanese greeting" shows
|
|
146
|
+
up as the diff. You have successfully kept up with the upgrade.
|
|
147
|
+
|
|
148
|
+
## Design philosophy: re-apply "your diff" onto the new upstream
|
|
149
|
+
|
|
150
|
+
Steps 4–5 of the tutorial are the heart of this tool. The diff it provides is
|
|
151
|
+
only "**upstream vs your override**", and that is by design — it assumes you
|
|
152
|
+
work this way:
|
|
153
|
+
|
|
154
|
+
- ✅ **Review only the diff you added on top of upstream, then re-apply it onto
|
|
155
|
+
the new upstream**
|
|
156
|
+
- ❌ NOT "merge the diff between old and new upstream into your override"
|
|
157
|
+
|
|
158
|
+
The latter is painful: if upstream has been heavily refactored, a huge amount of
|
|
159
|
+
unrelated diff shows up. All you need to know is "what you changed", and
|
|
160
|
+
re-applying that onto the new base is far easier. Even if you have 10 overrides,
|
|
161
|
+
you only look at the spots you touched — you don't have to read the entire
|
|
162
|
+
upstream changelog.
|
|
163
|
+
|
|
164
|
+
What makes this possible is the fact that **`bundle update` leaves the old
|
|
165
|
+
version's source under `gems/`**. As long as the marker points to the old
|
|
166
|
+
version, you can regenerate "old upstream vs your override" even after installing
|
|
167
|
+
the new version, and use it as a guide while re-applying onto the new version.
|
|
168
|
+
|
|
169
|
+
### Triaging which overrides upstream actually changed (upstream_diff)
|
|
170
|
+
|
|
171
|
+
When many gems are upgraded at once, re-applying every override blindly is
|
|
172
|
+
wasteful: some overrides target a spot upstream didn't touch at all, so all you
|
|
173
|
+
need to do there is bump the marker version. `upstream_diff` tells the two
|
|
174
|
+
groups apart by diffing the **two upstream versions** (old → new) for each
|
|
175
|
+
marker's target — not upstream vs your override.
|
|
176
|
+
|
|
177
|
+
```console
|
|
178
|
+
$ rake gem_override_marker:upstream_diff
|
|
179
|
+
========================================================================
|
|
180
|
+
app/overrides/greeter_patch.rb:2 greeter-1.0.0/lib/greeter/message.rb#hello
|
|
181
|
+
========================================================================
|
|
182
|
+
--- upstream greeter-1.0.0/lib/greeter/message.rb#hello
|
|
183
|
+
+++ upstream greeter-2.0.0/lib/greeter/message.rb#hello
|
|
184
|
+
@@ -1,3 +1,4 @@
|
|
185
|
+
def hello(name)
|
|
186
|
+
- "Hello, #{name}."
|
|
187
|
+
+ greeting = "Hello"
|
|
188
|
+
+ "#{greeting}, #{name}! Welcome."
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **No change** (`(no upstream change)`) → upstream didn't touch this spot, so
|
|
193
|
+
leave the override code alone and just bump the marker version (e.g. 1.0.0 →
|
|
194
|
+
2.0.0).
|
|
195
|
+
- **Changed** → use `diff` to review what *you* changed, then re-apply it onto
|
|
196
|
+
the new upstream as in step 5, and bump the marker version.
|
|
197
|
+
|
|
198
|
+
The target version defaults to the one in `Gemfile.lock` (the upgraded
|
|
199
|
+
version). Pass `to_version` to compare against a specific version instead. This
|
|
200
|
+
keeps the design philosophy intact — it does not merge upstream's diff into your
|
|
201
|
+
override; it only helps you decide where re-applying is even needed.
|
|
202
|
+
|
|
203
|
+
### Detecting version drift (check)
|
|
204
|
+
|
|
205
|
+
If you forget to update the marker version after re-applying, the version the
|
|
206
|
+
marker points to (old) and the actual version in `Gemfile.lock` (new) will
|
|
207
|
+
drift. `check` detects this drift and exits with code 1 if found (so it can run
|
|
208
|
+
in CI). It is the watchdog that keeps markers from being "set and forgotten".
|
|
209
|
+
|
|
210
|
+
```console
|
|
211
|
+
$ rake gem_override_marker:check
|
|
212
|
+
❌ Marker versions differ from Gemfile.lock (possible outdated overrides):
|
|
213
|
+
app/overrides/greeter_patch.rb:2
|
|
214
|
+
marker version: greeter-1.0.0
|
|
215
|
+
locked version: greeter-2.0.0
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Writing markers
|
|
219
|
+
|
|
220
|
+
Declare it on one line, right before the overridden method / at the top of the
|
|
221
|
+
overriding file.
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
# @gem-override spree_core-5.1.6/app/models/spree/stock/packer.rb#default_package
|
|
225
|
+
def default_package
|
|
226
|
+
# ...
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
For a whole-file override of a view / partial, omit the method name; the entire
|
|
231
|
+
file is the target.
|
|
232
|
+
|
|
233
|
+
```erb
|
|
234
|
+
<%# @gem-override spree_storefront-5.1.6/app/views/spree/products/_cart_form.html.erb %>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The marker format is `{gem}-{version}/{path-in-gem}#{method}`. Because it
|
|
238
|
+
includes the gem name, even when a single repository ships multiple gems (e.g.
|
|
239
|
+
Spree), "which gem's version to compare against" is unambiguous.
|
|
240
|
+
|
|
241
|
+
The tool only uses the `@gem-override` line. You can freely add notes on the
|
|
242
|
+
following lines — why you made the override, which issue it relates to, etc.
|
|
243
|
+
(the tool ignores them). To leave a pointer to an issue, the YARD `@see` tag
|
|
244
|
+
works well.
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# @gem-override spree_core-5.1.6/app/models/spree/stock/packer.rb#default_package
|
|
248
|
+
# @see https://github.com/your-org/your-repo/issues/1187
|
|
249
|
+
def default_package
|
|
250
|
+
# ...
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Only lines that start with "comment marker + exactly one space + `@gem-override`"
|
|
255
|
+
are treated as valid markers. This avoids false positives from indented examples
|
|
256
|
+
in documentation or quoted mentions in prose.
|
|
257
|
+
|
|
258
|
+
## Command reference
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# List markers
|
|
262
|
+
rake gem_override_marker:list
|
|
263
|
+
|
|
264
|
+
# Diff upstream vs override (compared against the marker's version)
|
|
265
|
+
rake gem_override_marker:diff
|
|
266
|
+
|
|
267
|
+
# Diff two upstream versions for each marker (old → Gemfile.lock version, or a given to_version)
|
|
268
|
+
rake gem_override_marker:upstream_diff
|
|
269
|
+
rake gem_override_marker:upstream_diff[,2.0.0]
|
|
270
|
+
|
|
271
|
+
# Detect drift between marker version and Gemfile.lock (exits 1 on mismatch)
|
|
272
|
+
rake gem_override_marker:check
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Without arguments, `list` / `diff` / `upstream_diff` / `check` target every
|
|
276
|
+
marker in the repository. They also accept a selector argument to target
|
|
277
|
+
specific markers.
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
# (1) override file path -> all markers in the file
|
|
281
|
+
rake gem_override_marker:diff[app/overrides/greeter_patch.rb]
|
|
282
|
+
|
|
283
|
+
# (2) path + method name -> just that method
|
|
284
|
+
rake gem_override_marker:diff[app/overrides/greeter_patch.rb,hello]
|
|
285
|
+
|
|
286
|
+
# (3) the marker's logical name -> a single unambiguous target
|
|
287
|
+
rake gem_override_marker:diff[greeter-1.0.0/lib/greeter/message.rb#hello]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## How it works
|
|
291
|
+
|
|
292
|
+
For reference, the internals are as follows.
|
|
293
|
+
|
|
294
|
+
- **Method extraction**: Parses the source into an AST with `prism` (standard in
|
|
295
|
+
Ruby 3.3) and slices only the target method's `DefNode`. Cutting from `def` to
|
|
296
|
+
`end` with a regex is impossible because nested `end`s can't be distinguished,
|
|
297
|
+
so a parser is used.
|
|
298
|
+
- **Indent normalization**: Upstream (directly under a class) and the override
|
|
299
|
+
(nested in modules) differ in nesting depth, so the slice is dedented to absorb
|
|
300
|
+
the difference.
|
|
301
|
+
- **Diff**: Generates a unified diff with `diff-lcs`.
|
|
302
|
+
- **Upstream source resolution**: Resolved from the actual gem install path
|
|
303
|
+
(`gems/{gem}-{version}/` under `Gem.path`). `upstream_diff` resolves the same
|
|
304
|
+
path for two versions — relying on `bundle update` leaving the old version's
|
|
305
|
+
source under `gems/` — and diffs them with the same extraction pipeline.
|
|
306
|
+
- **Version check**: Reads `Gemfile.lock` with `Bundler::LockfileParser` and
|
|
307
|
+
compares against the marker's version. The `Gemfile.lock` path is resolved
|
|
308
|
+
automatically by Bundler.
|
|
309
|
+
|
|
310
|
+
Since these only analyze upstream and the override as text, they do not depend on
|
|
311
|
+
Rails' `:environment`; they run without booting DB connections or app
|
|
312
|
+
initialization.
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
|
|
4
|
+
require "gem_override_marker/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.platform = Gem::Platform::RUBY
|
|
8
|
+
s.name = "gem_override_marker"
|
|
9
|
+
s.version = GemOverrideMarker::VERSION
|
|
10
|
+
s.summary = "Track diffs between gem source and your prepend/copy overrides via markers"
|
|
11
|
+
s.description = <<~DESC
|
|
12
|
+
When you override a gem's method (e.g. with prepend) or copy a gem's
|
|
13
|
+
view/partial and edit it, upstream changes on upgrade can silently break it.
|
|
14
|
+
gem_override_marker lets you declare what each override is based on with a
|
|
15
|
+
@gem-override marker, and generate the diff against upstream on demand, so you
|
|
16
|
+
can efficiently re-apply your customization to the new version.
|
|
17
|
+
DESC
|
|
18
|
+
s.required_ruby_version = ">= 3.3"
|
|
19
|
+
|
|
20
|
+
s.author = "be agile Co., Ltd."
|
|
21
|
+
s.email = "develop@be-agile.jp"
|
|
22
|
+
s.homepage = "https://github.com/be-agile/gem_override_marker"
|
|
23
|
+
s.licenses = [ "MIT" ]
|
|
24
|
+
|
|
25
|
+
s.files = `git ls-files`.split("\n").reject { |f| f.match(/^spec/) && !f.match(%r{^spec/fixtures}) }
|
|
26
|
+
s.require_path = "lib"
|
|
27
|
+
|
|
28
|
+
# prism は Ruby 3.3 以降は標準同梱だが、gem としての依存を明示しておく。
|
|
29
|
+
s.add_dependency "diff-lcs", ">= 1.2.0", "< 2.0"
|
|
30
|
+
s.add_dependency "prism", "~> 1.0"
|
|
31
|
+
|
|
32
|
+
s.add_development_dependency "rspec", "~> 3.0"
|
|
33
|
+
end
|