{"action":"closed","number":4030,"pull_request":{"url":"https://api.github.com/repos/bundler/bundler/pulls/4030","id":46705318,"html_url":"https://github.com/bundler/bundler/pull/4030","diff_url":"https://github.com/bundler/bundler/pull/4030.diff","patch_url":"https://github.com/bundler/bundler/pull/4030.patch","issue_url":"https://api.github.com/repos/bundler/bundler/issues/4030","number":4030,"state":"closed","locked":false,"title":"runtime: avoid cleaning up extensions directory","user":{"login":"Sirupsen","id":97400,"avatar_url":"https://avatars.githubusercontent.com/u/97400?v=3","gravatar_id":"","url":"https://api.github.com/users/Sirupsen","html_url":"https://github.com/Sirupsen","followers_url":"https://api.github.com/users/Sirupsen/followers","following_url":"https://api.github.com/users/Sirupsen/following{/other_user}","gists_url":"https://api.github.com/users/Sirupsen/gists{/gist_id}","starred_url":"https://api.github.com/users/Sirupsen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Sirupsen/subscriptions","organizations_url":"https://api.github.com/users/Sirupsen/orgs","repos_url":"https://api.github.com/users/Sirupsen/repos","events_url":"https://api.github.com/users/Sirupsen/events{/privacy}","received_events_url":"https://api.github.com/users/Sirupsen/received_events","type":"User","site_admin":false},"body":"When bundling gems via Git with native extensions with the `--clean` flag the `extensions/` directory should be left around.\r\n\r\nLacks a test, I'll follow up with that.\r\n\r\n---\r\n\r\n(optional debugging fairy tale for the record)\r\n\r\nIt all started an innocent Friday morning with a production Rails console taking a long time to boot (~2m). I started investigating why. `ps auxf` quickly surfaced that something fishy was going on:\r\n\r\n```\r\n8192     12831  0.3  0.0 196096 10696 pts/4    Ssl+ 15:48   0:00  \\_ borginit:shopify/console/1\r\n8192     12927  0.0  0.0   8316   668 pts/4    S+   15:48   0:00      \\_ /sbin/ppidshim /bin/bash -c exec bin/rails console\r\n8192     12929 48.0  0.3 853924 475960 pts/4   Sl+  15:48   0:10          \\_ ruby /app/bin/rails console\r\n8192     16762  0.0  0.0   8568   816 pts/4    S+   15:49   0:00              \\_ sh -c /usr/lib/shopify-ruby/2.1.6-shopify2/bin/ruby extconf.rb 2>&1\r\n8192     16764  7.7  0.1 290372 167536 pts/4   Sl+  15:49   0:00                  \\_ /usr/lib/shopify-ruby/2.1.6-shopify2/bin/ruby extconf.rb\r\n8192     23646  0.0  0.0   8568   820 pts/4    S+   15:49   0:00                      \\_ sh -c make >/artifacts/ruby/2.1.0/bundler/gems/nokogiri-e2821bee7e21/ext/nokogiri/tmp/x86_64-unknown-linux-\r\n8192     23647  0.0  0.0  10580  2492 pts/4    S+   15:49   0:00                          \\_ make\r\n8192     23652  0.0  0.0  10580  2776 pts/4    S+   15:49   0:00                              \\_ make all-recursive\r\n8192     23653  0.0  0.0  21696  3428 pts/4    S+   15:49   0:00                                  \\_ /bin/bash -c fail=; \\ if (target_option=k; case ${target_option-} in ?) ;; *) echo \"am__make_ru\r\n8192     23668  0.0  0.0  21700  2328 pts/4    S+   15:49   0:00                                      \\_ /bin/bash -c fail=; \\ if (target_option=k; case ${target_option-} in ?) ;; *) echo \"am__mak\r\n8192     23669  0.0  0.0  10580  2584 pts/4    S+   15:49   0:00                                          \\_ make all-am\r\n8192     24300  0.0  0.0  21660  3288 pts/4    S+   15:49   0:00                                              \\_ /bin/bash -c echo \"  CC      \" tree.lo;/bin/bash ./libtool --silent --tag=CC   --mo\r\n8192     24303  0.0  0.0  22420  4164 pts/4    S+   15:49   0:00                                                  \\_ /bin/bash ./libtool --silent --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I\r\n8192     24309  0.0  0.0  10852  2456 pts/4    S+   15:49   0:00                                                      \\_ gcc -DHAVE_CONFIG_H -I. -I./include -I./include -D_REENTRANT -fPIC -pedanti\r\n8192     24310  0.0  0.0  46200 16276 pts/4    R+   15:49   0:00                                                          \\_ /usr/lib/gcc/x86_64-linux-gnu/4.8/cc1 -quiet -I . -I ./include -I ./inc\r\n```\r\n\r\nFor some reason, `nokogiri` was compiling every time we booted. But only when booting the production console. `rails runner`, starting a Unicorn worker or firing up Resque all boot normally without doing any ridiculous compilation.\r\n\r\nI `gdb(1)`'ed into the process and ran my dear colleague [@csfrancis' MRI call stack script](https://gist.github.com/csfrancis/11376304) to obtain a Ruby backtrace to figure out where that compilation was happening, giving me:\r\n\r\n```\r\n/artifacts/ruby/2.1.0/gems/activesupport-4.2.4/lib/active_support/core_ext/kernel/agnostics.rb:7:in ``'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/builder.rb:73:in `run'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/ext_conf_builder.rb:38:in `block in build'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/tempfile.rb:324:in `open'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/ext_conf_builder.rb:17:in `build'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/builder.rb:161:in `block (2 levels) in build_extension'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/builder.rb:160:in `block in build_extension'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/monitor.rb:211:in `mon_synchronize'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/builder.rb:159:in `build_extension'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/builder.rb:198:in `block in build_extensions'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/ext/builder.rb:195:in `build_extensions'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/specification.rb:1436:in `block in build_extensions'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/user_interaction.rb:45:in `use_ui'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/specification.rb:1434:in `build_extensions'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/basic_specification.rb:56:in `contains_requirable_file?'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/specification.rb:915:in `block in find_by_path'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/specification.rb:883:in `block in each'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/forwardable.rb:183:in `each'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/specification.rb:882:in `each'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems/specification.rb:914:in `find_by_path'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/rubygems.rb:188:in `try_activate'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:150:in `block in search_file'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:158:in `block in each_localized_path'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:167:in `each_sublocale'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:157:in `each_localized_path'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:145:in `search_file'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:124:in `find'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:108:in `load'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/locale.rb:32:in `initialize'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/init.rb:114:in `init_config'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb/init.rb:16:in `setup'\r\n/usr/lib/shopify-ruby/2.1.6-shopify2/lib/ruby/2.1.0/irb.rb:380:in `start'\r\n/artifacts/ruby/2.1.0/gems/railties-4.2.4/lib/rails/commands/console.rb:110:in `start'\r\n/artifacts/ruby/2.1.0/gems/railties-4.2.4/lib/rails/commands/console.rb:9:in `start'\r\n/artifacts/ruby/2.1.0/gems/railties-4.2.4/lib/rails/commands/commands_tasks.rb:68:in `console'\r\n/artifacts/ruby/2.1.0/gems/railties-4.2.4/lib/rails/commands/commands_tasks.rb:39:in `run_command!'\r\n/artifacts/ruby/2.1.0/gems/railties-4.2.4/lib/rails/commands.rb:17:in `<top (required)>'\r\n/artifacts/ruby/2.1.0/gems/bootscale-0.5.1/lib/bootscale/core_ext.rb:4:in `require'\r\nbin/rails:5:in `<main>’\r\n```\r\n\r\nThis confirms that it does indeed happen as part of starting the REPL, and thus is only a problem with the production console. This problem did not manifest in development either.\r\n\r\nLooking into the `irb` source guided by the backtrace it's clear it attempts to pick up the file [`irb/encoding_aliases.rb`](https://github.com/ruby/ruby/blob/trunk/lib/irb/locale.rb#L31). Eventually it makes it's way down to [`IRB::Locale#search_file`](https://github.com/ruby/ruby/blob/trunk/lib/irb/locale.rb#L143-152) to try and find `irb/encoding_aliases.rb`:\r\n\r\n```ruby\r\n    def search_file(lib_paths, dir, file)\r\n      each_localized_path(dir, file) do |lc_path|\r\n        lib_paths.each do |libpath|\r\n          full_path = File.join(libpath, lc_path)\r\n          return full_path if File.readable?(full_path)\r\n        end\r\n        redo if defined?(Gem) and Gem.try_activate(lc_path)\r\n      end\r\n      nil\r\n    end\r\n```\r\n\r\nThe interesting code here is `Gem.try_activate(..)`. If Rubygems is loaded, it'll attempt to call [`Gem#try_activate`](https://github.com/ruby/ruby/blob/59991b6ac517fcaa4f8122a1da6552a66892449e/lib/rubygems.rb#L181) in an effort to expand the load path even further to try and find `irb/encoding_aliases.rb`. Eventually we end up in `Gem::Specification#build_extensions` as witnessed by the backtrace:\r\n\r\n```\r\n  def build_extensions # :nodoc:\r\n    return if default_gem?\r\n    return if extensions.empty?\r\n    return if installed_by_version < Gem::Version.new('2.2.0.preview.2')\r\n    return if File.exist? gem_build_complete_path\r\n    return if !File.writable?(base_dir)\r\n    return if !File.exist?(File.join(base_dir, 'extensions'))\r\n    # code to build\r\n  end\r\n```\r\n\r\nWhat seems to be happening is that `nokogiri` is not quitting this check, but simply proceeding down to build the extension once again.\r\n\r\nI added the following line to get an overview of a) whether any other extensions were getting built and I just happened to only catch `nokogiri`, b) figure out if any of those values were wrong:\r\n\r\n```ruby\r\nputs \"name=#{name} installed_by_version=#{installed_by_version.inspect} gem_build_complete_path=#{gem_build_complete_path} build_complete=#{File.exist?(gem_build_complete_path)} whatever=#{ !File.writable?(base_dir) && !File.exist?(File.join(base_dir, 'extensions'))}\"\r\n```\r\n\r\nThere are three interesting cases here I wanted to inspect the output of:\r\n\r\n1. Gem with rubygems as the source with extensions\r\n2. Gem with git as the source with extensions\r\n\r\nAn example of 1) is `Shopify/semian`:\r\n\r\n```\r\nname=semian installed_by_version=#<Gem::Version \"2.2.3\"> gem_build_complete_path=/artifacts/ruby/2.1.0/extensions/x86_64-linux/2.1.0-static/semian-0.3.0/gem.build_complete build_complete=true whatever=false\r\n```\r\n\r\nThis looks completely reasonable. For `nokogiri` as an example of 2):\r\n\r\n```\r\nname=nokogiri installed_by_version=#<Gem::Version \"2.2.3\"> gem_build_complete_path=/artifacts/ruby/2.1.0/bundler/gems/extensions/x86_64-linux/2.1.0-static/nokogiri-e2821bee7e21/gem.build_complete build_complete=false whatever=false\r\n```\r\n\r\nWhat was even more strange is that all other examples of 2) actually didn't compile. `nokogiri` is the only gem that actually compiles in the 2) group. The others look like this\r\n\r\n```\r\nname=json installed_by_version=#<Gem::Version \"0\"> gem_build_complete_path=/artifacts/ruby/2.1.0/bundler/gems/extensions/x86_64-linux/2.1.0-static/json-a41ce74ba367/gem.build_complete build_complete=false whatever=false\r\n```\r\n\r\nWhat's interesting about those two different examples of 2) is:\r\n\r\n1. `nokogiri` has a proper `installed_by_version`, but `json` has it at the default of `0`\r\n2. Both `json` and `nokogiri`'s `gem.build_complete` file doesn't exist\r\n3. `nokogiri` compiles, but `json` does not\r\n\r\n`gem.build_complete` is an empty file `rubygems` leaves around [after compiling the extension](https://github.com/ruby/ruby/blob/effdbf5936cc090a618e13c8f9a1b5412ebab2fa/lib/rubygems/ext/builder.rb#L203).\r\n\r\nThe reason we're compiling `nokogiri` and not `json` is that `json` has `installed_by_version` at the default value of `0`, which means the check above:\r\n\r\n```ruby\r\n    return if installed_by_version < Gem::Version.new('2.2.0.preview.2')\r\n```\r\n\r\nMakes `#build_extensions` return early, avoiding the compilation. However, this is just a coincidence (and probably a bug in `rubygems`). The check that should make it terminate is:\r\n\r\n```ruby\r\n    return if File.exist? gem_build_complete_path\r\n```\r\n\r\nSo why is `installed_by_version` set for `nokogiri` and not `json`? Because our fork of `nokogiri` has [`installed_by_version` set in its gemspec](https://github.com/Shopify/nokogiri/blob/master/nokogiri-1.6.6.2.gemspec#L26). It's the only example of a Gem with a git source that has this for Shopify:\r\n\r\n```ruby\r\ns.installed_by_version = \"2.2.2\" if s.respond_to? :installed_by_version\r\n```\r\n\r\n(I haven't dug deeper into is why the input says `2.2.2` when the output above says `2.2.3`)\r\n\r\nSo we now know why `nokogiri` is the only C-extension compiling: It doesn't return early on the version check. This starts to uncover the actual bug: `gem_build_complete_path` is not present for git-sourced gems and is not compiling by accident due to the version default.\r\n\r\nSure enough, running `find vendor/bundler -name \"*gem.build_complete\"` shows that `gem.build_complete` exists for all gems that are downloaded for `rubygems`. However, there are none for the gems with a Git source. \r\n\r\nWhen running the same `find(1)` command in development \"*gem.build_complete\" are clearly present in `vendor/bundler/bundler/extensions`. This directory doesn't exist at all in production. However, when `nokogiri` compiles it creates the `extensions/` directory with the `gem.build_complete` file in this directory as well as the shared object for the extension.\r\n\r\nWhat's different between development and production? I suspected our Docker image building process\r\nwould be the cause, since it does a lot of liberal cleanups to save space:\r\n\r\n```ruby\r\nprune_unwanted_files() {\r\n  set +x\r\n  # Sprockets cache; no value at runtime\r\n  rm -rf ${artifacts_path}/cache/assets\r\n  # Another cache; no value once all gems are installed\r\n  rm -rf ${artifacts_path}/ruby/*/cache\r\n  # git dirs of installed gems are useless once we no longer have to update them\r\n  rm -rf ${artifacts_path}/ruby/*/bundler/gems/*/.git\r\n  # final output is named rlibmemcached*; lib* are intermediate artifacts\r\n  rm -rf ${artifacts_path}/ruby/*/bundler/gems/memcached-*/ext/lib*\r\n  # Again, git dirs useless once we're done installing\r\n  rm -rf ${artifacts_path}/ruby/*/gems/*/.git\r\n  # Large source files, intermediate artifacts\r\n  rm -rf ${artifacts_path}/ruby/*/bundler/gems/nokogiri-*/{ports,ext/nokogiri/tmp}\r\n  # *.o, *.c, *.h, and *.a are intermediate object or source files not loadable\r\n  # at runtime.\r\n  find \"${artifacts_path}/ruby\" -name '*.{o,c,h,a}' -exec rm -f {} \\;\r\n  find \"${artifacts_path}/ruby\" -name 'gem_make.out' -exec rm -f {} \\;\r\n  set -x\r\n}\r\n```\r\n\r\nUnfortunately `bundler/extensions` is not there. Something else must be removing the `extensions/` directory and all its contents. I looked at our container build logs to try and see if anything suspicious was being run after `bundle install`:\r\n\r\n```\r\nOct  2 15:15:22.014:\tBundle complete! 213 Gemfile dependencies, 292 gems now installed.\r\nOct  2 15:15:22.015:\tGems in the groups development, test and debug were not installed.\r\nOct  2 15:15:22.015:\tBundled gems are installed into /artifacts.\r\nOct  2 15:15:22.079:\tRemoving  (extensions)\r\nOct  2 15:15:22.083:\tRemoving billing (7570b0472fbe)\r\n```\r\n\r\nThe second last line looked extremely suspicious. But where was it coming from? The removal goes back to the `--clean` flag to `bundler(1)` which ends up calling `Bundler::Runtime#clean`. This method is responsible for cleaning up unused gems. However, it doesn't consider the `extensions/` directory inside the directory that stores all the gems with a Git source:\r\n\r\n```ruby\r\nstale_git_dirs  = git_dirs - spec_git_paths\r\n```\r\n\r\n`extensions/` would be part of `git_dirs`, but not `spec_git_paths` and thus would end up being part of the resulting array for `stale_git_dirs`. \r\n\r\nIf the `extensions/` directory is left around, Rubygems would be able to pick up the `gem.build_complete` file and not compile the extension for the right reasons which resolves my bug.\r\n\r\nSome things to consider at the tail of this:\r\n\r\n* Why are versions not set correctly for Git sourced gems in Rubygems?\r\n* Why was the version set to `2.2.3` on `nokogiri` even though the spec says `2.2.2`?\r\n* Why is `irb` calling `Gem.try_activate` on every gem? Removing that would break backwards compatibility unfortunately.\r\n\r\n@ibawt @csfrancis @camilo @byroot @arthurnn @sgrif @EiNSTeiN- @burke\r\n\r\n@hone ","created_at":"2015-10-03T13:06:05Z","updated_at":"2015-10-03T19:22:00Z","closed_at":"2015-10-03T19:22:00Z","merged_at":"2015-10-03T19:22:00Z","merge_commit_sha":"cf8b31ddec8998c3ade630b38cb05316c3793680","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/bundler/bundler/pulls/4030/commits","review_comments_url":"https://api.github.com/repos/bundler/bundler/pulls/4030/comments","review_comment_url":"https://api.github.com/repos/bundler/bundler/pulls/comments{/number}","comments_url":"https://api.github.com/repos/bundler/bundler/issues/4030/comments","statuses_url":"https://api.github.com/repos/bundler/bundler/statuses/73fde334c5c6abb080fc561a42c3ff46b00944c3","head":{"label":"Sirupsen:dont-clean-extensions","ref":"dont-clean-extensions","sha":"73fde334c5c6abb080fc561a42c3ff46b00944c3","user":{"login":"Sirupsen","id":97400,"avatar_url":"https://avatars.githubusercontent.com/u/97400?v=3","gravatar_id":"","url":"https://api.github.com/users/Sirupsen","html_url":"https://github.com/Sirupsen","followers_url":"https://api.github.com/users/Sirupsen/followers","following_url":"https://api.github.com/users/Sirupsen/following{/other_user}","gists_url":"https://api.github.com/users/Sirupsen/gists{/gist_id}","starred_url":"https://api.github.com/users/Sirupsen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Sirupsen/subscriptions","organizations_url":"https://api.github.com/users/Sirupsen/orgs","repos_url":"https://api.github.com/users/Sirupsen/repos","events_url":"https://api.github.com/users/Sirupsen/events{/privacy}","received_events_url":"https://api.github.com/users/Sirupsen/received_events","type":"User","site_admin":false},"repo":{"id":43598178,"name":"bundler","full_name":"Sirupsen/bundler","owner":{"login":"Sirupsen","id":97400,"avatar_url":"https://avatars.githubusercontent.com/u/97400?v=3","gravatar_id":"","url":"https://api.github.com/users/Sirupsen","html_url":"https://github.com/Sirupsen","followers_url":"https://api.github.com/users/Sirupsen/followers","following_url":"https://api.github.com/users/Sirupsen/following{/other_user}","gists_url":"https://api.github.com/users/Sirupsen/gists{/gist_id}","starred_url":"https://api.github.com/users/Sirupsen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Sirupsen/subscriptions","organizations_url":"https://api.github.com/users/Sirupsen/orgs","repos_url":"https://api.github.com/users/Sirupsen/repos","events_url":"https://api.github.com/users/Sirupsen/events{/privacy}","received_events_url":"https://api.github.com/users/Sirupsen/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Sirupsen/bundler","description":"Manage your application's gem dependencies with less pain","fork":true,"url":"https://api.github.com/repos/Sirupsen/bundler","forks_url":"https://api.github.com/repos/Sirupsen/bundler/forks","keys_url":"https://api.github.com/repos/Sirupsen/bundler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Sirupsen/bundler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Sirupsen/bundler/teams","hooks_url":"https://api.github.com/repos/Sirupsen/bundler/hooks","issue_events_url":"https://api.github.com/repos/Sirupsen/bundler/issues/events{/number}","events_url":"https://api.github.com/repos/Sirupsen/bundler/events","assignees_url":"https://api.github.com/repos/Sirupsen/bundler/assignees{/user}","branches_url":"https://api.github.com/repos/Sirupsen/bundler/branches{/branch}","tags_url":"https://api.github.com/repos/Sirupsen/bundler/tags","blobs_url":"https://api.github.com/repos/Sirupsen/bundler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Sirupsen/bundler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Sirupsen/bundler/git/refs{/sha}","trees_url":"https://api.github.com/repos/Sirupsen/bundler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Sirupsen/bundler/statuses/{sha}","languages_url":"https://api.github.com/repos/Sirupsen/bundler/languages","stargazers_url":"https://api.github.com/repos/Sirupsen/bundler/stargazers","contributors_url":"https://api.github.com/repos/Sirupsen/bundler/contributors","subscribers_url":"https://api.github.com/repos/Sirupsen/bundler/subscribers","subscription_url":"https://api.github.com/repos/Sirupsen/bundler/subscription","commits_url":"https://api.github.com/repos/Sirupsen/bundler/commits{/sha}","git_commits_url":"https://api.github.com/repos/Sirupsen/bundler/git/commits{/sha}","comments_url":"https://api.github.com/repos/Sirupsen/bundler/comments{/number}","issue_comment_url":"https://api.github.com/repos/Sirupsen/bundler/issues/comments{/number}","contents_url":"https://api.github.com/repos/Sirupsen/bundler/contents/{+path}","compare_url":"https://api.github.com/repos/Sirupsen/bundler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Sirupsen/bundler/merges","archive_url":"https://api.github.com/repos/Sirupsen/bundler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Sirupsen/bundler/downloads","issues_url":"https://api.github.com/repos/Sirupsen/bundler/issues{/number}","pulls_url":"https://api.github.com/repos/Sirupsen/bundler/pulls{/number}","milestones_url":"https://api.github.com/repos/Sirupsen/bundler/milestones{/number}","notifications_url":"https://api.github.com/repos/Sirupsen/bundler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Sirupsen/bundler/labels{/name}","releases_url":"https://api.github.com/repos/Sirupsen/bundler/releases{/id}","created_at":"2015-10-03T12:20:40Z","updated_at":"2015-10-03T12:20:44Z","pushed_at":"2015-10-03T17:55:40Z","git_url":"git://github.com/Sirupsen/bundler.git","ssh_url":"git@github.com:Sirupsen/bundler.git","clone_url":"https://github.com/Sirupsen/bundler.git","svn_url":"https://github.com/Sirupsen/bundler","homepage":"http://bundler.io","size":35128,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":false,"has_downloads":false,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"bundler:master","ref":"master","sha":"84811218d9bdddec6b792da6b7cf4d44fa70d223","user":{"login":"bundler","id":1137638,"avatar_url":"https://avatars.githubusercontent.com/u/1137638?v=3","gravatar_id":"","url":"https://api.github.com/users/bundler","html_url":"https://github.com/bundler","followers_url":"https://api.github.com/users/bundler/followers","following_url":"https://api.github.com/users/bundler/following{/other_user}","gists_url":"https://api.github.com/users/bundler/gists{/gist_id}","starred_url":"https://api.github.com/users/bundler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bundler/subscriptions","organizations_url":"https://api.github.com/users/bundler/orgs","repos_url":"https://api.github.com/users/bundler/repos","events_url":"https://api.github.com/users/bundler/events{/privacy}","received_events_url":"https://api.github.com/users/bundler/received_events","type":"Organization","site_admin":false},"repo":{"id":488514,"name":"bundler","full_name":"bundler/bundler","owner":{"login":"bundler","id":1137638,"avatar_url":"https://avatars.githubusercontent.com/u/1137638?v=3","gravatar_id":"","url":"https://api.github.com/users/bundler","html_url":"https://github.com/bundler","followers_url":"https://api.github.com/users/bundler/followers","following_url":"https://api.github.com/users/bundler/following{/other_user}","gists_url":"https://api.github.com/users/bundler/gists{/gist_id}","starred_url":"https://api.github.com/users/bundler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bundler/subscriptions","organizations_url":"https://api.github.com/users/bundler/orgs","repos_url":"https://api.github.com/users/bundler/repos","events_url":"https://api.github.com/users/bundler/events{/privacy}","received_events_url":"https://api.github.com/users/bundler/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/bundler/bundler","description":"Manage your application's gem dependencies with less pain","fork":false,"url":"https://api.github.com/repos/bundler/bundler","forks_url":"https://api.github.com/repos/bundler/bundler/forks","keys_url":"https://api.github.com/repos/bundler/bundler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bundler/bundler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bundler/bundler/teams","hooks_url":"https://api.github.com/repos/bundler/bundler/hooks","issue_events_url":"https://api.github.com/repos/bundler/bundler/issues/events{/number}","events_url":"https://api.github.com/repos/bundler/bundler/events","assignees_url":"https://api.github.com/repos/bundler/bundler/assignees{/user}","branches_url":"https://api.github.com/repos/bundler/bundler/branches{/branch}","tags_url":"https://api.github.com/repos/bundler/bundler/tags","blobs_url":"https://api.github.com/repos/bundler/bundler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bundler/bundler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bundler/bundler/git/refs{/sha}","trees_url":"https://api.github.com/repos/bundler/bundler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bundler/bundler/statuses/{sha}","languages_url":"https://api.github.com/repos/bundler/bundler/languages","stargazers_url":"https://api.github.com/repos/bundler/bundler/stargazers","contributors_url":"https://api.github.com/repos/bundler/bundler/contributors","subscribers_url":"https://api.github.com/repos/bundler/bundler/subscribers","subscription_url":"https://api.github.com/repos/bundler/bundler/subscription","commits_url":"https://api.github.com/repos/bundler/bundler/commits{/sha}","git_commits_url":"https://api.github.com/repos/bundler/bundler/git/commits{/sha}","comments_url":"https://api.github.com/repos/bundler/bundler/comments{/number}","issue_comment_url":"https://api.github.com/repos/bundler/bundler/issues/comments{/number}","contents_url":"https://api.github.com/repos/bundler/bundler/contents/{+path}","compare_url":"https://api.github.com/repos/bundler/bundler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bundler/bundler/merges","archive_url":"https://api.github.com/repos/bundler/bundler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bundler/bundler/downloads","issues_url":"https://api.github.com/repos/bundler/bundler/issues{/number}","pulls_url":"https://api.github.com/repos/bundler/bundler/pulls{/number}","milestones_url":"https://api.github.com/repos/bundler/bundler/milestones{/number}","notifications_url":"https://api.github.com/repos/bundler/bundler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bundler/bundler/labels{/name}","releases_url":"https://api.github.com/repos/bundler/bundler/releases{/id}","created_at":"2010-01-26T00:46:38Z","updated_at":"2015-10-03T09:39:19Z","pushed_at":"2015-10-03T19:22:00Z","git_url":"git://github.com/bundler/bundler.git","ssh_url":"git@github.com:bundler/bundler.git","clone_url":"https://github.com/bundler/bundler.git","svn_url":"https://github.com/bundler/bundler","homepage":"http://bundler.io","size":35128,"stargazers_count":3049,"watchers_count":3049,"language":"Ruby","has_issues":true,"has_downloads":false,"has_wiki":false,"has_pages":true,"forks_count":1390,"mirror_url":null,"open_issues_count":95,"forks":1390,"open_issues":95,"watchers":3049,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/bundler/bundler/pulls/4030"},"html":{"href":"https://github.com/bundler/bundler/pull/4030"},"issue":{"href":"https://api.github.com/repos/bundler/bundler/issues/4030"},"comments":{"href":"https://api.github.com/repos/bundler/bundler/issues/4030/comments"},"review_comments":{"href":"https://api.github.com/repos/bundler/bundler/pulls/4030/comments"},"review_comment":{"href":"https://api.github.com/repos/bundler/bundler/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/bundler/bundler/pulls/4030/commits"},"statuses":{"href":"https://api.github.com/repos/bundler/bundler/statuses/73fde334c5c6abb080fc561a42c3ff46b00944c3"}},"merged":true,"mergeable":null,"mergeable_state":"unknown","merged_by":{"login":"andremedeiros","id":9689,"avatar_url":"https://avatars.githubusercontent.com/u/9689?v=3","gravatar_id":"","url":"https://api.github.com/users/andremedeiros","html_url":"https://github.com/andremedeiros","followers_url":"https://api.github.com/users/andremedeiros/followers","following_url":"https://api.github.com/users/andremedeiros/following{/other_user}","gists_url":"https://api.github.com/users/andremedeiros/gists{/gist_id}","starred_url":"https://api.github.com/users/andremedeiros/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andremedeiros/subscriptions","organizations_url":"https://api.github.com/users/andremedeiros/orgs","repos_url":"https://api.github.com/users/andremedeiros/repos","events_url":"https://api.github.com/users/andremedeiros/events{/privacy}","received_events_url":"https://api.github.com/users/andremedeiros/received_events","type":"User","site_admin":false},"comments":1,"review_comments":2,"commits":1,"additions":1,"deletions":1,"changed_files":1}}