mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
208 lines
3.4 KiB
Ruby
Executable file
208 lines
3.4 KiB
Ruby
Executable file
#!/usr/bin/env ruby --disable-gems
|
|
# Add the following line to your `.git/hooks/pre-commit`:
|
|
#
|
|
# $ ruby --disable-gems misc/expand_tabs.rb
|
|
#
|
|
|
|
require 'shellwords'
|
|
require 'tmpdir'
|
|
ENV['LC_ALL'] = 'C'
|
|
|
|
class Git
|
|
def initialize(oldrev, newrev)
|
|
@oldrev = oldrev
|
|
@newrev = newrev
|
|
end
|
|
|
|
# ["foo/bar.c", "baz.h", ...]
|
|
def updated_paths
|
|
with_clean_env do
|
|
IO.popen(['git', 'diff', '--cached', '--name-only', @newrev], &:readlines).each(&:chomp!)
|
|
end
|
|
end
|
|
|
|
# [0, 1, 4, ...]
|
|
def updated_lines(file)
|
|
lines = []
|
|
revs_pattern = ("0"*40) + " "
|
|
with_clean_env { IO.popen(['git', 'blame', '-l', '--', file], &:readlines) }.each_with_index do |line, index|
|
|
if line.b.start_with?(revs_pattern)
|
|
lines << index
|
|
end
|
|
end
|
|
lines
|
|
end
|
|
|
|
def add(file)
|
|
git('add', file)
|
|
end
|
|
|
|
def toplevel
|
|
IO.popen(['git', 'rev-parse', '--show-toplevel'], &:read).chomp
|
|
end
|
|
|
|
private
|
|
|
|
def git(*args)
|
|
cmd = ['git', *args].shelljoin
|
|
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[
|
|
abbrev
|
|
base64
|
|
benchmark
|
|
bundler
|
|
cmath
|
|
csv
|
|
debug
|
|
delegate
|
|
did_you_mean
|
|
drb
|
|
english
|
|
erb
|
|
fileutils
|
|
find
|
|
forwardable
|
|
getoptlong
|
|
ipaddr
|
|
irb
|
|
logger
|
|
mutex_m
|
|
net-http
|
|
net-protocol
|
|
observer
|
|
open3
|
|
open-uri
|
|
optparse
|
|
ostruct
|
|
pp
|
|
prettyprint
|
|
prime
|
|
pstore
|
|
rdoc
|
|
readline
|
|
reline
|
|
resolv
|
|
resolv-replace
|
|
rexml
|
|
rinda
|
|
rss
|
|
rubygems
|
|
scanf
|
|
securerandom
|
|
set
|
|
shellwords
|
|
singleton
|
|
tempfile
|
|
thwait
|
|
time
|
|
timeout
|
|
tmpdir
|
|
un
|
|
tsort
|
|
uri
|
|
weakref
|
|
yaml
|
|
]
|
|
|
|
DEFAULT_GEM_EXTS = %w[
|
|
bigdecimal
|
|
cgi
|
|
date
|
|
digest
|
|
etc
|
|
fcntl
|
|
fiddle
|
|
io-console
|
|
io-nonblock
|
|
io-wait
|
|
json
|
|
nkf
|
|
openssl
|
|
pathname
|
|
psych
|
|
racc
|
|
readline-ext
|
|
stringio
|
|
strscan
|
|
syslog
|
|
win32ole
|
|
zlib
|
|
]
|
|
|
|
EXPANDTAB_IGNORED_FILES = [
|
|
# default gems whose master is GitHub
|
|
%r{\Abin/(?!erb)\w+\z},
|
|
*DEFAULT_GEM_LIBS.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 (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},
|
|
]
|
|
|
|
git = Git.new('HEAD^', 'HEAD')
|
|
|
|
Dir.chdir(git.toplevel) do
|
|
paths = git.updated_paths
|
|
paths.select! {|f|
|
|
(f.end_with?('.c') || f.end_with?('.h') || f == 'insns.def') && EXPANDTAB_IGNORED_FILES.all? { |re| !f.match(re) }
|
|
}
|
|
files = paths.select {|n| File.file?(n)}
|
|
exit if files.empty?
|
|
|
|
files.each do |f|
|
|
src = File.binread(f) rescue next
|
|
|
|
expanded = false
|
|
updated_lines = git.updated_lines(f)
|
|
unless updated_lines.empty?
|
|
src.gsub!(/^.*$/).with_index do |line, lineno|
|
|
if updated_lines.include?(lineno) && line.start_with?("\t") # last-committed line with hard tabs
|
|
expanded = true
|
|
line.sub(/\A\t+/) { |tabs| ' ' * (8 * tabs.length) }
|
|
else
|
|
line
|
|
end
|
|
end
|
|
end
|
|
|
|
if expanded
|
|
File.binwrite(f, src)
|
|
git.add(f)
|
|
end
|
|
end
|
|
end
|