1 #!/usr/bin/env ruby --disable-gems
2 # Add the following line to your `.git/hooks/pre-commit`:
4 # $ ruby --disable-gems misc/expand_tabs.rb
12 def initialize(oldrev, newrev)
17 # ["foo/bar.c", "baz.h", ...]
20 IO.popen(['git', 'diff', '--cached', '--name-only', @newrev], &:readlines).each(&:chomp!)
25 def updated_lines(file)
27 revs_pattern = ("0"*40) + " "
28 with_clean_env { IO.popen(['git', 'blame', '-l', '--', file], &:readlines) }.each_with_index do |line, index|
29 if line.b.start_with?(revs_pattern)
41 IO.popen(['git', 'rev-parse', '--show-toplevel'], &:read).chomp
47 cmd = ['git', *args].shelljoin
48 unless with_clean_env { system(cmd) }
49 abort "Failed to run: #{cmd}"
54 git_dir = ENV.delete('GIT_DIR') # this overcomes '-C' or pwd
57 ENV['GIT_DIR'] = git_dir if git_dir
61 DEFAULT_GEM_LIBS = %w[
119 DEFAULT_GEM_EXTS = %w[
144 EXPANDTAB_IGNORED_FILES = [
145 # default gems whose master is GitHub
146 %r{\Abin/(?!erb)\w+\z},
147 *DEFAULT_GEM_LIBS.flat_map { |lib|
150 %r{\Alib/#{lib}\.gemspec\z},
151 %r{\Alib/#{lib}\.rb\z},
155 *DEFAULT_GEM_EXTS.flat_map { |ext|
167 %r{\Ainclude/ruby/onigmo\.h\z},
168 %r{\Areg.+\.(c|h)\z},
170 # explicit or implicit `c-file-style: "linux"`
171 %r{\Aaddr2line\.c\z},
174 %r{\Avsnprintf\.c\z},
177 git = Git.new('HEAD^', 'HEAD')
179 Dir.chdir(git.toplevel) do
180 paths = git.updated_paths
182 (f.end_with?('.c') || f.end_with?('.h') || f == 'insns.def') && EXPANDTAB_IGNORED_FILES.all? { |re| !f.match(re) }
184 files = paths.select {|n| File.file?(n)}
188 src = File.binread(f) rescue next
191 updated_lines = git.updated_lines(f)
192 unless updated_lines.empty?
193 src.gsub!(/^.*$/).with_index do |line, lineno|
194 if updated_lines.include?(lineno) && line.start_with?("\t") # last-committed line with hard tabs
196 line.sub(/\A\t+/) { |tabs| ' ' * (8 * tabs.length) }
204 File.binwrite(f, src)