forked from instructure/canvas-lms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbundle_update
executable file
·114 lines (87 loc) · 3.08 KB
/
bundle_update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env ruby
# frozen_string_literal: true
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler"
require "set"
require "yaml"
# parse the Gemfile, but don't actual set anything up
# this ensures "base_gems" is valid
Bundler.definition
base_gems = Gem.loaded_specs.keys.to_set
require "shellwords"
output = `git status --porcelain --untracked=no`.strip
unless output.empty?
warn "git status is not clean; please commit or stash your changes first"
exit 1
end
config = YAML.safe_load_file(File.expand_path("bundle_update_config.yml", __dir__))
ignored_gems = config["ignored_gems"].to_set
cohort_gems = config["cohort_gems"]
explicit_dependencies = Bundler.definition.dependencies.to_set(&:name)
reverse_dependencies = {}
# infer "cohort" gems from the lockfile automatically
Bundler.definition.specs.each do |spec|
spec.dependencies.each do |dep|
(reverse_dependencies[dep.name] ||= []) << spec.name
end
end
Bundler.definition.specs.each do |spec| # rubocop:disable Style/CombinableLoops
next if explicit_dependencies.include?(spec.name)
parent = spec.name
until explicit_dependencies.include?(parent)
parents = reverse_dependencies[parent]
if parents.length > 1
parent = nil
break
end
parent = parents.first
end
next unless parent
(cohort_gems[parent] ||= []) << spec.name
end
cohort_gems_inverse = {}
cohort_gems.each do |parent_gem, child_gems|
child_gems.each do |child_gem|
(cohort_gems_inverse[child_gem] ||= []) << parent_gem
end
end
output = Bundler.with_unbundled_env { `bundle outdated --parseable` }
output = output.split("\n").map(&:strip)
gems_to_update = Set.new
output.each do |line|
next if line.empty?
gem, details = line.split(" ", 2)
next if details.include?("requested = ") # exact requirements can't be updated
gems_to_update << gem
end
# only update "rails" if "activesupport" _and_ "rails" need updated
gems_to_update.reject! { |gem, _| (parent_gems = cohort_gems_inverse[gem]) && gems_to_update.intersect?(parent_gems) }
gems_to_update = gems_to_update.to_a
until gems_to_update.empty?
parent_gem = nil
gem = gems_to_update.shift
# if "aws-sdk-core" and "aws-sdk-s3" need updated, do them together
if (parent_gems = cohort_gems_inverse[gem])
parent_gem = parent_gems.min_by { |g| -(cohort_gems[g] & gems_to_update).length }
sibling_gems = cohort_gems[parent_gem] & gems_to_update
if sibling_gems.empty?
parent_gem = nil
else
gems_to_update -= sibling_gems
gem = ([gem] + sibling_gems).join(" ")
end
end
next if ignored_gems.include?(gem)
puts "Updating #{parent_gem || gem}..."
system("bundle update #{gem} --quiet")
unless $?.success?
system("git reset --hard HEAD > #{IO::NULL}")
next
end
output = `git status --porcelain --untracked=no`.strip
# couldn't update; should have warned anyway
next if output.empty?
message = "bundle update #{parent_gem || gem}"
message += "\n\n!! this commit needs hotfixed, since it is a base gem" if base_gems.include?(gem)
`git commit -am #{Shellwords.escape(message)} 2> #{IO::NULL}`
end