mirror of
https://github.com/ruby/ruby.git
synced 2025-09-15 16:44:01 +02:00
Sync merger.rb and redmine-backporter.rb
This commit is contained in:
parent
a777087be6
commit
e96ba90fd6
2 changed files with 94 additions and 241 deletions
188
tool/merger.rb
188
tool/merger.rb
|
@ -2,9 +2,8 @@
|
||||||
# -*- ruby -*-
|
# -*- ruby -*-
|
||||||
exec "${RUBY-ruby}" "-x" "$0" "$@" && [ ] if false
|
exec "${RUBY-ruby}" "-x" "$0" "$@" && [ ] if false
|
||||||
#!ruby
|
#!ruby
|
||||||
# This needs ruby 2.0, Subversion and Git.
|
# This needs ruby 2.0 and Git.
|
||||||
# As a Ruby committer, run this in an SVN repository
|
# As a Ruby committer, run this in a git repository to commit a change.
|
||||||
# to commit a change.
|
|
||||||
|
|
||||||
require 'tempfile'
|
require 'tempfile'
|
||||||
require 'net/http'
|
require 'net/http'
|
||||||
|
@ -15,20 +14,11 @@ ENV['LC_ALL'] = 'C'
|
||||||
ORIGIN = 'git@git.ruby-lang.org:ruby.git'
|
ORIGIN = 'git@git.ruby-lang.org:ruby.git'
|
||||||
GITHUB = 'git@github.com:ruby/ruby.git'
|
GITHUB = 'git@github.com:ruby/ruby.git'
|
||||||
|
|
||||||
module Merger
|
class << Merger = Object.new
|
||||||
REPOS = 'svn+ssh://svn@ci.ruby-lang.org/ruby/'
|
|
||||||
end
|
|
||||||
|
|
||||||
class << Merger
|
|
||||||
include Merger
|
|
||||||
|
|
||||||
def help
|
def help
|
||||||
puts <<-HELP
|
puts <<-HELP
|
||||||
\e[1msimple backport\e[0m
|
\e[1msimple backport\e[0m
|
||||||
ruby #$0 1234
|
ruby #$0 1234abc
|
||||||
|
|
||||||
\e[1mbackport from other branch\e[0m
|
|
||||||
ruby #$0 17502 mvm
|
|
||||||
|
|
||||||
\e[1mrevision increment\e[0m
|
\e[1mrevision increment\e[0m
|
||||||
ruby #$0 revisionup
|
ruby #$0 revisionup
|
||||||
|
@ -37,16 +27,16 @@ class << Merger
|
||||||
ruby #$0 teenyup
|
ruby #$0 teenyup
|
||||||
|
|
||||||
\e[1mtagging major release\e[0m
|
\e[1mtagging major release\e[0m
|
||||||
ruby #$0 tag 2.2.0
|
ruby #$0 tag 3.2.0
|
||||||
|
|
||||||
\e[1mtagging patch release\e[0m (about 2.1.0 or later, it means X.Y.Z (Z > 0) release)
|
\e[1mtagging patch release\e[0m (for 2.1.0 or later, it means X.Y.Z (Z > 0) release)
|
||||||
ruby #$0 tag
|
ruby #$0 tag
|
||||||
|
|
||||||
\e[1mtagging preview/RC\e[0m
|
\e[1mtagging preview/RC\e[0m
|
||||||
ruby #$0 tag 2.2.0-preview1
|
ruby #$0 tag 3.2.0-preview1
|
||||||
|
|
||||||
\e[1mremove tag\e[0m
|
\e[1mremove tag\e[0m
|
||||||
ruby #$0 removetag 2.2.9
|
ruby #$0 removetag 3.2.9
|
||||||
|
|
||||||
\e[33;1m* all operations shall be applied to the working directory.\e[0m
|
\e[33;1m* all operations shall be applied to the working directory.\e[0m
|
||||||
HELP
|
HELP
|
||||||
|
@ -57,11 +47,11 @@ class << Merger
|
||||||
yield if block_given?
|
yield if block_given?
|
||||||
STDERR.puts "\e[1;33m#{str} ([y]es|[a]bort|[r]etry#{'|[e]dit' if editfile})\e[0m"
|
STDERR.puts "\e[1;33m#{str} ([y]es|[a]bort|[r]etry#{'|[e]dit' if editfile})\e[0m"
|
||||||
case STDIN.gets
|
case STDIN.gets
|
||||||
when /\Aa/i then exit
|
when /\Aa/i then exit 1
|
||||||
when /\Ar/i then redo
|
when /\Ar/i then redo
|
||||||
when /\Ay/i then break
|
when /\Ay/i then break
|
||||||
when /\Ae/i then system(ENV['EDITOR'], editfile)
|
when /\Ae/i then system(ENV['EDITOR'], editfile)
|
||||||
else exit
|
else exit 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -69,11 +59,7 @@ class << Merger
|
||||||
def version_up(teeny: false)
|
def version_up(teeny: false)
|
||||||
now = Time.now
|
now = Time.now
|
||||||
now = now.localtime(9*60*60) # server is Japan Standard Time +09:00
|
now = now.localtime(9*60*60) # server is Japan Standard Time +09:00
|
||||||
if svn_mode?
|
system('git', 'checkout', 'HEAD', 'version.h')
|
||||||
system('svn', 'revert', 'version.h')
|
|
||||||
else
|
|
||||||
system('git', 'checkout', 'HEAD', 'version.h')
|
|
||||||
end
|
|
||||||
v, pl = version
|
v, pl = version
|
||||||
|
|
||||||
if teeny
|
if teeny
|
||||||
|
@ -129,31 +115,10 @@ class << Merger
|
||||||
end
|
end
|
||||||
tagname = "v#{v.join('_')}#{("_#{pl}" if v[0] < "2" || (v[0] == "2" && v[1] < "1") || /^(?:preview|rc)/ =~ pl)}"
|
tagname = "v#{v.join('_')}#{("_#{pl}" if v[0] < "2" || (v[0] == "2" && v[1] < "1") || /^(?:preview|rc)/ =~ pl)}"
|
||||||
|
|
||||||
if svn_mode?
|
unless execute('git', 'tag', tagname)
|
||||||
if relname
|
abort 'specfied tag already exists. check tag name and remove it if you want to force re-tagging'
|
||||||
branch_url = `svn info`[/URL: (.*)/, 1]
|
|
||||||
else
|
|
||||||
branch_url = "#{REPOS}branches/ruby_"
|
|
||||||
if v[0] < '2' || (v[0] == '2' && v[1] < '1')
|
|
||||||
abort 'patchlevel must be greater than 0 for patch release' if pl == '0'
|
|
||||||
branch_url << v.join('_')
|
|
||||||
else
|
|
||||||
abort 'teeny must be greater than 0 for patch release' if v[2] == '0'
|
|
||||||
branch_url << v.join('_').sub(/_\d+\z/, '')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
tag_url = "#{REPOS}tags/#{tagname}"
|
|
||||||
system('svn', 'info', tag_url, out: IO::NULL, err: IO::NULL)
|
|
||||||
if $?.success?
|
|
||||||
abort 'specfied tag already exists. check tag name and remove it if you want to force re-tagging'
|
|
||||||
end
|
|
||||||
execute('svn', 'cp', '-m', "add tag #{tagname}", branch_url, tag_url, interactive: true)
|
|
||||||
else
|
|
||||||
unless execute('git', 'tag', tagname)
|
|
||||||
abort 'specfied tag already exists. check tag name and remove it if you want to force re-tagging'
|
|
||||||
end
|
|
||||||
execute('git', 'push', ORIGIN, tagname, interactive: true)
|
|
||||||
end
|
end
|
||||||
|
execute('git', 'push', ORIGIN, tagname, interactive: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_tag(relname)
|
def remove_tag(relname)
|
||||||
|
@ -173,64 +138,44 @@ class << Merger
|
||||||
tagname = relname
|
tagname = relname
|
||||||
end
|
end
|
||||||
|
|
||||||
if svn_mode?
|
execute('git', 'tag', '-d', tagname)
|
||||||
tag_url = "#{REPOS}tags/#{tagname}"
|
execute('git', 'push', ORIGIN, ":#{tagname}", interactive: true)
|
||||||
execute('svn', 'rm', '-m', "remove tag #{tagname}", tag_url, interactive: true)
|
execute('git', 'push', GITHUB, ":#{tagname}", interactive: true)
|
||||||
else
|
|
||||||
execute('git', 'tag', '-d', tagname)
|
|
||||||
execute('git', 'push', ORIGIN, ":#{tagname}", interactive: true)
|
|
||||||
execute('git', 'push', GITHUB, ":#{tagname}", interactive: true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_revision_h
|
def update_revision_h
|
||||||
if svn_mode?
|
|
||||||
execute('svn', 'up')
|
|
||||||
end
|
|
||||||
execute('ruby tool/file2lastrev.rb --revision.h . > revision.tmp')
|
execute('ruby tool/file2lastrev.rb --revision.h . > revision.tmp')
|
||||||
execute('tool/ifchange', '--timestamp=.revision.time', 'revision.h', 'revision.tmp')
|
execute('tool/ifchange', '--timestamp=.revision.time', 'revision.h', 'revision.tmp')
|
||||||
execute('rm', '-f', 'revision.tmp')
|
execute('rm', '-f', 'revision.tmp')
|
||||||
end
|
end
|
||||||
|
|
||||||
def stat
|
def stat
|
||||||
if svn_mode?
|
`git status --short`
|
||||||
`svn stat`
|
|
||||||
else
|
|
||||||
`git status --short`
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def diff(file = nil)
|
def diff(file = nil)
|
||||||
if svn_mode?
|
command = %w[git diff --color HEAD]
|
||||||
command = %w[svn diff --diff-cmd=diff -x -upw]
|
|
||||||
else
|
|
||||||
command = %w[git diff --color]
|
|
||||||
end
|
|
||||||
IO.popen(command + [file].compact, &:read)
|
IO.popen(command + [file].compact, &:read)
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit(file)
|
def commit(file)
|
||||||
if svn_mode?
|
current_branch = IO.popen(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], &:read).strip
|
||||||
begin
|
execute('git', 'add', '.') && execute('git', 'commit', '-F', file)
|
||||||
execute('svn', 'ci', '-F', file)
|
end
|
||||||
execute('svn', 'update') # svn ci doesn't update revision info on working copy
|
|
||||||
ensure
|
def has_conflicts?
|
||||||
execute('rm', '-f', 'subversion.commitlog')
|
changes = IO.popen(%w[git status --porcelain -z]) { |io| io.readlines("\0", chomp: true) }
|
||||||
end
|
# Discover unmerged files
|
||||||
else
|
# AU: unmerged, added by us
|
||||||
current_branch = IO.popen(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], &:read).strip
|
# DU: unmerged, deleted by us
|
||||||
execute('git', 'add', '.') &&
|
# UU: unmerged, both modified
|
||||||
execute('git', 'commit', '-F', file)
|
# AA: unmerged, both added
|
||||||
end
|
conflict = changes.grep(/\A(?:.U|AA) /) {$'}
|
||||||
|
!conflict.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def svn_mode?
|
|
||||||
return @svn_mode if defined?(@svn_mode)
|
|
||||||
@svn_mode = system("svn info", %i(out err) => IO::NULL)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Prints the version of Ruby found in version.h
|
# Prints the version of Ruby found in version.h
|
||||||
def version
|
def version
|
||||||
v = p = nil
|
v = p = nil
|
||||||
|
@ -289,21 +234,21 @@ else
|
||||||
|
|
||||||
case ARGV[0]
|
case ARGV[0]
|
||||||
when /--ticket=(.*)/
|
when /--ticket=(.*)/
|
||||||
tickets = $1.split(/,/).map{|num| " [Backport ##{num}]"}.join
|
tickets = $1.split(/,/)
|
||||||
ARGV.shift
|
ARGV.shift
|
||||||
else
|
else
|
||||||
tickets = ''
|
tickets = []
|
||||||
|
detect_ticket = true
|
||||||
end
|
end
|
||||||
|
|
||||||
revstr = ARGV[0].delete('^, :\-0-9a-fA-F')
|
revstr = ARGV[0].gsub(%r!https://github\.com/ruby/ruby/commit/|https://bugs\.ruby-lang\.org/projects/ruby-master/repository/git/revisions/!, '')
|
||||||
|
revstr = revstr.delete('^, :\-0-9a-fA-F')
|
||||||
revs = revstr.split(/[,\s]+/)
|
revs = revstr.split(/[,\s]+/)
|
||||||
commit_message = ''
|
commit_message = ''
|
||||||
|
|
||||||
revs.each do |rev|
|
revs.each do |rev|
|
||||||
git_rev = nil
|
git_rev = nil
|
||||||
case rev
|
case rev
|
||||||
when /\A\d{1,6}\z/
|
|
||||||
svn_rev = rev
|
|
||||||
when /\A\h{7,40}\z/
|
when /\A\h{7,40}\z/
|
||||||
git_rev = rev
|
git_rev = rev
|
||||||
when nil then
|
when nil then
|
||||||
|
@ -314,27 +259,24 @@ else
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
|
|
||||||
# Merge revision from Git patch or SVN
|
# Merge revision from Git patch
|
||||||
if git_rev
|
git_uri = "https://git.ruby-lang.org/ruby.git/patch/?id=#{git_rev}"
|
||||||
git_uri = "https://git.ruby-lang.org/ruby.git/patch/?id=#{git_rev}"
|
resp = Net::HTTP.get_response(URI(git_uri))
|
||||||
resp = Net::HTTP.get_response(URI(git_uri))
|
if resp.code != '200'
|
||||||
if resp.code != '200'
|
abort "'#{git_uri}' returned status '#{resp.code}':\n#{resp.body}"
|
||||||
abort "'#{git_uri}' returned status '#{resp.code}':\n#{resp.body}"
|
|
||||||
end
|
|
||||||
patch = resp.body.sub(/^diff --git a\/version\.h b\/version\.h\nindex .*\n--- a\/version\.h\n\+\+\+ b\/version\.h\n@@ .* @@\n(?:[-\+ ].*\n|\n)+/, '')
|
|
||||||
|
|
||||||
message = "\n\n#{(patch[/^Subject: (.*)\n\ndiff --git/m, 1] || "Message not found for revision: #{git_rev}\n")}"
|
|
||||||
puts '+ git apply'
|
|
||||||
IO.popen(['git', 'apply'], 'wb') { |f| f.write(patch) }
|
|
||||||
else
|
|
||||||
default_merge_branch = (%r{^URL: .*/branches/ruby_1_8_} =~ `svn info` ? 'branches/ruby_1_8' : 'trunk')
|
|
||||||
svn_src = "#{Merger::REPOS}#{ARGV[1] || default_merge_branch}"
|
|
||||||
message = IO.popen(['svn', 'log', '-c', svn_rev, svn_src], &:read)
|
|
||||||
|
|
||||||
cmd = ['svn', 'merge', '--accept=postpone', '-c', svn_rev, svn_src]
|
|
||||||
puts "+ #{cmd.join(' ')}"
|
|
||||||
system(*cmd)
|
|
||||||
end
|
end
|
||||||
|
patch = resp.body.sub(/^diff --git a\/version\.h b\/version\.h\nindex .*\n--- a\/version\.h\n\+\+\+ b\/version\.h\n@@ .* @@\n(?:[-\+ ].*\n|\n)+/, '')
|
||||||
|
|
||||||
|
if detect_ticket
|
||||||
|
tickets += patch.scan(/\[(?:Bug|Feature|Misc) #(\d+)\]/i).map(&:first)
|
||||||
|
end
|
||||||
|
|
||||||
|
message = "#{(patch[/^Subject: (.*)\n---\n /m, 1] || "Message not found for revision: #{git_rev}\n")}"
|
||||||
|
message.gsub!(/\G(.*)\n( .*)/, "\\1\\2")
|
||||||
|
message = "\n\n#{message}"
|
||||||
|
|
||||||
|
puts '+ git apply'
|
||||||
|
IO.popen(['git', 'apply', '--3way'], 'wb') { |f| f.write(patch) }
|
||||||
|
|
||||||
commit_message << message.sub(/\A-+\nr.*/, '').sub(/\n-+\n\z/, '').gsub(/^./, "\t\\&")
|
commit_message << message.sub(/\A-+\nr.*/, '').sub(/\n-+\n\z/, '').gsub(/^./, "\t\\&")
|
||||||
end
|
end
|
||||||
|
@ -345,20 +287,22 @@ else
|
||||||
|
|
||||||
Merger.version_up
|
Merger.version_up
|
||||||
f = Tempfile.new 'merger.rb'
|
f = Tempfile.new 'merger.rb'
|
||||||
f.printf "merge revision(s) %s:%s", revstr, tickets
|
f.printf "merge revision(s) %s:%s", revstr, tickets.map{|num| " [Backport ##{num}]"}.join
|
||||||
f.write commit_message
|
f.write commit_message
|
||||||
f.flush
|
f.flush
|
||||||
f.close
|
f.close
|
||||||
|
|
||||||
Merger.interactive('conflicts resolved?', f.path) do
|
if Merger.has_conflicts?
|
||||||
IO.popen(ENV['PAGER'] || ['less', '-R'], 'w') do |g|
|
Merger.interactive('conflicts resolved?', f.path) do
|
||||||
g << Merger.stat
|
IO.popen(ENV['PAGER'] || ['less', '-R'], 'w') do |g|
|
||||||
g << "\n\n"
|
g << Merger.stat
|
||||||
f.open
|
g << "\n\n"
|
||||||
g << f.read
|
f.open
|
||||||
f.close
|
g << f.read
|
||||||
g << "\n\n"
|
f.close
|
||||||
g << Merger.diff
|
g << "\n\n"
|
||||||
|
g << Merger.diff
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,7 @@ require 'optparse'
|
||||||
require 'abbrev'
|
require 'abbrev'
|
||||||
require 'pp'
|
require 'pp'
|
||||||
require 'shellwords'
|
require 'shellwords'
|
||||||
begin
|
require 'reline'
|
||||||
require 'readline'
|
|
||||||
rescue LoadError
|
|
||||||
module Readline; end
|
|
||||||
end
|
|
||||||
|
|
||||||
VERSION = '0.0.1'
|
|
||||||
|
|
||||||
opts = OptionParser.new
|
opts = OptionParser.new
|
||||||
target_version = nil
|
target_version = nil
|
||||||
|
@ -24,10 +18,9 @@ repo_path = nil
|
||||||
api_key = nil
|
api_key = nil
|
||||||
ssl_verify = true
|
ssl_verify = true
|
||||||
opts.on('-k REDMINE_API_KEY', '--key=REDMINE_API_KEY', 'specify your REDMINE_API_KEY') {|v| api_key = v}
|
opts.on('-k REDMINE_API_KEY', '--key=REDMINE_API_KEY', 'specify your REDMINE_API_KEY') {|v| api_key = v}
|
||||||
opts.on('-t TARGET_VERSION', '--target=TARGET_VARSION', /\A\d(?:\.\d)+\z/, 'specify target version (ex: 2.1)') {|v| target_version = v}
|
opts.on('-t TARGET_VERSION', '--target=TARGET_VARSION', /\A\d(?:\.\d)+\z/, 'specify target version (ex: 3.1)') {|v| target_version = v}
|
||||||
opts.on('-r RUBY_REPO_PATH', '--repository=RUBY_REPO_PATH', 'specify repository path') {|v| repo_path = v}
|
opts.on('-r RUBY_REPO_PATH', '--repository=RUBY_REPO_PATH', 'specify repository path') {|v| repo_path = v}
|
||||||
opts.on('--[no-]ssl-verify', TrueClass, 'use / not use SSL verify') {|v| ssl_verify = v}
|
opts.on('--[no-]ssl-verify', TrueClass, 'use / not use SSL verify') {|v| ssl_verify = v}
|
||||||
opts.version = VERSION
|
|
||||||
opts.parse!(ARGV)
|
opts.parse!(ARGV)
|
||||||
|
|
||||||
http_options = {use_ssl: true}
|
http_options = {use_ssl: true}
|
||||||
|
@ -35,17 +28,17 @@ http_options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
|
||||||
$openuri_options = {}
|
$openuri_options = {}
|
||||||
$openuri_options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
|
$openuri_options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
|
||||||
|
|
||||||
TARGET_VERSION = target_version || ENV['TARGET_VERSION'] || (raise 'need to specify TARGET_VERSION')
|
TARGET_VERSION = target_version || ENV['TARGET_VERSION'] || (puts opts.help; raise 'need to specify TARGET_VERSION')
|
||||||
RUBY_REPO_PATH = repo_path || ENV['RUBY_REPO_PATH']
|
RUBY_REPO_PATH = repo_path || ENV['RUBY_REPO_PATH']
|
||||||
BACKPORT_CF_KEY = 'cf_5'
|
BACKPORT_CF_KEY = 'cf_5'
|
||||||
STATUS_CLOSE = 5
|
STATUS_CLOSE = 5
|
||||||
REDMINE_API_KEY = api_key || ENV['REDMINE_API_KEY'] || (raise 'need to specify REDMINE_API_KEY')
|
REDMINE_API_KEY = api_key || ENV['REDMINE_API_KEY'] || (puts opts.help; raise 'need to specify REDMINE_API_KEY')
|
||||||
REDMINE_BASE = 'https://bugs.ruby-lang.org'
|
REDMINE_BASE = 'https://bugs.ruby-lang.org'
|
||||||
|
|
||||||
@query = {
|
@query = {
|
||||||
'f[]' => BACKPORT_CF_KEY,
|
'f[]' => BACKPORT_CF_KEY,
|
||||||
"op[#{BACKPORT_CF_KEY}]" => '~',
|
"op[#{BACKPORT_CF_KEY}]" => '~',
|
||||||
"v[#{BACKPORT_CF_KEY}][]" => "#{TARGET_VERSION}: REQUIRED",
|
"v[#{BACKPORT_CF_KEY}][]" => "\"#{TARGET_VERSION}: REQUIRED\"",
|
||||||
'limit' => 40,
|
'limit' => 40,
|
||||||
'status_id' => STATUS_CLOSE,
|
'status_id' => STATUS_CLOSE,
|
||||||
'sort' => 'updated_on'
|
'sort' => 'updated_on'
|
||||||
|
@ -70,28 +63,28 @@ COLORS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
class String
|
class String
|
||||||
def color(fore=nil, back=nil, bold: false, underscore: false)
|
def color(fore=nil, back=nil, opts={}, bold: false, underscore: false)
|
||||||
seq = ""
|
seq = ""
|
||||||
if bold
|
if bold || opts[:bold]
|
||||||
seq << "\e[1m"
|
seq = seq + "\e[1m"
|
||||||
end
|
end
|
||||||
if underscore
|
if underscore || opts[:underscore]
|
||||||
seq << "\e[2m"
|
seq = seq + "\e[2m"
|
||||||
end
|
end
|
||||||
if fore
|
if fore
|
||||||
c = COLORS[fore]
|
c = COLORS[fore]
|
||||||
raise "unknown foreground color #{fore}" unless c
|
raise "unknown foreground color #{fore}" unless c
|
||||||
seq << "\e[#{c}m"
|
seq = seq + "\e[#{c}m"
|
||||||
end
|
end
|
||||||
if back
|
if back
|
||||||
c = COLORS[back]
|
c = COLORS[back]
|
||||||
raise "unknown background color #{back}" unless c
|
raise "unknown background color #{back}" unless c
|
||||||
seq << "\e[#{c + 10}m"
|
seq = seq + "\e[#{c + 10}m"
|
||||||
end
|
end
|
||||||
if seq.empty?
|
if seq.empty?
|
||||||
self
|
self
|
||||||
else
|
else
|
||||||
seq << self << "\e[0m"
|
seq = seq + self + "\e[0m"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -162,84 +155,6 @@ def more(sio)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class << Readline
|
|
||||||
def readline(prompt = '')
|
|
||||||
console = IO.console
|
|
||||||
console.binmode
|
|
||||||
_, lx = console.winsize
|
|
||||||
if /mswin|mingw/ =~ RUBY_PLATFORM or /^(?:vt\d\d\d|xterm)/i =~ ENV["TERM"]
|
|
||||||
cls = "\r\e[2K"
|
|
||||||
else
|
|
||||||
cls = "\r" << (" " * lx)
|
|
||||||
end
|
|
||||||
cls << "\r" << prompt
|
|
||||||
console.print prompt
|
|
||||||
console.flush
|
|
||||||
line = ''
|
|
||||||
while true
|
|
||||||
case c = console.getch
|
|
||||||
when "\r", "\n"
|
|
||||||
puts
|
|
||||||
HISTORY << line
|
|
||||||
return line
|
|
||||||
when "\C-?", "\b" # DEL/BS
|
|
||||||
print "\b \b" if line.chop!
|
|
||||||
when "\C-u"
|
|
||||||
print cls
|
|
||||||
line.clear
|
|
||||||
when "\C-d"
|
|
||||||
return nil if line.empty?
|
|
||||||
line << c
|
|
||||||
when "\C-p"
|
|
||||||
HISTORY.pos -= 1
|
|
||||||
line = HISTORY.current
|
|
||||||
print cls
|
|
||||||
print line
|
|
||||||
when "\C-n"
|
|
||||||
HISTORY.pos += 1
|
|
||||||
line = HISTORY.current
|
|
||||||
print cls
|
|
||||||
print line
|
|
||||||
else
|
|
||||||
if c >= " "
|
|
||||||
print c
|
|
||||||
line << c
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
HISTORY = []
|
|
||||||
def HISTORY.<<(val)
|
|
||||||
HISTORY.push(val)
|
|
||||||
@pos = self.size
|
|
||||||
self
|
|
||||||
end
|
|
||||||
def HISTORY.pos
|
|
||||||
@pos ||= 0
|
|
||||||
end
|
|
||||||
def HISTORY.pos=(val)
|
|
||||||
@pos = val
|
|
||||||
if @pos < 0
|
|
||||||
@pos = -1
|
|
||||||
elsif @pos >= self.size
|
|
||||||
@pos = self.size
|
|
||||||
end
|
|
||||||
end
|
|
||||||
def HISTORY.current
|
|
||||||
@pos ||= 0
|
|
||||||
if @pos < 0 || @pos >= self.size
|
|
||||||
''
|
|
||||||
else
|
|
||||||
self[@pos]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end unless defined?(Readline.readline)
|
|
||||||
|
|
||||||
def find_svn_log(pattern)
|
|
||||||
`svn log --xml --stop-on-copy --search="#{pattern}" #{RUBY_REPO_PATH}`
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_git_log(pattern)
|
def find_git_log(pattern)
|
||||||
`git #{RUBY_REPO_PATH ? "-C #{RUBY_REPO_PATH.shellescape}" : ""} log --grep="#{pattern}"`
|
`git #{RUBY_REPO_PATH ? "-C #{RUBY_REPO_PATH.shellescape}" : ""} log --grep="#{pattern}"`
|
||||||
end
|
end
|
||||||
|
@ -276,10 +191,12 @@ def backport_command_string
|
||||||
|
|
||||||
# check if the Git revision is included in master
|
# check if the Git revision is included in master
|
||||||
has_commit(c, "master")
|
has_commit(c, "master")
|
||||||
|
end.sort_by do |changeset|
|
||||||
|
Integer(IO.popen(%W[git show -s --format=%ct #{changeset}], &:read))
|
||||||
end
|
end
|
||||||
@changesets.define_singleton_method(:validated){true}
|
@changesets.define_singleton_method(:validated){true}
|
||||||
end
|
end
|
||||||
" #{merger_path} --ticket=#{@issue} #{@changesets.sort.join(',')}"
|
"#{merger_path} --ticket=#{@issue} #{@changesets.join(',')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_char(obj)
|
def status_char(obj)
|
||||||
|
@ -294,7 +211,7 @@ end
|
||||||
console = IO.console
|
console = IO.console
|
||||||
row, = console.winsize
|
row, = console.winsize
|
||||||
@query['limit'] = row - 2
|
@query['limit'] = row - 2
|
||||||
puts "Backporter #{VERSION}".color(bold: true) + " for #{TARGET_VERSION}"
|
puts "Redmine Backporter".color(bold: true) + " for Ruby #{TARGET_VERSION}"
|
||||||
|
|
||||||
class CommandSyntaxError < RuntimeError; end
|
class CommandSyntaxError < RuntimeError; end
|
||||||
commands = {
|
commands = {
|
||||||
|
@ -306,10 +223,11 @@ commands = {
|
||||||
@issues = issues = res["issues"]
|
@issues = issues = res["issues"]
|
||||||
from = res["offset"] + 1
|
from = res["offset"] + 1
|
||||||
total = res["total_count"]
|
total = res["total_count"]
|
||||||
|
closed = issues.count { |x, _| x["status"]["name"] == "Closed" }
|
||||||
to = from + issues.size - 1
|
to = from + issues.size - 1
|
||||||
puts "#{from}-#{to} / #{total}"
|
puts "#{from}-#{to} / #{total} (closed: #{closed})"
|
||||||
issues.each_with_index do |x, i|
|
issues.each_with_index do |x, i|
|
||||||
id = "##{x["id"]}".color(*PRIORITIES[x["priority"]["name"]])
|
id = "##{x["id"]}".color(*PRIORITIES[x["priority"]["name"]], bold: x["status"]["name"] == "Closed")
|
||||||
puts "#{'%2d' % i} #{id} #{x["priority"]["name"][0]} #{status_char(x["status"])} #{x["subject"][0,80]}"
|
puts "#{'%2d' % i} #{id} #{x["priority"]["name"][0]} #{status_char(x["status"])} #{x["subject"][0,80]}"
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
@ -376,9 +294,6 @@ eom
|
||||||
"rel" => proc{|args|
|
"rel" => proc{|args|
|
||||||
# this feature requires custom redmine which allows add_related_issue API
|
# this feature requires custom redmine which allows add_related_issue API
|
||||||
case args
|
case args
|
||||||
when /\Ar?(\d+)\z/ # SVN
|
|
||||||
rev = $1
|
|
||||||
uri = URI("#{REDMINE_BASE}/projects/ruby-master/repository/trunk/revisions/#{rev}/issues.json")
|
|
||||||
when /\A\h{7,40}\z/ # Git
|
when /\A\h{7,40}\z/ # Git
|
||||||
rev = args
|
rev = args
|
||||||
uri = URI("#{REDMINE_BASE}/projects/ruby-master/repository/git/revisions/#{rev}/issues.json")
|
uri = URI("#{REDMINE_BASE}/projects/ruby-master/repository/git/revisions/#{rev}/issues.json")
|
||||||
|
@ -422,7 +337,7 @@ eom
|
||||||
},
|
},
|
||||||
|
|
||||||
"done" => proc{|args|
|
"done" => proc{|args|
|
||||||
raise CommandSyntaxError unless /\A(\d+)?(?: by (\h+))?(?:\s*-- +(.*))?\z/ =~ args
|
raise CommandSyntaxError unless /\A(\d+)?(?: *by (\h+))?(?:\s*-- +(.*))?\z/ =~ args
|
||||||
notes = $3
|
notes = $3
|
||||||
notes.strip! if notes
|
notes.strip! if notes
|
||||||
rev = $2
|
rev = $2
|
||||||
|
@ -436,23 +351,17 @@ eom
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
if rev
|
if rev.nil? && log = find_git_log("##@issue]")
|
||||||
elsif system("svn info #{RUBY_REPO_PATH&.shellescape}", %i(out err) => IO::NULL) # SVN
|
/^commit (?<rev>\h{40})$/ =~ log
|
||||||
if (log = find_svn_log("##@issue]")) && (/revision="(?<rev>\d+)/ =~ log)
|
|
||||||
rev = "r#{rev}"
|
|
||||||
end
|
|
||||||
else # Git
|
|
||||||
if log = find_git_log("##@issue]")
|
|
||||||
/^commit (?<rev>\h{40})$/ =~ log
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if log && rev
|
if log && rev
|
||||||
str = log[/merge revision\(s\) ([^:]+)(?=:)/]
|
str = log[/merge revision\(s\) ([^:]+)(?=:)/]
|
||||||
if str
|
if str
|
||||||
str.insert(5, "d")
|
str.sub!(/\Amerge/, 'merged')
|
||||||
str = "ruby_#{TARGET_VERSION.tr('.','_')} #{rev} #{str}."
|
str.gsub!(/\h{40}/, 'commit:\0')
|
||||||
|
str = "ruby_#{TARGET_VERSION.tr('.','_')} commit:#{rev} #{str}."
|
||||||
else
|
else
|
||||||
str = "ruby_#{TARGET_VERSION.tr('.','_')} #{rev}."
|
str = "ruby_#{TARGET_VERSION.tr('.','_')} commit:#{rev}."
|
||||||
end
|
end
|
||||||
if notes
|
if notes
|
||||||
str << "\n"
|
str << "\n"
|
||||||
|
@ -571,7 +480,7 @@ list = Abbrev.abbrev(commands.keys)
|
||||||
@changesets = nil
|
@changesets = nil
|
||||||
while true
|
while true
|
||||||
begin
|
begin
|
||||||
l = Readline.readline "#{('#' + @issue.to_s).color(bold: true) if @issue}> "
|
l = Reline.readline "#{('#' + @issue.to_s).color(bold: true) if @issue}> "
|
||||||
rescue Interrupt
|
rescue Interrupt
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue