forked from ruby/ruby
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathauto-style.rb
251 lines (220 loc) · 5.36 KB
/
auto-style.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#!/usr/bin/env ruby
# Usage:
# auto-style.rb [oldrev] [newrev] [pushref]
require 'shellwords'
require 'tmpdir'
ENV['LC_ALL'] = 'C'
class Git
attr_reader :depth
def initialize(oldrev, newrev, branch = nil)
@oldrev = oldrev
@newrev = newrev
@branch = branch
# GitHub may not fetch github.event.pull_request.base.sha at checkout
git('fetch', '--depth=1', 'origin', @oldrev)
git('fetch', '--depth=100', 'origin', @newrev)
with_clean_env do
@revs = {}
IO.popen(['git', 'log', '--format=%H %s', "#{@oldrev}..#{@newrev}"]) do |f|
f.each do |line|
line.chomp!
rev, subj = line.split(' ', 2)
@revs[rev] = subj
end
end
@depth = @revs.size
end
end
# ["foo/bar.c", "baz.h", ...]
def updated_paths
with_clean_env do
IO.popen(['git', 'diff', '--name-only', @oldrev, @newrev], &:readlines).each(&:chomp!)
end
end
# [0, 1, 4, ...]
def updated_lines(file) # NOTE: This doesn't work well on pull requests, so not used anymore
lines = []
revs = @revs.map {|rev, subj| rev unless subj.start_with?("Revert ")}.compact
revs_pattern = /\A(?:#{revs.join('|')}) /
with_clean_env { IO.popen(['git', 'blame', '-l', '--', file], &:readlines) }.each_with_index do |line, index|
if revs_pattern =~ line
lines << index
end
end
lines
end
def commit(log, *files)
git('add', *files)
git('commit', '-m', log)
end
def push
git('push', 'origin', @branch)
end
def diff
git('--no-pager', 'diff')
end
private
def git(*args)
cmd = ['git', *args].shelljoin
puts "+ #{cmd}"
unless with_clean_env { system(cmd) }
abort "Failed to run: #{cmd}"
end
end
def with_clean_env
git_dir = ENV.delete('GIT_DIR') # this overcomes '-C' or pwd
yield
ensure
ENV['GIT_DIR'] = git_dir if git_dir
end
end
DEFAULT_GEM_LIBS = %w[
bundler
cmath
csv
e2mmap
fileutils
forwardable
ipaddr
irb
logger
matrix
mutex_m
ostruct
prime
rdoc
rexml
rss
scanf
shell
sync
thwait
tracer
webrick
]
DEFAULT_GEM_EXTS = %w[
bigdecimal
date
dbm
digest
etc
fcntl
fiddle
gdbm
io/console
io/nonblock
json
openssl
psych
racc
sdbm
stringio
strscan
zlib
]
IGNORED_FILES = [
# default gems whose master is GitHub
%r{\Abin/(?!erb)\w+\z},
*(DEFAULT_GEM_LIBS + DEFAULT_GEM_EXTS).flat_map { |lib|
[
%r{\Alib/#{lib}/},
%r{\Alib/#{lib}\.gemspec\z},
%r{\Alib/#{lib}\.rb\z},
%r{\Atest/#{lib}/},
]
},
*DEFAULT_GEM_EXTS.flat_map { |ext|
[
%r{\Aext/#{ext}/},
%r{\Atest/#{ext}/},
]
},
# vendoring (ccan)
%r{\Accan/},
# vendoring (io/)
%r{\Aext/io/},
# vendoring (nkf)
%r{\Aext/nkf/nkf-utf8/},
# vendoring (onigmo)
%r{\Aenc/},
%r{\Ainclude/ruby/onigmo\.h\z},
%r{\Areg.+\.(c|h)\z},
# explicit or implicit `c-file-style: "linux"`
%r{\Aaddr2line\.c\z},
%r{\Amissing/},
%r{\Astrftime\.c\z},
%r{\Avsnprintf\.c\z},
# to respect the original statements of licenses
%r{\ALEGAL\z},
# trailing spaces could be intentional in TRICK code
%r{\Asample/trick[^/]*/},
]
oldrev, newrev, pushref = ARGV
unless dry_run = pushref.empty?
branch = IO.popen(['git', 'rev-parse', '--symbolic', '--abbrev-ref', pushref], &:read).strip
end
git = Git.new(oldrev, newrev, branch)
updated_files = git.updated_paths
files = updated_files.select {|l|
/^\d/ !~ l and /\.bat\z/ !~ l and
(/\A(?:config|[Mm]akefile|GNUmakefile|README)/ =~ File.basename(l) or
/\A\z|\.(?:[chsy]|\d+|e?rb|tmpl|bas[eh]|z?sh|in|ma?k|def|src|trans|rdoc|ja|en|el|sed|awk|p[ly]|scm|mspec|html|)\z/ =~ File.extname(l))
}
files.select! {|n| File.file?(n) }
files.reject! do |f|
IGNORED_FILES.any? { |re| f.match(re) }
end
if files.empty?
puts "No files are an auto-style target:\n#{updated_files.join("\n")}"
exit
end
trailing = eofnewline = expandtab = false
edited_files = files.select do |f|
src = File.binread(f) rescue next
eofnewline = eofnewline0 = true if src.sub!(/(?<!\A|\n)\z/, "\n")
trailing0 = false
expandtab0 = false
src.gsub!(/^.*$/).with_index do |line, lineno|
trailing = trailing0 = true if line.sub!(/[ \t]+$/, '')
line
end
if f.end_with?('.c') || f.end_with?('.h') || f == 'insns.def'
# If and only if unedited lines did not have tab indentation, prevent introducing tab indentation to the file.
expandtab_allowed = src.each_line.with_index.all? do |line, lineno|
!line.start_with?("\t")
end
if expandtab_allowed
src.gsub!(/^.*$/).with_index do |line, lineno|
if line.start_with?("\t") # last-committed line with hard tabs
expandtab = expandtab0 = true
line.sub(/\A\t+/) { |tabs| ' ' * (8 * tabs.length) }
else
line
end
end
end
end
if trailing0 or eofnewline0 or expandtab0
File.binwrite(f, src)
true
end
end
if edited_files.empty?
puts "All edited lines are formatted well:\n#{files.join("\n")}"
else
msg = [('remove trailing spaces' if trailing),
('append newline at EOF' if eofnewline),
('expand tabs' if expandtab),
].compact
message = "* #{msg.join(', ')}. [ci skip]"
if expandtab
message += "\nPlease consider using misc/expand_tabs.rb as a pre-commit hook."
end
if dry_run
git.diff
abort message
else
git.commit(message, *edited_files)
git.push
end
end