[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / misc / expand_tabs.rb
bloba94eea5046f1231254873f44539a77e2d5197f6e
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
7 require 'shellwords'
8 require 'tmpdir'
9 ENV['LC_ALL'] = 'C'
11 class Git
12   def initialize(oldrev, newrev)
13     @oldrev = oldrev
14     @newrev = newrev
15   end
17   # ["foo/bar.c", "baz.h", ...]
18   def updated_paths
19     with_clean_env do
20       IO.popen(['git', 'diff', '--cached', '--name-only', @newrev], &:readlines).each(&:chomp!)
21     end
22   end
24   # [0, 1, 4, ...]
25   def updated_lines(file)
26     lines = []
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)
30         lines << index
31       end
32     end
33     lines
34   end
36   def add(file)
37     git('add', file)
38   end
40   def toplevel
41     IO.popen(['git', 'rev-parse', '--show-toplevel'], &:read).chomp
42   end
44   private
46   def git(*args)
47     cmd = ['git', *args].shelljoin
48     unless with_clean_env { system(cmd) }
49       abort "Failed to run: #{cmd}"
50     end
51   end
53   def with_clean_env
54     git_dir = ENV.delete('GIT_DIR') # this overcomes '-C' or pwd
55     yield
56   ensure
57     ENV['GIT_DIR'] = git_dir if git_dir
58   end
59 end
61 DEFAULT_GEM_LIBS = %w[
62   abbrev
63   base64
64   benchmark
65   bundler
66   cmath
67   csv
68   debug
69   delegate
70   did_you_mean
71   drb
72   english
73   erb
74   fileutils
75   find
76   forwardable
77   getoptlong
78   ipaddr
79   irb
80   logger
81   mutex_m
82   net-http
83   net-protocol
84   observer
85   open3
86   open-uri
87   optparse
88   ostruct
89   pp
90   prettyprint
91   prime
92   pstore
93   rdoc
94   readline
95   reline
96   resolv
97   resolv-replace
98   rexml
99   rinda
100   rss
101   rubygems
102   scanf
103   securerandom
104   set
105   shellwords
106   singleton
107   tempfile
108   thwait
109   time
110   timeout
111   tmpdir
112   un
113   tsort
114   uri
115   weakref
116   yaml
119 DEFAULT_GEM_EXTS = %w[
120   bigdecimal
121   cgi
122   date
123   digest
124   etc
125   fcntl
126   fiddle
127   io-console
128   io-nonblock
129   io-wait
130   json
131   nkf
132   openssl
133   pathname
134   psych
135   racc
136   readline-ext
137   stringio
138   strscan
139   syslog
140   win32ole
141   zlib
144 EXPANDTAB_IGNORED_FILES = [
145   # default gems whose master is GitHub
146   %r{\Abin/(?!erb)\w+\z},
147   *DEFAULT_GEM_LIBS.flat_map { |lib|
148     [
149       %r{\Alib/#{lib}/},
150       %r{\Alib/#{lib}\.gemspec\z},
151       %r{\Alib/#{lib}\.rb\z},
152       %r{\Atest/#{lib}/},
153     ]
154   },
155   *DEFAULT_GEM_EXTS.flat_map { |ext|
156     [
157       %r{\Aext/#{ext}/},
158       %r{\Atest/#{ext}/},
159     ]
160   },
162   # vendoring (ccan)
163   %r{\Accan/},
165   # vendoring (onigmo)
166   %r{\Aenc/},
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},
172   %r{\Amissing/},
173   %r{\Astrftime\.c\z},
174   %r{\Avsnprintf\.c\z},
177 git = Git.new('HEAD^', 'HEAD')
179 Dir.chdir(git.toplevel) do
180   paths = git.updated_paths
181   paths.select! {|f|
182     (f.end_with?('.c') || f.end_with?('.h') || f == 'insns.def') && EXPANDTAB_IGNORED_FILES.all? { |re| !f.match(re) }
183   }
184   files = paths.select {|n| File.file?(n)}
185   exit if files.empty?
187   files.each do |f|
188     src = File.binread(f) rescue next
190     expanded = false
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
195           expanded = true
196           line.sub(/\A\t+/) { |tabs| ' ' * (8 * tabs.length) }
197         else
198           line
199         end
200       end
201     end
203     if expanded
204       File.binwrite(f, src)
205       git.add(f)
206     end
207   end