summaryrefslogtreecommitdiff
path: root/tool/lib/vcs.rb
diff options
context:
space:
mode:
Diffstat (limited to 'tool/lib/vcs.rb')
-rw-r--r--tool/lib/vcs.rb231
1 files changed, 194 insertions, 37 deletions
diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb
index 5d78d94c26..3894f9c8e8 100644
--- a/tool/lib/vcs.rb
+++ b/tool/lib/vcs.rb
@@ -1,6 +1,8 @@
# vcs
require 'fileutils'
require 'optparse'
+require 'pp'
+require 'tempfile'
# This library is used by several other tools/ scripts to detect the current
# VCS in use (e.g. SVN, Git) or to interact with that VCS.
@@ -9,6 +11,22 @@ ENV.delete('PWD')
class VCS
DEBUG_OUT = STDERR.dup
+
+ def self.dump(obj, pre = nil)
+ out = DEBUG_OUT
+ @pp ||= PP.new(out)
+ @pp.guard_inspect_key do
+ if pre
+ @pp.group(pre.size, pre) {
+ obj.pretty_print(@pp)
+ }
+ else
+ obj.pretty_print(@pp)
+ end
+ @pp.flush
+ out << "\n"
+ end
+ end
end
unless File.respond_to? :realpath
@@ -19,14 +37,14 @@ unless File.respond_to? :realpath
end
def IO.pread(*args)
- VCS::DEBUG_OUT.puts(args.inspect) if $DEBUG
+ VCS.dump(args, "args: ") if $DEBUG
popen(*args) {|f|f.read}
end
module DebugPOpen
refine IO.singleton_class do
def popen(*args)
- VCS::DEBUG_OUT.puts args.inspect if $DEBUG
+ VCS.dump(args, "args: ") if $DEBUG
super
end
end
@@ -34,7 +52,7 @@ end
using DebugPOpen
module DebugSystem
def system(*args)
- VCS::DEBUG_OUT.puts args.inspect if $DEBUG
+ VCS.dump(args, "args: ") if $DEBUG
exception = false
opts = Hash.try_convert(args[-1])
if RUBY_VERSION >= "2.6"
@@ -69,6 +87,9 @@ class VCS
begin
@@dirs.each do |dir, klass, pred|
if pred ? pred[curr, dir] : File.directory?(File.join(curr, dir))
+ if klass.const_defined?(:COMMAND)
+ IO.pread([{'LANG' => 'C', 'LC_ALL' => 'C'}, klass::COMMAND, "--version"]) rescue next
+ end
vcs = klass.new(curr)
vcs.define_options(parser) if parser
vcs.set_options(options)
@@ -92,9 +113,23 @@ class VCS
parser.separator(" VCS common options:")
parser.define("--[no-]dryrun") {|v| opts[:dryrun] = v}
parser.define("--[no-]debug") {|v| opts[:debug] = v}
+ parser.define("-z", "--zone=OFFSET", /\A[-+]\d\d:\d\d\z/) {|v| opts[:zone] = v}
opts
end
+ def release_date(time)
+ t = time.getlocal(@zone)
+ [
+ t.strftime('#define RUBY_RELEASE_YEAR %Y'),
+ t.strftime('#define RUBY_RELEASE_MONTH %-m'),
+ t.strftime('#define RUBY_RELEASE_DAY %-d'),
+ ]
+ end
+
+ def self.short_revision(rev)
+ rev
+ end
+
attr_reader :srcdir
def initialize(path)
@@ -112,14 +147,14 @@ class VCS
def set_options(opts)
@debug = opts.fetch(:debug) {$DEBUG}
@dryrun = opts.fetch(:dryrun) {@debug}
+ @zone = opts.fetch(:zone) {'+09:00'}
end
attr_reader :dryrun, :debug
alias dryrun? dryrun
alias debug? debug
- NullDevice = defined?(IO::NULL) ? IO::NULL :
- %w[/dev/null NUL NIL: NL:].find {|dev| File.exist?(dev)}
+ NullDevice = IO::NULL
# returns
# * the last revision of the current branch
@@ -132,7 +167,7 @@ class VCS
end
last, changed, modified, *rest = (
begin
- if NullDevice
+ if NullDevice and !debug?
save_stderr = STDERR.dup
STDERR.reopen NullDevice, 'w'
end
@@ -159,6 +194,7 @@ class VCS
rescue ArgumentError
modified = Time.utc(*$~[1..6]) + $7.to_i * 3600 + $8.to_i * 60
end
+ modified = modified.getlocal(@zone)
end
return last, changed, modified, *rest
end
@@ -190,6 +226,7 @@ class VCS
def after_export(dir)
FileUtils.rm_rf(Dir.glob("#{dir}/.git*"))
+ FileUtils.rm_rf(Dir.glob("#{dir}/.mailmap"))
end
def revision_handler(rev)
@@ -204,6 +241,36 @@ class VCS
revision_handler(rev).short_revision(rev)
end
+ # make-snapshot generates only release_date whereas file2lastrev generates both release_date and release_datetime
+ def revision_header(last, release_date, release_datetime = nil, branch = nil, title = nil, limit: 20)
+ short = short_revision(last)
+ if /[^\x00-\x7f]/ =~ title and title.respond_to?(:force_encoding)
+ title = title.dup.force_encoding("US-ASCII")
+ end
+ code = [
+ "#define RUBY_REVISION #{short.inspect}",
+ ]
+ unless short == last
+ code << "#define RUBY_FULL_REVISION #{last.inspect}"
+ end
+ if branch
+ e = '..'
+ name = branch.sub(/\A(.{#{limit-e.size}}).{#{e.size+1},}/o) {$1+e}
+ name = name.dump.sub(/\\#/, '#')
+ code << "#define RUBY_BRANCH_NAME #{name}"
+ end
+ if title
+ title = title.dump.sub(/\\#/, '#')
+ code << "#define RUBY_LAST_COMMIT_TITLE #{title}"
+ end
+ if release_datetime
+ t = release_datetime.utc
+ code << t.strftime('#define RUBY_RELEASE_DATETIME "%FT%TZ"')
+ end
+ code += self.release_date(release_date)
+ code
+ end
+
class SVN < self
register(".svn")
COMMAND = ENV['SVN'] || 'svn'
@@ -212,10 +279,6 @@ class VCS
"r#{rev}"
end
- def self.short_revision(rev)
- rev
- end
-
def _get_revisions(path, srcdir = nil)
if srcdir and self.class.local_path?(path)
path = File.join(srcdir, path)
@@ -350,7 +413,7 @@ class VCS
def commit
args = %W"#{COMMAND} commit"
if dryrun?
- VCS::DEBUG_OUT.puts(args.inspect)
+ VCS.dump(args, "commit: ")
return true
end
system(*args)
@@ -358,8 +421,21 @@ class VCS
end
class GIT < self
- register(".git") {|path, dir| File.exist?(File.join(path, dir))}
- COMMAND = ENV["GIT"] || 'git'
+ register(".git") do |path, dir|
+ SAFE_DIRECTORIES ||=
+ begin
+ command = ENV["GIT"] || 'git'
+ dirs = IO.popen(%W"#{command} config --global --get-all safe.directory", &:read).split("\n")
+ rescue
+ command = nil
+ dirs = []
+ ensure
+ VCS.dump(dirs, "safe.directory: ") if $DEBUG
+ COMMAND = command
+ end
+
+ COMMAND and File.exist?(File.join(path, dir))
+ end
def cmd_args(cmds, srcdir = nil)
(opts = cmds.last).kind_of?(Hash) or cmds << (opts = {})
@@ -367,7 +443,7 @@ class VCS
if srcdir
opts[:chdir] ||= srcdir
end
- VCS::DEBUG_OUT.puts cmds.inspect if debug?
+ VCS.dump(cmds, "cmds: ") if debug? and !$DEBUG
cmds
end
@@ -377,7 +453,7 @@ class VCS
def cmd_read_at(srcdir, cmds)
result = without_gitconfig { IO.pread(*cmd_args(cmds, srcdir)) }
- VCS::DEBUG_OUT.puts result.inspect if debug?
+ VCS.dump(result, "result: ") if debug?
result
end
@@ -398,7 +474,14 @@ class VCS
def _get_revisions(path, srcdir = nil)
ref = Branch === path ? path.to_str : 'HEAD'
gitcmd = [COMMAND]
- last = cmd_read_at(srcdir, [[*gitcmd, 'rev-parse', ref]]).rstrip
+ last = nil
+ IO.pipe do |r, w|
+ last = cmd_read_at(srcdir, [[*gitcmd, 'rev-parse', ref, err: w]]).rstrip
+ w.close
+ unless r.eof?
+ raise "#{COMMAND} rev-parse failed\n#{r.read.gsub(/^(?=\s*\S)/, ' ')}"
+ end
+ end
log = cmd_read_at(srcdir, [[*gitcmd, 'log', '-n1', '--date=iso', '--pretty=fuller', *path]])
changed = log[/\Acommit (\h+)/, 1]
modified = log[/^CommitDate:\s+(.*)/, 1]
@@ -460,16 +543,35 @@ class VCS
end
def without_gitconfig
- home = ENV.delete('HOME')
+ envs = (%w'HOME XDG_CONFIG_HOME' + ENV.keys.grep(/\AGIT_/)).each_with_object({}) do |v, h|
+ h[v] = ENV.delete(v)
+ end
+ ENV['GIT_CONFIG_SYSTEM'] = NullDevice
+ ENV['GIT_CONFIG_GLOBAL'] = global_config
yield
ensure
- ENV['HOME'] = home if home
+ ENV.update(envs)
+ end
+
+ def global_config
+ return NullDevice if SAFE_DIRECTORIES.empty?
+ unless @gitconfig
+ @gitconfig = Tempfile.new(%w"vcs_ .gitconfig")
+ @gitconfig.close
+ ENV['GIT_CONFIG_GLOBAL'] = @gitconfig.path
+ SAFE_DIRECTORIES.each do |dir|
+ system(*%W[#{COMMAND} config --global --add safe.directory #{dir}])
+ end
+ VCS.dump(`#{COMMAND} config --global --get-all safe.directory`, "safe.directory: ") if debug?
+ end
+ @gitconfig.path
end
def initialize(*)
super
@srcdir = File.realpath(@srcdir)
- VCS::DEBUG_OUT.puts @srcdir.inspect if debug?
+ @gitconfig = nil
+ VCS.dump(@srcdir, "srcdir: ") if debug?
self
end
@@ -575,11 +677,16 @@ class VCS
end
end
+ LOG_FIX_REGEXP_SEPARATORS = '/!:;|,#%&'
+
def format_changelog(path, arg, base_url = nil)
env = {'TZ' => 'JST-9', 'LANG' => 'C', 'LC_ALL' => 'C'}
- cmd = %W"#{COMMAND} log --format=fuller --notes=commits --notes=log-fix --topo-order --no-merges"
+ cmd = %W[#{COMMAND} log
+ --format=fuller --notes=commits --notes=log-fix --topo-order --no-merges
+ --fixed-strings --invert-grep --grep=[ci\ skip] --grep=[skip\ ci]
+ ]
date = "--date=iso-local"
- unless system(env, *cmd, date, chdir: @srcdir, out: NullDevice, exception: false)
+ unless system(env, *cmd, date, "-1", chdir: @srcdir, out: NullDevice, exception: false)
date = "--date=iso"
end
cmd << date
@@ -590,19 +697,51 @@ class VCS
cmd_pipe(env, cmd, chdir: @srcdir) do |r|
while s = r.gets("\ncommit ")
h, s = s.split(/^$/, 2)
+
+ next if /^Author: *dependabot\[bot\]/ =~ h
+
h.gsub!(/^(?:(?:Author|Commit)(?:Date)?|Date): /, ' \&')
if s.sub!(/\nNotes \(log-fix\):\n((?: +.*\n)+)/, '')
fix = $1
s = s.lines
fix.each_line do |x|
+ next unless x.sub!(/^(\s+)(?:(\d+)|\$(?:-\d+)?)/, '')
+ b = ($2&.to_i || (s.size - 1 + $3.to_i))
+ sp = $1
+ if x.sub!(/^,(?:(\d+)|\$(?:-\d+)?)/, '')
+ range = b..($1&.to_i || (s.size - 1 + $2.to_i))
+ else
+ range = b..b
+ end
case x
- when %r[^ +(\d+)s/(.+)/(.*)/]
- begin
- s[$1.to_i][$2] = $3
- rescue IndexError
- message = ["format_changelog failed to replace #{$2.dump} with #{$3.dump} at #$1\n"]
- from = [1, $1.to_i-2].max
- to = [s.size-1, $1.to_i+2].min
+ when %r[^s([#{LOG_FIX_REGEXP_SEPARATORS}])(.+)\1(.*)\1([gr]+)?]o
+ wrong = $2
+ correct = $3
+ if opt = $4 and opt.include?("r") # regexp
+ wrong = Regexp.new(wrong)
+ correct.gsub!(/(?<!\\)(?:\\\\)*\K(?:\\n)+/) {"\n" * ($&.size / 2)}
+ sub = opt.include?("g") ? :gsub! : :sub!
+ else
+ sub = false
+ end
+ range.each do |n|
+ if sub
+ ss = s[n].sub(/^#{sp}/, "") # un-indent for /^/
+ if ss.__send__(sub, wrong, correct)
+ s[n, 1] = ss.lines.map {|l| "#{sp}#{l}"}
+ next
+ end
+ else
+ begin
+ s[n][wrong] = correct
+ rescue IndexError
+ else
+ next
+ end
+ end
+ message = ["format_changelog failed to replace #{wrong.dump} with #{correct.dump} at #{n}\n"]
+ from = [1, n-2].max
+ to = [s.size-1, n+2].min
s.each_with_index do |e, i|
next if i < from
break if to < i
@@ -610,16 +749,23 @@ class VCS
end
raise message.join('')
end
- when %r[^( +)(\d+)i/(.*)/]
- s[$2.to_i, 0] = "#{$1}#{$3}\n"
- when %r[^ +(\d+)(?:,(\d+))?d]
- n = $1.to_i
- e = $2
- s[n..(e ? e.to_i : n)] = []
+ when %r[^i([#{LOG_FIX_REGEXP_SEPARATORS}])(.*)\1]o
+ insert = "#{sp}#{$2}\n"
+ range.reverse_each do |n|
+ s[n, 0] = insert
+ end
+ when %r[^d]
+ s[range] = []
end
end
s = s.join('')
end
+
+ if %r[^ +(https://github\.com/[^/]+/[^/]+/)commit/\h+\n(?=(?: +\n(?i: +Co-authored-by: .*\n)+)?(?:\n|\Z))] =~ s
+ issue = "#{$1}pull/"
+ s.gsub!(/\b(?:(?i:fix(?:e[sd])?) +|GH-)\K#(?=\d+\b)|\(\K#(?=\d+\))/) {issue}
+ end
+
s.gsub!(/ +\n/, "\n")
s.sub!(/^Notes:/, ' \&')
w.print h, s
@@ -663,13 +809,13 @@ class VCS
def commit(opts = {})
args = [COMMAND, "push"]
- args << "-n" if dryrun
+ args << "-n" if dryrun?
remote, branch = upstream
args << remote
branches = %W[refs/notes/commits:refs/notes/commits HEAD:#{branch}]
if dryrun?
branches.each do |b|
- VCS::DEBUG_OUT.puts((args + [b]).inspect)
+ VCS.dump(args + [b], "commit: ")
end
return true
end
@@ -699,7 +845,7 @@ class VCS
commits.each_with_index do |l, i|
r, a, c = l.split(' ')
dcommit = [COMMAND, "svn", "dcommit"]
- dcommit.insert(-2, "-n") if dryrun
+ dcommit.insert(-2, "-n") if dryrun?
dcommit << "--add-author-from" unless a == c
dcommit << r
system(*dcommit) or return false
@@ -719,4 +865,15 @@ class VCS
true
end
end
+
+ class Null < self
+ def get_revisions(path, srcdir = nil)
+ @modified ||= Time.now - 10
+ return nil, nil, @modified
+ end
+
+ def revision_header(last, release_date, release_datetime = nil, branch = nil, title = nil, limit: 20)
+ self.release_date(release_date)
+ end
+ end
end