rakelib/hglib.rb
changeset 83 8405c22a8ffd
parent 79 1058962ee3ef
child 85 6d918f722075
--- a/rakelib/hglib.rb	Thu Nov 24 10:40:01 2016 +0000
+++ b/rakelib/hglib.rb	Thu Nov 24 20:22:25 2016 +0000
@@ -38,11 +38,11 @@
     require 'logger'
     $LOGGER = Logger.new(STDOUT)
     
-	if (VERBOSE != nil) then
-	    $LOGGER.level = Logger::DEBUG
-	else 
-	   $LOGGER.level = Logger::INFO	
-	end
+    if (VERBOSE != nil) then
+        $LOGGER.level = Logger::DEBUG
+    else 
+       $LOGGER.level = Logger::INFO 
+    end
   else 
     require 'syslog/logger'
     $LOGGER = Syslog::Logger.new($0)    
@@ -130,13 +130,13 @@
       files = Dir.glob('/etc/mercurial/hgrc.d/*.rc') + 
           [ '/etc/mercurial/hgrc' ,
           hgrc() ]
-	  if Gem.win_platform? then
-	     hg_exe = which("hg")
-		 hgrc_d = File.join(File.dirname(hg_exe), "hgrc.d")
-		 if File.directory? (hgrc_d) then
-			files += Dir.glob("#{hgrc_d}\\*.rc".gsub('\\', '/'))
-		 end
-	  end
+      if Gem.win_platform? then
+         hg_exe = which("hg")
+         hgrc_d = File.join(File.dirname(hg_exe), "hgrc.d")
+         if File.directory? (hgrc_d) then
+            files += Dir.glob("#{hgrc_d}\\*.rc".gsub('\\', '/'))
+         end
+      end
       @@config = IniFile.new()
       files.each do | file |
         if File.exist?(file)
@@ -149,12 +149,111 @@
   end
 
   def self.hgrc()
-  	return File.expand_path('~/.hgrc')
+    return File.expand_path('~/.hgrc')
   end
 
   class Repository
+    @@HOSTS_ON_LAN = {}
+
     attr_accessor :path, :config
 
