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 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
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
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