1#!/bin/sh 2# -*- ruby -*- 3exec "${RUBY-ruby}" "-x" "$0" "$@" && [ ] if false 4#!ruby 5# This needs ruby 1.9 and subversion. 6# As a Ruby committer, run this in an SVN repository 7# to commit a change. 8 9require 'fileutils' 10require 'tempfile' 11 12$repos = 'svn+ssh://svn@ci.ruby-lang.org/ruby/' 13ENV['LC_ALL'] = 'C' 14 15def help 16 puts <<-end 17\e[1msimple backport\e[0m 18 ruby #$0 1234 19 20\e[1mrange backport\e[0m 21 ruby #$0 1234:5678 22 23\e[1mbackport from other branch\e[0m 24 ruby #$0 17502 mvm 25 26\e[1mrevision increment\e[0m 27 ruby #$0 revisionup 28 29\e[1mteeny increment\e[0m 30 ruby #$0 teenyup 31 32\e[1mtagging major release\e[0m 33 ruby #$0 tag 2.2.0 34 35\e[1mtagging patch release\e[0m (about 2.1.0 or later, it means X.Y.Z (Z > 0) release) 36 ruby #$0 tag 37 38\e[1mtagging preview/RC\e[0m 39 ruby #$0 tag 2.2.0-preview1 40 41\e[1mremove tag\e[0m 42 ruby #$0 removetag 2.2.9 43 44\e[33;1m* all operations shall be applied to the working directory.\e[0m 45end 46end 47 48# Prints the version of Ruby found in version.h 49 50def version 51 v = p = nil 52 open 'version.h', 'rb' do |f| 53 f.each_line do |l| 54 case l 55 when /^#define RUBY_VERSION "(\d+)\.(\d+)\.(\d+)"$/ 56 v = $~.captures 57 when /^#define RUBY_PATCHLEVEL (-?\d+)$/ 58 p = $1 59 end 60 end 61 end 62 return v, p 63end 64 65def interactive str, editfile = nil 66 loop do 67 yield 68 STDERR.puts "\e[1;33m#{str} ([y]es|[a]bort|[r]etry#{'|[e]dit' if editfile})\e[0m" 69 case STDIN.gets 70 when /\Aa/i then exit 71 when /\Ar/i then redo 72 when /\Ay/i then break 73 when /\Ae/i then system(ENV["EDITOR"], editfile) 74 else exit 75 end 76 end 77end 78 79def version_up(inc=nil) 80 d = Time.now 81 d = d.localtime(9*60*60) # server is Japan Standard Time +09:00 82 system(*%w'svn revert version.h') 83 v, pl = version 84 85 if inc == :teeny 86 v[2].succ! 87 end 88 # patchlevel 89 if pl != "-1" 90 pl.succ! 91 end 92 93 str = open 'version.h', 'rb' do |f| f.read end 94 [%W[RUBY_VERSION "#{v.join '.'}"], 95 %W[RUBY_VERSION_CODE #{v.join ''}], 96 %W[RUBY_VERSION_MAJOR #{v[0]}], 97 %W[RUBY_VERSION_MINOR #{v[1]}], 98 %W[RUBY_VERSION_TEENY #{v[2]}], 99 %W[RUBY_RELEASE_DATE "#{d.strftime '%Y-%m-%d'}"], 100 %W[RUBY_RELEASE_CODE #{d.strftime '%Y%m%d'}], 101 %W[RUBY_PATCHLEVEL #{pl}], 102 %W[RUBY_RELEASE_YEAR #{d.year}], 103 %W[RUBY_RELEASE_MONTH #{d.month}], 104 %W[RUBY_RELEASE_DAY #{d.day}], 105 ].each do |(k, i)| 106 str.sub!(/^(#define\s+#{k}\s+).*$/, "\\1#{i}") 107 end 108 str.sub!(/\s+\z/m, '') 109 fn = sprintf 'version.h.tmp.%032b', rand(1 << 31) 110 File.rename 'version.h', fn 111 open 'version.h', 'wb' do |f| 112 f.puts str 113 end 114 File.unlink fn 115end 116 117def tag intv_p = false, relname=nil 118 # relname: 119 # * 2.2.0-preview1 120 # * 2.2.0-rc1 121 # * 2.2.0 122 v, pl = version 123 x = v.join('_') 124 if relname 125 abort "patchlevel is not -1 but '#{pl}' for preview or rc" if pl != '-1' && /-(?:preview|rc)/ =~ relname 126 abort "patchlevel is not 0 but '#{pl}' for the first release" if pl != '0' && /-(?:preview|rc)/ !~ relname 127 pl = relname[/-(.*)\z/, 1] 128 curver = v.join('.') + (pl ? '-' + pl : '') 129 if relname != curver 130 abort "given relname '#{relname}' conflicts current version '#{curver}'" 131 end 132 branch_url = `svn info`[/URL: (.*)/, 1] 133 else 134 if pl == '-1' 135 abort "no relname is given and not in a release branch even if this is patch release" 136 end 137 branch_url = $repos + 'branches/ruby_' 138 if v[0] < "2" || (v[0] == "2" && v[1] < "1") 139 abort "patchlevel must be greater than 0 for patch release" if pl == "0" 140 branch_url << x 141 else 142 abort "teeny must be greater than 0 for patch release" if v[2] == "0" 143 branch_url << x.sub(/_\d+$/, '') 144 end 145 end 146 tagname = 'v' + x + (v[0] < "2" || (v[0] == "2" && v[1] < "1") || /^(?:preview|rc)/ =~ pl ? '_' + pl : '') 147 tag_url = $repos + 'tags/' + tagname 148 system(*%w'svn info', tag_url, out: IO::NULL, err: IO::NULL) 149 if $?.success? 150 abort "specfied tag already exists. check tag name and remove it if you want to force re-tagging" 151 end 152 if intv_p 153 interactive "OK? svn cp -m \"add tag #{tagname}\" #{branch_url} #{tag_url}" do 154 # nothing to do here 155 end 156 end 157 system(*%w'svn cp -m', "add tag #{tagname}", branch_url, tag_url) 158end 159 160def remove_tag intv_p = false, relname 161 # relname: 162 # * 2.2.0-preview1 163 # * 2.2.0-rc1 164 # * 2.2.0 165 # * v2_2_0_preview1 166 # * v2_2_0_rc1 167 # * v2_2_0 168 if !relname && !intv_p.is_a?(String) 169 raise ArgumentError, "relname is not specified" 170 end 171 intv_p, relname = false, intv_p if !relname && intv_p.is_a?(String) 172 173 if /^v/ !~ relname 174 tagname = 'v' + relname.tr(".-", "_") 175 else 176 tagname = relname 177 end 178 tag_url = $repos + 'tags/' + tagname 179 if intv_p 180 interactive "OK? svn rm -m \"remove tag #{tagname}\" #{tag_url}" do 181 # nothing to do here 182 end 183 end 184 system(*%w'svn rm -m', "remove tag #{tagname}", tag_url) 185end 186 187def default_merge_branch 188 %r{^URL: .*/branches/ruby_1_8_} =~ `svn info` ? 'branches/ruby_1_8' : 'trunk' 189end 190 191case ARGV[0] 192when "teenyup" 193 version_up(:teeny) 194 system 'svn diff version.h' 195when "up", /\A(ver|version|rev|revision|lv|level|patch\s*level)\s*up/ 196 version_up 197 system 'svn diff version.h' 198when "tag" 199 tag :interactive, ARGV[1] 200when /\A(?:remove|rm|del)_?tag\z/ 201 remove_tag :interactive, ARGV[1] 202when nil, "-h", "--help" 203 help 204 exit 205else 206 system 'svn up' 207 system 'ruby tool/file2lastrev.rb --revision.h . > revision.tmp' 208 system 'tool/ifchange "--timestamp=.revision.time" "revision.h" "revision.tmp"' 209 FileUtils.rm_f('revision.tmp') 210 211 case ARGV[0] 212 when /--ticket=(.*)/ 213 tickets = $1.split(/,/).map{|num| " [Backport ##{num}]"}.join 214 ARGV.shift 215 when /merge revision\(s\) ([\d,\-]+):( \[.*)/ 216 tickets = $2 217 ARGV[0] = $1 218 else 219 tickets = '' 220 end 221 222 q = $repos + (ARGV[1] || default_merge_branch) 223 revstr = ARGV[0].delete('^, :\-0-9') 224 revs = revstr.split(/[,\s]+/) 225 log = '' 226 log_svn = '' 227 228 revs.each do |rev| 229 case rev 230 when /\A\d+:\d+\z/ 231 r = ['-r', rev] 232 when /\A(\d+)-(\d+)\z/ 233 rev = "#{$1.to_i-1}:#$2" 234 r = ['-r', rev] 235 when /\A\d+\z/ 236 r = ['-c', rev] 237 when nil then 238 puts "#$0 revision" 239 exit 240 else 241 puts "invalid revision part '#{rev}' in '#{ARGV[0]}'" 242 exit 243 end 244 245 l = IO.popen %w'svn diff' + r + %w'--diff-cmd=diff -x -pU0' + [File.join(q, 'ChangeLog')] do |f| 246 f.read 247 end 248 249 log << l 250 l = l.lines.grep(/^\+\t/).join.gsub(/^\+/, '').gsub(/^\t\*/, "\n\t\*") 251 252 if l.empty? 253 l = IO.popen %w'svn log ' + r + [q] do |f| 254 f.read 255 end.sub(/\A-+\nr.*/, '').sub(/\n-+\n\z/, '').gsub(/^./, "\t\\&") 256 end 257 log_svn << l 258 259 a = %w'svn merge --accept=postpone' + r + [q] 260 STDERR.puts a.join(' ') 261 262 system(*a) 263 system(*%w'svn revert ChangeLog') if /^\+/ =~ l 264 end 265 266 if `svn diff --diff-cmd=diff -x -upw`.empty? 267 interactive 'Only ChangeLog is modified, right?' do 268 end 269 end 270 271 if /^\+/ =~ log 272 system(*%w'svn revert ChangeLog') 273 IO.popen %w'patch -p0', 'wb' do |f| 274 f.write log.gsub(/\+(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ 123][0-9] [012][0-9]:[0-5][0-9]:[0-5][0-9] \d\d\d\d/, 275 # this format-time-string was from the file local variables of ChangeLog 276 '+'+Time.now.strftime('%a %b %e %H:%M:%S %Y')) 277 end 278 system(*%w'touch ChangeLog') # needed somehow, don't know why... 279 else 280 STDERR.puts '*** You should write ChangeLog NOW!!! ***' 281 end 282 283 version_up 284 f = Tempfile.new 'merger.rb' 285 f.printf "merge revision(s) %s:%s", revstr, tickets 286 f.write log_svn 287 f.flush 288 f.close 289 290 interactive 'conflicts resolved?', f.path do 291 IO.popen(ENV["PAGER"] || "less", "w") do |g| 292 g << `svn stat` 293 g << "\n\n" 294 f.open 295 g << f.read 296 f.close 297 g << "\n\n" 298 g << `svn diff --diff-cmd=diff -x -upw` 299 end 300 end 301 302 if system(*%w'svn ci -F', f.path) 303 # tag :interactive # no longer needed. 304 system 'rm -f subversion.commitlog' 305 else 306 puts 'commit failed; try again.' 307 end 308 309 f.close(true) 310end 311