+
+    private
+    # Return --ssh config string for use with passed remote url or nil
+    # if no special --ssh config is needed. 
+    # 
+    # Rationale: 
+    #
+    # On Windows, most of users tend to use TortoiseHG and hg.exe comming with
+    # it. THG makes hg.exe to use (shipped) plink.exe which is bad for performance 
+    # since it uses 16k channel input buffer (!) leading to a pretty slow transfers 
+    # (a lot of iowaits...)
+    # OpenSSH OTOH has 2MB input buffer which is good though on Windows bit 
+    # oversized as Windows TCP window size is fixed to 65k for all connections with 
+    # RTT less than 1ms. Still, 65k better then 16k. 
+    # As a workaround, look if MSYS2's OpenSSH client is installed and if so, use that 
+    # one - but only if `ui.ssh` config option has the default value. 
+    # Ugly, isn't it? 
+    def self.sshconf(uri_string) 
+      uri = URI(uri_string)
+      ssh = nil      
+      if (uri.scheme == 'ssh') 
+        ssh_in_path = which('ssh') ? true : false
+        if Gem.win_platform? then      
+          # Running on Windows 
+          #
+          # Mercurial uses `ssh` by default, so to use `plink.exe`, `ui.ssh` 
+          # config option has to be explicitly set.
+          #
+          # It it's set to `plink.exe`, check whether MSYS's `ssh.exe` is available
+          # and if so, change it to `ssh.exe`...
+          ssh_configured = HG::config['ui']['ssh'] 
+          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
+            # A downside of messing with ssh configuration is that OpenSSH client does not know how
+            # to connect to pageant. So issue a warning...
+            if ssh then
+              $LOGGER.info("Passing --ssh \"#{ssh}\" option to 'hg' command to reduce CPU load on LAN tranfers")
+              if not ENV['SSH_AUTH_SOCK'] then
+                  $LOGGER.warn("Transfet may fail since MSYS2 `ssh.exe` dont know how to talk to pageant. ")
+                  $LOGGER.warn("Consider using ssh-agent or ssh-pageant (if you want to use PuTTY's pageant)")
+              end
+            end
+            # Turn off SSH compression - data transferred by Mercurial are either
+            # already compressed or --uncompressed was given to reduce CPU load
+            # in which case passing -C would reduce it further.
+            if ssh and HG::Repository::host_on_lan?(uri.host) then
+              ssh += " -C"
+            end
+          end       
+        else           
+          # Turn off SSH compression - data transferred by Mercurial are either
+          # already compressed or --uncompressed was given to reduce CPU load
+          # in which case passing -C would reduce it further.
+          if ssh_in_path and HG::Repository::host_on_lan?(uri.host) then
+            ssh = "ssh -C"
+          end
+        end
+      end
+      return ssh
+    end
+
+    # Same as class method, but allows for remote aliases
+    def sshconf(remote) 
+      return HG::Repository::sshconf(self.paths[remote] || remote)
+    end
+
+    # Given a hostname, return true if the host is on local LAN, false
+    # otherwise. 
+    #
+    # Results are cached to reduce resolver queries (maybe not worth it,
+    # system may cache answers anyways)
+    #
+    # The detection is rather simplistic - it only checks if host's address
+    # is from private IP range - and only for IPv4. 
+    # This may cause problems when using over VPN that assigns private address
+    # (the usuall case). In that case this code will treat is a local
+    # which may result in transfer of uncompressed data over WAN. Not nice,
+    # This should be fixed, somehow. 
+    def self.host_on_lan?(hostname)
+      if not @@HOSTS_ON_LAN.has_key? hostname then
+        require 'resolv'
+        addr = Resolv.getaddress(hostname)
+        # Really poor detection of LAN, but since this is an 
+        # optimization, getting this wrong does not hurt.         
+        local = (addr.start_with? '192.168.') or (addr.start_with? '10.10.')
+        @@HOSTS_ON_LAN[hostname] = local
+      end
+      return @@HOSTS_ON_LAN[hostname]
+    end
+
+    public
     # Clone a repository from given `uri` to given `directory`. 
     # Returns an `HG::Repository` instance representing the repository
     # clone. 
@@ -162,60 +261,14 @@
     # empty. Use this when you're going to issue `update(rev)` shortly after.
     #
     def self.clone(uri, directory, noupdate: false)
-	  uri_obj = URI(uri)
+      uri_obj = URI(uri)
       host = uri_obj.host
-	  scheme = uri_obj.scheme
+      scheme = uri_obj.scheme
       # When cloning over LAN, use --uncompressed option
       # as it tends to be faster if bandwidth is good (1GB norm
       # these days) amd saves some CPU cycles.
