ruby-changes:56530
From: Hiroshi <ko1@a...>
Date: Mon, 15 Jul 2019 09:40:44 +0900 (JST)
Subject: [ruby-changes:56530] Hiroshi SHIBATA: 0a711b0edf (master): Put vcs .rb to under the lib direcotory.
https://git.ruby-lang.org/ruby.git/commit/?id=0a711b0edf From 0a711b0edff6eaf978cfc17cdd6a7cc6c17c6686 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA <hsbt@r...> Date: Mon, 15 Jul 2019 08:34:24 +0900 Subject: Put vcs .rb to under the lib direcotory. Because it's the common library for tool files. diff --git a/tool/file2lastrev.rb b/tool/file2lastrev.rb index e39e742..68c660e 100755 --- a/tool/file2lastrev.rb +++ b/tool/file2lastrev.rb @@ -7,7 +7,7 @@ require 'optparse' https://github.com/ruby/ruby/blob/trunk/tool/file2lastrev.rb#L7 # this file run with BASERUBY, which may be older than 1.9, so no # require_relative -require File.expand_path('../vcs', __FILE__) +require File.expand_path('../lib/vcs', __FILE__) Program = $0 diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb new file mode 100644 index 0000000..dd6162c --- /dev/null +++ b/tool/lib/vcs.rb @@ -0,0 +1,669 @@ https://github.com/ruby/ruby/blob/trunk/tool/lib/vcs.rb#L1 +# 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 + (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/