Do not tamper with SSH configuration if SSH agent is not running.
Sigh, we should not tamper with SSH configuration wildly. User may have
her ssh and mercurial properly configured to use `plink.exe` and `pageant`.
If we just start using `ssh.exe` clone/pull might not work beause
`ssh.exe` cannot talk to `pageant`. So, if we don't fine OpenSSH's
style of agent, don't use `ssh.exe` event if available.
require 'rakelib/hglib'
module Rake
end
module Rake::StX
end
# Cross-platform way of finding an executable in the $PATH.
#
# which('ruby') #=> /usr/bin/ruby
def which(cmd)
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
exts.each { |ext|
exe = File.join(path, "#{cmd}#{ext}")
return exe if File.executable?(exe) && !File.directory?(exe)
}
end
return nil
end
cvs_rsh_set = false
module Rake::Stx::SCM
# Not quite sure why following code
#
# include RakeFileUtils
#
# does not make extra methods (like `sh`) defined in `extensions.rb`
# visible here. To workaround, define them here. Ugly...
module_function
def sh(cmd, cwd: Dir.pwd, &block)
begin
return RakeFileUtils::sh(cmd, cwd: cwd, &block)
rescue
return false
end
end
# Make sure CVS_RSH environment variable is properly set. Prefer MSYS2 ssh.exe
# over plink.exe. For details, see `hglib.rb`, method `sshconf()`
module_function
def ensure_cvs_rsh_set()
if @cvs_rsh_set then
return
end
ssh = nil
ssh_configured = ENV['CVS_RSH']
ssh_in_path = which('ssh') ? true : false
plink_in_path = which('plink') ? true : false
if Gem.win_platform? then
# If CVS_RSH is not set or is set to plink.exe, try to change to
# MSYS2 ssh.exe as it gives better performance on (fast) LANs.
if /^.*[pP]link(\.exe)?"?\s*(-ssh)?\s*(-2)?$/ =~ ssh_configured then
if ssh_in_path then
ssh = 'ssh'
else
if (File.exist? "c:\\msys64\\usr\\bin\\ssh.exe") then
ssh = "\"c:\\msys64\\usr\\bin\\ssh.exe\""
end
end
# Sigh, we should not tamper with SSH configuration wildly. User may have
# her ssh and mercurial properly configured to use `plink.exe` and `pageant`.
# If we just start using `ssh.exe` clone/pull might not work beause
# `ssh.exe` cannot talk to `pageant`. So, if we don't find OpenSSH's
# style of agent, don't use `ssh.exe` event if available.
if ssh then
if ENV['SSH_AUTH_SOCK'] then
# Good, OpenSSH agent running but still, be nice and tell the
# user SSH configuration has been tampered wirh.
info("Setting CVS_RSH=\"#{ssh}\" for faster transfers")
else
# No agent, no fun. Be nice and give user a hit
warn("Not using CVS_RSH=\"#{ssh}\" option because SSH agent is not running")
warn("For faster CVS checkout over LAN, consider using ssh-agent or ssh-pageant (if you want to use PuTTY's pageant)")
ssh = nil
end
end
end
else
if not ssh_configured then
ssh = "ssh"
end
end
if ssh then
ENV['CVS_RSH'] = ssh
end
cvs_rsh_set = true
end
public
class CheckoutException < Exception
end # class CheckoutException
def self._check_type(type)
if (type != :cvs and type != :svn and type != :git and type != :hg)
raise CheckoutException.new("Unknown version control system type (#{type})")
end
end
def self.update(repository, directory, **kwargs)
type = repository.type
url = repository.canonical
self._check_type(type)
root = kwargs[:root] || BUILD_DIR
branch = kwargs[:branch]
if branch == nil
if type == :svn
branch = 'trunk'
elsif type == :hg
branch = 'default'
end
end
wc = root / directory
if (! File.exist? wc)
self.checkout(repository, directory, **kwargs)
return
end
case type
when :svn then _update_svn(repository, directory, branch, root, **kwargs)
when :cvs then _update_cvs(repository, directory, branch, root, **kwargs)
when :git then _update_git(repository, directory, branch, root, **kwargs)
when :hg then _update_hg(repository, directory, branch, root, **kwargs)
end
end
def self._update_hg(repository, directory, branch, root, **kwargs)
wc = root / directory
separator = kwargs[:separator] || '.'
revision = kwargs[:revision]
if directory != nil then
url = "#{repository.canonical}/#{directory.gsub('/', separator)}"
end
hg = HG::Repository.new(wc)
begin
paths = hg.paths
if repository.staging then
if not paths.has_key? 'staging'
paths['staging'] = "#{repository.staging}/#{directory.gsub('/', separator)}"
hg.paths = paths
end
hg.pull('staging')
end
if not paths.has_key? 'canonical'
paths['canonical'] = "#{repository.canonical}/#{directory.gsub('/', separator)}"
hg.paths = paths
end
hg.pull('default')
if paths['default'] != paths['canonical'] then
hg.pull('canonical')
end
# If revision is not specified, then look for an active bookmark
# and update to it. If no bookmark is active, then look for bookmark
# `master`. If it exist, then update to `master`. If it
# does not, then update to tip or throw an error.
# The error is thrown if there's no bookmark `master` and
# branch has multiple heads since it's not clear which
# head rev to use.
if not revision then
revision = hg.bookmark()
if not revision then
bookmarks = hg.bookmarks(branch)
if (bookmarks.has_key? 'master') then
revision = 'master'
else
if (hg.heads(branch).size > 1) then
raise CheckoutException.new("HG: Cannot checkout #{directory}: branch #{branch} has multiple heads but no bookmark named 'master'!")
end
end
end
end
hg.update(revision || branch)
rescue Exception => ex
raise CheckoutException.new("HG: Cannot update #{wc}: #{ex.message}")
end
end
def self._update_svn(repository, directory, branch, root, **kwargs)
wc = root / directory
if not sh %W{svn --non-interactive --trust-server-cert update}, cwd: wc
raise CheckoutException.new("SVN: Cannot update #{wc}")
end
end
def self._update_cvs(repository, directory, branch, root, **kwargs)
ensure_cvs_rsh_set()
wc = root / directory
if File.directory? wc
if not sh %W{cvs -z 9 update -A -d}, cwd: wc
raise CheckoutException.new("CVS: Cannot update #{wc}")
end
else
if not sh %W{cvs -z 9 update -A #{File.basename(wc)}}, cwd: File.dirname(wc)
raise CheckoutException.new("CVS: Cannot update #{wc}")
end
end
end
def self.checkout(repository, directory, **kwargs)
type = repository.type
url = repository.canonical
self._check_type(type)
root = kwargs[:root] || BUILD_DIR
branch = kwargs[:branch]
if branch == nil
if type == :svn
branch = 'trunk'
elsif type == :hg
branch = 'default'
end
end
wc = root / directory
if (File.exist? wc)
self.update(repository, directory, **kwargs)
return
end
if (not File.exists? File.dirname(wc))
begin
FileUtils.mkdir_p(File.dirname(wc))
rescue => ex
raise CheckoutException.new("Cannot create directory for working copy (#{ex})")
end
end
case type
when :svn then _checkout_svn(repository, directory, branch, root, **kwargs)
when :cvs then _checkout_cvs(repository, directory, branch, root, **kwargs)
when :git then _checkout_git(repository, directory, branch, root, **kwargs)
when :hg then _checkout_hg(repository, directory, branch, root, **kwargs)
end
end
def self._checkout_svn(repository, directory, branch, root, **kwargs)
url = "#{repository.canonical}/#{directory}/#{branch}"
if not sh %W{svn --non-interactive --trust-server-cert co #{url} #{directory}}, cwd: root
raise CheckoutException.new("SVN: Cannot checkout from #{url}")
end
end
def self._checkout_hg(repository, directory, branch, root, **kwargs)
separator = kwargs[:separator] || '.'
revision = kwargs[:revision]
paths = { 'default' => "#{repository.upstream}/#{directory.gsub('/', separator)}",
'canonical' => "#{repository.canonical}/#{directory.gsub('/', separator)}" }
if repository.staging then
paths['staging'] = "#{repository.staging}/#{directory.gsub('/', separator)}"
end
begin
if repository.staging then
hg = HG::Repository.clone(paths['staging'], root / directory, noupdate: true)
hg.paths = paths
hg.pull('default')
else
hg = HG::Repository.clone(paths['default'], root / directory, noupdate: true)
hg.paths = paths
end
if paths['default'] != paths['canonical'] then
hg.pull('canonical')
end
# If revision is not specified, then look for bookmark
# `master`. If it exist, then check out `master`. If it
# does not, then checkout tip or throw an error.
# The error is thrown if there's no bookmark `master` and
# branch has multiple heads since it's not clear which
# head rev to use.
if not revision then
bookmarks = hg.bookmarks(branch)
if (bookmarks.has_key? 'master') then
revision = 'master'
else
if (hg.heads(branch).size > 1) then
raise CheckoutException.new("HG: Cannot checkout #{directory}: branch #{branch} has multiple heads but no bookmark named 'master'!")
end
end
end
hg.update(revision || branch)
#rescue Exception => e
# raise CheckoutException.new("HG: Cannot clone from #{url}: #{e.message}")
end
end
def self._checkout_cvs(repository, directory, branch, root, **kwargs)
ensure_cvs_rsh_set()
if not sh %W{cvs -z 9 -d #{repository.canonical} co #{directory}}, cwd: root
raise CheckoutException.new("CVS: Cannot checkout #{directory}from #{repository.url}")
end
end
end # module Rake::Stx::SCM
def checkout(repo_name, directory, **kwargs)
# repository should be symbolic name
repo = Rake::Stx::Configuration::Repository::find(repo_name)
if not repo then
error("checkout(): No repository found (#{repo_name})")
end
kwargs[:separator] = repo.separator
Rake::Stx::SCM.checkout(repo, directory, **kwargs)
end
def update(repo_name, directory, **kwargs)
# repository should be symbolic name
repo = Rake::Stx::Configuration::Repository::find(repo_name)
if not repo then
error("update(): No repository found (#{repo_name})")
end
kwargs[:separator] = repo.separator
Rake::Stx::SCM.update(repo, directory, **kwargs)
end
def cvs(url, directory, **kwargs)
repo = Rake::Stx::Configuration::Repository.new(:type => :cvs, :url => url)
Rake::Stx::SCM.checkout(repo, directory, **kwargs)
end
def svn(url, directory, **kwargs)
repo = Rake::Stx::Configuration::Repository.new(:type => :svn, :url => url)
Rake::Stx::SCM.checkout(repo, directory, **kwargs)
end
def hg(url, directory, **kwargs)
repo = Rake::Stx::Configuration::Repository.new(:type => :hg, :url => url)
Rake::Stx::SCM.checkout(repo, directory, **kwargs)
end
def git(url, directory, **kwargs)
repo = Rake::Stx::Configuration::Repository.new(:type => :git, :url => url)
Rake::Stx::SCM.checkout(repo, directory, **kwargs)
end