-	  local = false
-      if host
-        require 'resolv'
-        addr = Resolv.getaddress(host)
-        # Really poor detection of LAN, but since this is an 
-        # optimization, getting this wrong does not hurt.         
-        local = (addr.start_with? '192.168.') or (addr.start_with? '10.10.')
-      end
-	  # On Windows, most of users tend to use TortoiseHG and hg.exe comming with
-	  # it. THG makes hg.exe to use (shipped) plink.exe which is bad for performance 
-	  # since it uses 16k channel input buffer (!) leading to a pretty slow transfers 
-	  # (a lot of iowaits...)
-	  # OpenSSH OTOH has 2MB input buffer which is good though on Windows bit 
-	  # oversized as Windows TCP window size is fixed to 65k for all connections with 
-	  # RTT less than 1ms. Still, 65k better then 16k. 
-	  # As a workaround, look if MSYS2's OpenSSH client is installed and if so, use that 
-	  # one - but only if `ui.ssh` config option has the default value. 
-	  # Ugly, isn't it? 
-	  ssh = nil
-	  puts "1 #{HG::config['ui'].has_key?('ssh')}"
-	  if (scheme == 'ssh') and (Gem.win_platform?) and (HG::config['ui'].has_key?('ssh')) then
-	    # For THG, `ui.ssh` is configured as: 
-		# 
-		#     "C:\Program Files\TortoiseHg\lib\TortoisePlink.exe" -ssh -2
-		#
-		# Be more relaxed and conver all "standard" plink.exe usages...
-		if File.exist? "c:\\msys64\\usr\\bin\\ssh.exe" then
-			# Rename. only if user did not override the setting! 
-			if /^.*[pP]link.exe"?\s*(-ssh)?\s*(-2)?$/ =~ HG::config['ui']['ssh'] then
-				ssh = "\"c:\\msys64\\usr\\bin\\ssh.exe\""
-			end
-			# Since we're messing wth ssh config anyway, add -C if we're cloning "over LAN"
-			# to save some CPU cycles. Same reasosing as for --uncompressed above. 
-			if local then
-				ssh += " -C"
-			end
-		end
-	  end
-	  
-	  # A downside of messing with ssh configuration is that OpenSSH client does not know how
-	  # to connect to pageant. So issue a warning...
-	  if ssh then
-		$LOGGER.warn("Passing --ssh \"#{ssh}\" option to 'hg clone' for better performance.")
-		if not ENV['SSH_AUTH_SOCK'] then
-			$LOGGER.warn("Clone may fail since MSYS2 `ssh.exe` dont know how to talk to pageant. ")
-			$LOGGER.warn("Consider using ssh-pageant")
-		end
-	  end
+      local = HG::Repository::host_on_lan?(URI(uri).host)
+      ssh = HG::Repository::sshconf(uri)      
       if noupdate then
         HG::hg("clone", uri, directory, ssh: ssh, uncompressed: local, noupdate: true)
       else
@@ -342,7 +395,7 @@
     public 
 
     def incoming(remote = 'default', user: nil, pass: nil, rev: nil)
-      hg("incoming", remote, config: authconf(remote, user, pass), rev: rev) do | status, stdout|        
+      hg("incoming", remote, ssh: sshconf(remote), config: authconf(remote, user, pass), rev: rev) do | status, stdout|        
         case status.exitstatus
         when 0
           STDOUT.print stdout
@@ -355,7 +408,7 @@
     end
 
     def pull(remote = 'default', user: nil, pass: nil, rev: nil, bookmarks: nil)
-      hg("pull", remote, config: authconf(remote, user, pass), rev: rev, bookmark: bookmarks) do | status, stdout|                
+      hg("pull", remote, ssh: sshconf(remote), config: authconf(remote, user, pass), rev: rev, bookmark: bookmarks) do | status, stdout|                
         STDOUT.print stdout
         case status.exitstatus
         when 0                    
@@ -369,7 +422,7 @@
     end
 
     def outgoing(remote = 'default', user: nil, pass: nil, rev: nil)
-      hg("outgoing", remote, config: authconf(remote, user, pass), rev: rev) do | status, stdout|                
+      hg("outgoing", remote, ssh: sshconf(remote), config: authconf(remote, user, pass), rev: rev) do | status, stdout|                
         case status.exitstatus
         when 0          
           STDOUT.print stdout
@@ -382,7 +435,7 @@
     end
 
     def push(remote = 'default', user: nil, pass: nil, rev: nil, bookmarks: [])
-      hg("push", remote, config: authconf(remote, user, pass), rev: rev, bookmark: bookmarks) do | status, stdout|                
+      hg("push", remote, ssh: sshconf(remote), config: authconf(remote, user, pass), rev: rev, bookmark: bookmarks) do | status, stdout|                
         case status.exitstatus
         when 0          
           STDOUT.print stdout
@@ -452,8 +505,8 @@
     end
 
     def has_revision?(rev)
-    	revs = log(rev)
-    	return revs.size > 0      
+        revs = log(rev)
+        return revs.size > 0      
     end
 
     # Lookup a repository in given `directory`. If found,