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
|
#!/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
|