TL;DR

ミニマムなバージョンアップを行いたいときは bundle update --conservative を使おう

gem の更新

 ライブラリのバージョンアップをしようとしているとする。単に bundle update と叩いてすべての依存を一括でバージョンアップするのは、差分を追うことがまず不可能になってしまうから、個別のライブラリをひとつずつアップグレードしていきたい。

 功利的にも、例えば LRU 方式で一日ひとつずつのライブラリをアップグレードしていく、くらいのやり方であれば、それなりの頻度で更新を行っていけるし、チームに対してもよい影響を与えていけるだろう。

 というわけで、個別のライブラリをターゲットに bundle update を実施したいわけであるが、必ずしもそれで十分ではないことがある。変更範囲を最小限に bundle update を実行するには、 --conservative オプションを使おう、というのが今日の学びである。

サンプル

 適当な Gemfile と Gemfile.lock を用意してみた。

 Gemfile に記載された3つの gem のうち、 Gemfile.lock に定義されたバージョンと最新バージョンの乖離をテーブル化するとこうなる。

name current_version upstream_version
capistrano 3.11.0 3.14.1
capistrano-bundler 1.4.0 2.0.1
capistrano-rails 1.4.0 1.6.1
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

group :development do
  gem 'capistrano'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
end
GEM
  remote: https://rubygems.org/
  specs:
    airbrussh (1.4.0)
      sshkit (>= 1.6.1, != 1.7.0)
    capistrano (3.11.0)
      airbrussh (>= 1.0.0)
      i18n
      rake (>= 10.0.0)
      sshkit (>= 1.9.0)
    capistrano-bundler (1.4.0)
      capistrano (~> 3.1)
      sshkit (~> 1.2)
    capistrano-rails (1.4.0)
      capistrano (~> 3.1)
      capistrano-bundler (~> 1.1)
    concurrent-ruby (1.1.7)
    i18n (1.8.5)
      concurrent-ruby (~> 1.0)
    net-scp (3.0.0)
      net-ssh (>= 2.6.5, < 7.0.0)
    net-ssh (6.1.0)
    rake (13.0.1)
    sshkit (1.21.1)
      net-scp (>= 1.1.2)
      net-ssh (>= 2.8.0)

PLATFORMS
  ruby

DEPENDENCIES
  capistrano
  capistrano-bundler
  capistrano-rails

BUNDLED WITH
   2.1.4

 この設定のプロジェクトについて、 capistrano-rails だけを最新バージョンに更新してみよう。

単に bundle update capistrano-rails を実行する

 単に bundle update capistrano-rails と実行すると、差分としてはこうなる。

   specs:
     airbrussh (1.4.0)
       sshkit (>= 1.6.1, != 1.7.0)
-    capistrano (3.11.0)
+    capistrano (3.14.1)
       airbrussh (>= 1.0.0)
       i18n
       rake (>= 10.0.0)
       sshkit (>= 1.9.0)
-    capistrano-bundler (1.4.0)
+    capistrano-bundler (2.0.1)
       capistrano (~> 3.1)
-      sshkit (~> 1.2)
-    capistrano-rails (1.4.0)
+    capistrano-rails (1.6.1)
       capistrano (~> 3.1)
-      capistrano-bundler (~> 1.1)
+      capistrano-bundler (>= 1.1, < 3)
     concurrent-ruby (1.1.7)
     i18n (1.8.5)
       concurrent-ruby (~> 1.0)

capistrano-rails だけを更新することを意図したつもりが、依存先の capistranocapistrano-bundler も同時に更新されている。依存は解決されているため現実的に問題ないかもしれないとはいえ、例えば capistrano-bundler のメジャーバージョンが意図せず上がってしまっているのは実に気持ちが悪い。あくまで capistrano-rails を単体で更新するにはどうするのがよいだろう?

--conservative オプションを与えて実行する

 bundle update capistrano-rails --conservative とオプションを追加してあげればよい。結果、次の通りにきちんと狙った gem だけが更新できており、依存先の更新を巻き込んでしまわずに済んでいる。

   specs:
     airbrussh (1.4.0)
       sshkit (>= 1.6.1, != 1.7.0)
     capistrano (3.11.0)
       airbrussh (>= 1.0.0)
       i18n
       rake (>= 10.0.0)
       sshkit (>= 1.9.0)
     capistrano-bundler (1.4.0)
       capistrano (~> 3.1)
       sshkit (~> 1.2)
-    capistrano-rails (1.4.0)
+    capistrano-rails (1.6.1)
       capistrano (~> 3.1)
-      capistrano-bundler (~> 1.1)
+      capistrano-bundler (>= 1.1, < 3)
     concurrent-ruby (1.1.7)
     i18n (1.8.5)
       concurrent-ruby (~> 1.0)

 conservative というと必ずしもいい響きに聞こえないけれども、ことライブラリの更新というタスクについていうと、保守的に進めて悪いことなんかないと思っている。というより、デフォルトの振る舞いが --conservative であることを期待していた身にとっては、オプションを明示しない限り変更範囲が最小とならないというのはちょっと意外であった。思わぬ形でえた学びとして、正しく Today I Learned であった。