mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			168 lines
		
	
	
	
		
			3 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
	
		
			3 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[
 | 
						|
  bundler
 | 
						|
  cmath
 | 
						|
  csv
 | 
						|
  e2mmap
 | 
						|
  fileutils
 | 
						|
  forwardable
 | 
						|
  ipaddr
 | 
						|
  irb
 | 
						|
  logger
 | 
						|
  matrix
 | 
						|
  mutex_m
 | 
						|
  ostruct
 | 
						|
  prime
 | 
						|
  racc
 | 
						|
  rdoc
 | 
						|
  rexml
 | 
						|
  rss
 | 
						|
  scanf
 | 
						|
  shell
 | 
						|
  sync
 | 
						|
  thwait
 | 
						|
  tracer
 | 
						|
  webrick
 | 
						|
]
 | 
						|
 | 
						|
DEFAULT_GEM_EXTS = %w[
 | 
						|
  bigdecimal
 | 
						|
  date
 | 
						|
  dbm
 | 
						|
  etc
 | 
						|
  fcntl
 | 
						|
  fiddle
 | 
						|
  gdbm
 | 
						|
  io/console
 | 
						|
  json
 | 
						|
  openssl
 | 
						|
  psych
 | 
						|
  stringio
 | 
						|
  strscan
 | 
						|
  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
 |