-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathget-revisions.rb
executable file
·139 lines (119 loc) · 3.12 KB
/
get-revisions.rb
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env ruby
require 'open3'
require 'json'
NixpkgsPath = File.expand_path(ENV['NIXPKGS_PATH'] || (raise 'Please set NIXPKGS_PATH'))
# master as of 2021-10-25
StartRev = "8e18c70837aa01ade3718cd0fd35b649b3a2cf52"
Change = Struct.new(:version, :drv, :date, :rev, :depth, keyword_init: true)
def find_versions(versions_to_find: 10, start_depth: 0, log: Log.new)
@log = log
prev_change = Change.new(depth: start_depth - 1, rev: StartRev, drv: nil, version: nil)
versions_found = 0
while versions_found < versions_to_find
upper_bound = find_next_drv(prev_change)
change = find_drv_change(prev_change, upper_bound)
@log.add_change(change)
if change.version != prev_change.version
versions_found += 1
end
prev_change = change
end
end
# Find upper bound of next change after arg `change`
# by probing with exponential step size
def find_next_drv(change)
depth = 0
step = 1
while true
depth += step
version, drv = get_version(change, depth)
break if drv != change.drv
step *= 2
end
depth
end
# binary search for next change within arg `change` and
# the following `hi` (integer) commits
def find_drv_change(change, hi)
lo = 0
loop do
mid = (lo + hi + 1) / 2 # round up
version, drv = get_version(change, mid)
if mid == hi
return Change.new(version: version, drv: drv, date: get_date,
rev: get_rev, depth: change.depth + mid)
end
if drv == change.drv
lo = mid
else
hi = mid
end
end
end
# get version at depth relative to change
def get_version(change, depth)
@log.print_depth(change.depth, depth)
checkout(change.rev, depth)
maybe_stop
expr = <<~EOF
with (import "#{NixpkgsPath}" { config = {}; overlays = []; });
{
version = glibc.version;
drv = glibc.drvPath;
}
EOF
json = run('nix', 'eval', '--impure', '--json', '--expr', expr)
maybe_stop
data = JSON.load(json)
[data['version'], data['drv']]
end
def maybe_stop
exit if @abort
end
def get_date
git_nixpkgs('show', '-s', '--format=%ci')
end
def get_rev
git_nixpkgs('rev-parse', 'HEAD')
end
def checkout(rev, depth)
git_nixpkgs('checkout', "#{rev}~#{depth}")
end
def git_nixpkgs(*args)
run('git', '-C', NixpkgsPath, *args).strip
end
def run(*cmd)
stdout, stderr, status = Open3.capture3(*cmd)
if !status.success?
raise "Command '#{cmd}' failed with output:\n#{stderr}"
end
stdout
end
class Log
def initialize(dir: Dir.pwd)
@changes = Hash.new{|h,k| h[k] = [] }
@num_changes = 0
@path = File.join(dir, "changes-#{StartRev[0..6]}.json")
at_exit { write_log }
end
def write_log
File.write(@path, JSON.pretty_generate(@changes) + "\n")
end
def add_change(change)
puts "Found change #{change.to_h.except(:drv)}"
@changes[change.version] << change.to_h.except(:version)
@num_changes += 1
write_log if @num_changes % 5 == 0
end
def print_depth(base, extra)
puts "Checking depth #{base + extra} (Relative to last change: #{extra})"
end
end
puts
puts "Press ENTER to cleanly exit this program"
puts
Thread.new do
gets
@abort = true
end
find_versions(versions_to_find: 10)