summaryrefslogtreecommitdiff
path: root/tool/vcs.rb
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2019-07-15 08:34:24 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2019-07-15 09:40:22 +0900
commit0a711b0edff6eaf978cfc17cdd6a7cc6c17c6686 (patch)
treea4f7984cad1c874691ffa4f8c88167cf0451520b /tool/vcs.rb
parent8f7884761e30c453287d73de6ea733d565635ebc (diff)
Put vcs .rb to under the lib direcotory.
Because it's the common library for tool files.
Diffstat (limited to 'tool/vcs.rb')
-rw-r--r--tool/vcs.rb669
1 files changed, 0 insertions, 669 deletions
diff --git a/tool/vcs.rb b/tool/vcs.rb
deleted file mode 100644
index dd6162c..0000000
--- a/tool/vcs.rb
+++ /dev/null
@@ -1,669 +0,0 @@
-# vcs
-require 'fileutils'
-require 'optparse'
-
-# 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.
-
-ENV.delete('PWD')
-
-unless File.respond_to? :realpath
- require 'pathname'
- def File.realpath(arg)
- Pathname(arg).realpath.to_s
- end
-end
-
-def IO.pread(*args)
- STDERR.puts(args.inspect) if $DEBUG
- popen(*args) {|f|f.read}
-end
-
-if RUBY_VERSION < "2.0"
- class IO
- @orig_popen = method(:popen)
-
- if defined?(fork)
- def self.popen(command, *rest, &block)
- if command.kind_of?(Hash)
- env = command
- command = rest.shift
- end
- opts = rest.last
- if opts.kind_of?(Hash)
- dir = opts.delete(:chdir)
- rest.pop if opts.empty?
- opts.delete(:external_encoding)
- end
-
- if block
- @orig_popen.call("-", *rest) do |f|
- if f
- yield(f)
- else
- Dir.chdir(dir) if dir
- ENV.replace(env) if env
- exec(*command)
- end
- end
- else
- f = @orig_popen.call("-", *rest)
- unless f
- Dir.chdir(dir) if dir
- ENV.replace(env) if env
- exec(*command)
- end
- f
- end
- end
- else
- require 'shellwords'
- def self.popen(command, *rest, &block)
- if command.kind_of?(Hash)
- env = command
- oldenv = ENV.to_hash
- command = rest.shift
- end
- opts = rest.last
- if opts.kind_of?(Hash)
- dir = opts.delete(:chdir)
- rest.pop if opts.empty?
- opts.delete(:external_encoding)
- end
-
- command = command.shelljoin if Array === command
- Dir.chdir(dir || ".") do
- ENV.replace(env) if env
- @orig_popen.call(command, *rest, &block)
- ENV.replace(oldenv) if oldenv
- end
- end
- end
- end
-else
- module DebugPOpen
- verbose, $VERBOSE = $VERBOSE, nil if RUBY_VERSION < "2.1"
- refine IO.singleton_class do
- def popen(*args)
- STDERR.puts args.inspect if $DEBUG
- super
- end
- end
- ensure
- $VERBOSE = verbose unless verbose.nil?
- end
- using DebugPOpen
- module DebugSystem
- def system(*args)
- STDERR.puts args.inspect if $DEBUG
- exception = false
- opts = Hash.try_convert(args[-1])
- if RUBY_VERSION >= "2.6"
- unless opts
- opts = {}
- args << opts
- end
- exception = opts.fetch(:exception) {opts[:exception] = true}
- elsif opts
- exception = opts.delete(:exception) {true}
- args.pop if opts.empty?
- end
- ret = super(*args)
- raise "Command failed with status (#$?): #{args[0]}" if exception and !ret
- ret
- end
- end
- module Kernel
- prepend(DebugSystem)
- end
-end
-
-class VCS
- prepend(DebugSystem) if defined?(DebugSystem)
- class NotFoundError < RuntimeError; end
-
- @@dirs = []
- def self.register(dir, &pred)
- @@dirs << [dir, self, pred]
- end
-
- def self.detect(path, options = {}, argv = ::ARGV)
- uplevel_limit = options.fetch(:uplevel_limit, 0)
- curr = path
- begin
- @@dirs.each do |dir, klass, pred|
- if pred ? pred[curr, dir] : File.directory?(File.join(curr, dir))
- vcs = klass.new(curr)
- vcs.parse_options(argv)
- return vcs
- end
- end
- if uplevel_limit
- break if uplevel_limit.zero?
- uplevel_limit -= 1
- end
- prev, curr = curr, File.realpath(File.join(curr, '..'))
- end until curr == prev # stop at the root directory
- raise VCS::NotFoundError, "does not seem to be under a vcs: #{path}"
- end
-
- def self.local_path?(path)
- String === path or path.respond_to?(:to_path)
- end
-
- attr_reader :srcdir
-
- def initialize(path)
- @srcdir = path
- super()
- end
-
- def parse_options(opts, parser = OptionParser.new)
- case opts
- when Array
- parser.on("--[no-]dryrun") {|v| @dryrun = v}
- parser.on("--[no-]debug") {|v| @debug = v}
- parser.parse(opts)
- @debug = $DEBUG unless defined?(@debug)
- @dryrun = @debug unless defined?(@dryrun)
- when Hash
- unless (keys = opts.keys - [:debug, :dryrun]).empty?
- raise "Unknown options: #{keys.join(', ')}"
- end
- @debug = opts.fetch(:debug) {$DEBUG}
- @dryrun = opts.fetch(:dryrun) {@debug}
- end
- 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)}
-
- # return a pair of strings, the last revision and the last revision in which
- # +path+ was modified.
- def get_revisions(path)
- if self.class.local_path?(path)
- path = relative_to(path)
- end
- last, changed, modified, *rest = (
- begin
- if NullDevice
- save_stderr = STDERR.dup
- STDERR.reopen NullDevice, 'w'
- end
- _get_revisions(path, @srcdir)
- rescue Errno::ENOENT => e
- raise VCS::NotFoundError, e.message
- ensure
- if save_stderr
- STDERR.reopen save_stderr
- save_stderr.close
- end
- end
- )
- last or raise VCS::NotFoundError, "last revision not found"
- changed or raise VCS::NotFoundError, "changed revision not found"
- if modified
- /\A(\d+)-(\d+)-(\d+)\D(\d+):(\d+):(\d+(?:\.\d+)?)\s*(?:Z|([-+]\d\d)(\d\d))\z/ =~ modified or
- raise "unknown time format - #{modified}"
- match = $~[1..6].map { |x| x.to_i }
- off = $7 ? "#{$7}:#{$8}" : "+00:00"
- match << off
- begin
- modified = Time.new(*match)
- rescue ArgumentError
- modified = Time.utc(*$~[1..6]) + $7.to_i * 3600 + $8.to_i * 60
- end
- end
- return last, changed, modified, *rest
- end
-
- def modified(path)
- _, _, modified, * = get_revisions(path)
- modified
- end
-
- def relative_to(path)
- if path
- srcdir = File.realpath(@srcdir)
- path = File.realdirpath(path)
- list1 = srcdir.split(%r{/})
- list2 = path.split(%r{/})
- while !list1.empty? && !list2.empty? && list1.first == list2.first
- list1.shift
- list2.shift
- end
- if list1.empty? && list2.empty?
- "."
- else
- ([".."] * list1.length + list2).join("/")
- end
- else
- '.'
- end
- end
-
- def after_export(dir)
- end
-
- def revision_name(rev)
- self.class.revision_name(rev)
- end
-
- def short_revision(rev)
- self.class.short_revision(rev)
- end
-
- class SVN < self
- register(".svn")
- COMMAND = ENV['SVN'] || 'svn'
-
- def self.revision_name(rev)
- "r#{rev}"
- end
-
- def self.short_revision(rev)
- rev
- end
-
- def _get_revisions(path, srcdir = nil)
- if srcdir and local_path?(path)
- path = File.join(srcdir, path)
- end
- if srcdir
- info_xml = IO.pread(%W"#{COMMAND} info --xml #{srcdir}")
- info_xml = nil unless info_xml[/<url>(.*)<\/url>/, 1] == path.to_s
- end
- info_xml ||= IO.pread(%W"#{COMMAND} info --xml #{path}")
- _, last, _, changed, _ = info_xml.split(/revision="(\d+)"/)
- modified = info_xml[/<date>([^<>]*)/, 1]
- branch = info_xml[%r'<relative-url>\^/(?:branches/|tags/)?([^<>]+)', 1]
- [last, changed, modified, branch]
- end
-
- def self.search_root(path)
- return unless local_path?(path)
- parent = File.realpath(path)
- begin
- parent = File.dirname(wkdir = parent)
- return wkdir if File.directory?(wkdir + "/.svn")
- end until parent == wkdir
- end
-
- def get_info
- @info ||= IO.pread(%W"#{COMMAND} info --xml #{@srcdir}")
- end
-
- def url
- unless @url
- url = get_info[/<root>(.*)<\/root>/, 1]
- @url = URI.parse(url+"/") if url
- end
- @url
- end
-
- def wcroot
- unless @wcroot
- info = get_info
- @wcroot = info[/<wcroot-abspath>(.*)<\/wcroot-abspath>/, 1]
- @wcroot ||= self.class.search_root(@srcdir)
- end
- @wcroot
- end
-
- def branch(name)
- return trunk if name == "trunk"
- url + "branches/#{name}"
- end
-
- def tag(name)
- url + "tags/#{name}"
- end
-
- def trunk
- url + "trunk"
- end
- alias master trunk
-
- def branch_list(pat)
- IO.popen(%W"#{COMMAND} ls #{branch('')}") do |f|
- f.each do |line|
- line.chomp!
- line.chomp!('/')
- yield(line) if File.fnmatch?(pat, line)
- end
- end
- end
-
- def grep(pat, tag, *files, &block)
- cmd = %W"#{COMMAND} cat"
- files.map! {|n| File.join(tag, n)} if tag
- set = block.binding.eval("proc {|match| $~ = match}")
- IO.popen([cmd, *files]) do |f|
- f.grep(pat) do |s|
- set[$~]
- yield s
- end
- end
- end
-
- def export(revision, url, dir, keep_temp = false)
- if @srcdir and (rootdir = wcroot)
- srcdir = File.realpath(@srcdir)
- rootdir << "/"
- if srcdir.start_with?(rootdir)
- subdir = srcdir[rootdir.size..-1]
- subdir = nil if subdir.empty?
- FileUtils.mkdir_p(svndir = dir+"/.svn")
- FileUtils.ln_s(Dir.glob(rootdir+"/.svn/*"), svndir)
- system(COMMAND, "-q", "revert", "-R", subdir || ".", :chdir => dir) or return false
- FileUtils.rm_rf(svndir) unless keep_temp
- if subdir
- tmpdir = Dir.mktmpdir("tmp-co.", "#{dir}/#{subdir}")
- File.rename(tmpdir, tmpdir = "#{dir}/#{File.basename(tmpdir)}")
- FileUtils.mv(Dir.glob("#{dir}/#{subdir}/{.[^.]*,..?*,*}"), tmpdir)
- begin
- Dir.rmdir("#{dir}/#{subdir}")
- end until (subdir = File.dirname(subdir)) == '.'
- FileUtils.mv(Dir.glob("#{tmpdir}/#{subdir}/{.[^.]*,..?*,*}"), dir)
- Dir.rmdir(tmpdir)
- end
- return true
- end
- end
- IO.popen(%W"#{COMMAND} export -r #{revision} #{url} #{dir}") do |pipe|
- pipe.each {|line| /^A/ =~ line or yield line}
- end
- $?.success?
- end
-
- def after_export(dir)
- FileUtils.rm_rf(dir+"/.svn")
- end
-
- def branch_beginning(url)
- # `--limit` of svn-log is useless in this case, because it is
- # applied before `--search`.
- rev = IO.pread(%W[ #{COMMAND} log --xml
- --search=matz --search-and=has\ started
- -- #{url}/version.h])[/<logentry\s+revision="(\d+)"/m, 1]
- rev.to_i if rev
- end
-
- def export_changelog(url, from, to, path)
- range = [to || 'HEAD', (from ? from+1 : branch_beginning(url))].compact.join(':')
- IO.popen({'TZ' => 'JST-9', 'LANG' => 'C', 'LC_ALL' => 'C'},
- %W"#{COMMAND} log -r#{range} #{url}") do |r|
- open(path, 'w') do |w|
- IO.copy_stream(r, w)
- end
- end
- end
-
- def commit
- args = %W"#{COMMAND} commit"
- if dryrun?
- STDERR.puts(args.inspect)
- return true
- end
- system(*args)
- end
- end
-
- class GIT < self
- register(".git") {|path, dir| File.exist?(File.join(path, dir))}
- COMMAND = ENV["GIT"] || 'git'
-
- def cmd_args(cmds, srcdir = nil)
- (opts = cmds.last).kind_of?(Hash) or cmds << (opts = {})
- opts[:external_encoding] ||= "UTF-8"
- if srcdir and self.class.local_path?(srcdir)
- opts[:chdir] ||= srcdir
- end
- STDERR.puts cmds.inspect if debug?
- cmds
- end
-
- def cmd_pipe_at(srcdir, cmds, &block)
- without_gitconfig { IO.popen(*cmd_args(cmds, srcdir), &block) }
- end
-
- def cmd_read_at(srcdir, cmds)
- without_gitconfig { IO.pread(*cmd_args(cmds, srcdir)) }
- end
-
- def cmd_pipe(*cmds, &block)
- cmd_pipe_at(@srcdir, cmds, &block)
- end
-
- def cmd_read(*cmds)
- cmd_read_at(@srcdir, cmds)
- end
-
- def _get_revisions(path, srcdir = nil)
- gitcmd = [COMMAND]
- last = cmd_read_at(srcdir, [[*gitcmd, 'rev-parse', 'HEAD']]).rstrip
- log = cmd_read_at(srcdir, [[*gitcmd, 'log', '-n1', '--date=iso', '--pretty=fuller', *path]])
- changed = log[/\Acommit (\h+)/, 1]
- modified = log[/^CommitDate:\s+(.*)/, 1]
- branch = cmd_read_at(srcdir, [gitcmd + %W[symbolic-ref --short HEAD]])
- if branch.empty?
- branch_list = cmd_read_at(srcdir, [gitcmd + %W[branch --list --contains HEAD]]).lines.to_a
- branch, = branch_list.grep(/\A\*/)
- case branch
- when /\A\* *\(\S+ detached at (.*)\)\Z/
- branch = $1
- branch = nil if last.start_with?(branch)
- when /\A\* (\S+)\Z/
- branch = $1
- else
- branch = nil
- end
- unless branch
- branch_list.each {|b| b.strip!}
- branch_list.delete_if {|b| / / =~ b}
- branch = branch_list.min_by(&:length) || ""
- end
- end
- branch.chomp!
- branch = ":detached:" if branch.empty?
- upstream = cmd_read_at(srcdir, [gitcmd + %W[branch --list --format=%(upstream:short) #{branch}]])
- upstream.chomp!
- title = cmd_read_at(srcdir, [gitcmd + %W[log --format=%s -n1 #{upstream}..HEAD]])
- title = nil if title.empty?
- [last, changed, modified, branch, title]
- end
-
- def self.revision_name(rev)
- short_revision(rev)
- end
-
- def self.short_revision(rev)
- rev[0, 10]
- end
-
- def without_gitconfig
- home = ENV.delete('HOME')
- yield
- ensure
- ENV['HOME'] = home if home
- end
-
- def initialize(*)
- super
- if srcdir = @srcdir and self.class.local_path?(srcdir)
- @srcdir = File.realpath(srcdir)
- end
- self
- end
-
- Branch = Struct.new(:to_str)
-
- def branch(name)
- Branch.new(name)
- end
-
- alias tag branch
-
- def master
- branch("master")
- end
- alias trunk master
-
- def stable
- cmd = %W"#{COMMAND} for-each-ref --format=\%(refname:short) refs/heads/ruby_[0-9]*"
- branch(cmd_read(cmd)[/.*^(ruby_\d+_\d+)$/m, 1])
- end
-
- def branch_list(pat)
- cmd = %W"#{COMMAND} for-each-ref --format=\%(refname:short) refs/heads/#{pat}"
- cmd_pipe(cmd) {|f|
- f.each {|line|
- line.chomp!
- yield line
- }
- }
- end
-
- def grep(pat, tag, *files, &block)
- cmd = %W[#{COMMAND} grep -h --perl-regexp #{tag} --]
- set = block.binding.eval("proc {|match| $~ = match}")
- cmd_pipe(cmd+files) do |f|
- f.grep(pat) do |s|
- set[$~]
- yield s
- end
- end
- end
-
- def export(revision, url, dir, keep_temp = false)
- ret = system(COMMAND, "clone", "-s", (@srcdir || '.').to_s, "-b", url, dir)
- ret
- end
-
- def after_export(dir)
- FileUtils.rm_rf(Dir.glob("#{dir}/.git*"))
- end
-
- def branch_beginning(url)
- cmd_read(%W[ #{COMMAND} log -n1 --format=format:%H
- --author=matz --committer=matz --grep=has\ started
- -- version.h include/ruby/version.h])
- end
-
- def export_changelog(url, from, to, path)
- from, to = [from, to].map do |rev|
- rev or next
- if Integer === rev
- rev = cmd_read({'LANG' => 'C', 'LC_ALL' => 'C'},
- %W"#{COMMAND} log -n1 --format=format:%H" <<
- "--grep=^ *git-svn-id: .*@#{rev} ")
- end
- rev unless rev.empty?
- end
- unless /./.match(from ||= branch_beginning(url))
- raise "cannot find the beginning revision of the branch"
- end
- range = [from, (to || 'HEAD')].join('^..')
- cmd_pipe({'TZ' => 'JST-9', 'LANG' => 'C', 'LC_ALL' => 'C'},
- %W"#{COMMAND} log --format=medium --no-notes --date=iso-local --topo-order #{range}", "rb") do |r|
- format_changelog(r, path)
- end
- end
-
- def format_changelog(r, path)
- IO.copy_stream(r, path)
- end
-
- def commit(opts = {})
- args = [COMMAND, "push"]
- args << "-n" if dryrun
- (branch = cmd_read(%W"#{COMMAND} symbolic-ref --short HEAD")).chomp!
- (upstream = cmd_read(%W"#{COMMAND} branch --list --format=%(upstream) #{branch}")).chomp!
- while ref = upstream[%r"\Arefs/heads/(.*)", 1]
- upstream = cmd_read(%W"#{COMMAND} branch --list --format=%(upstream) #{ref}")
- end
- unless %r"\Arefs/remotes/([^/]+)/(.*)" =~ upstream
- raise "Upstream not found"
- end
- args << $1 << "HEAD:#$2"
- if dryrun?
- STDERR.puts(args.inspect)
- return true
- end
- system(*args) or return false
- true
- end
- end
-
- class GITSVN < GIT
- def self.revision_name(rev)
- SVN.revision_name(rev)
- end
-
- def self.short_revision(rev)
- SVN.short_revision(rev)
- end
-
- def format_changelog(r, path)
- open(path, 'w') do |w|
- sep = "-"*72
- w.puts sep
- while s = r.gets('')
- author = s[/^Author:\s*(\S+)/, 1]
- time = s[/^Date:\s*(.+)/, 1]
- s = r.gets('')
- s.gsub!(/^ {4}/, '')
- s.sub!(/^git-svn-id: .*@(\d+) .*\n+\z/, '')
- rev = $1
- s.gsub!(/^ {8}/, '') if /^(?! {8}|$)/ !~ s
- s.sub!(/\n\n\z/, "\n")
- if /\A(\d+)-(\d+)-(\d+)/ =~ time
- date = Time.new($1.to_i, $2.to_i, $3.to_i).strftime("%a, %d %b %Y")
- end
- lines = s.count("\n")
- lines = "#{lines} line#{lines == 1 ? '' : 's'}"
- w.puts "r#{rev} | #{author} | #{time} (#{date}) | #{lines}\n\n"
- w.puts s, sep
- end
- end
- end
-
- def last_changed_revision
- rev = cmd_read(%W"#{COMMAND} svn info"+[STDERR=>[:child, :out]])[/^Last Changed Rev: (\d+)/, 1]
- com = cmd_read(%W"#{COMMAND} svn find-rev r#{rev}").chomp
- return rev, com
- end
-
- def commit(opts = {})
- rev, com = last_changed_revision
- head = cmd_read(%W"#{COMMAND} symbolic-ref --short HEAD").chomp
-
- commits = cmd_read([COMMAND, "log", "--reverse", "--format=%H %ae %ce", "#{com}..@"], "rb").split("\n")
- commits.each_with_index do |l, i|
- r, a, c = l.split
- dcommit = [COMMAND, "svn", "dcommit"]
- dcommit.insert(-2, "-n") if dryrun
- dcommit << "--add-author-from" unless a == c
- dcommit << r
- system(*dcommit) or return false
- system(COMMAND, "checkout", head) or return false
- system(COMMAND, "rebase") or return false
- end
-
- if rev
- old = [cmd_read(%W"#{COMMAND} log -1 --format=%H").chomp]
- old << cmd_read(%W"#{COMMAND} svn reset -r#{rev}")[/^r#{rev} = (\h+)/, 1]
- 3.times do
- sleep 2
- system(*%W"#{COMMAND} pull --no-edit --rebase")
- break unless old.include?(cmd_read(%W"#{COMMAND} log -1 --format=%H").chomp)
- end
- end
- true
- end
- end
-end