From 3e04a05cd8219ce11d90a84a827290e84e26d6f5 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 22:50:35 -0200 Subject: [PATCH 01/13] Extract GitReader to it's own file --- lib/hub/context.rb | 54 ++-------------------------------- lib/hub/context/git_reader.rb | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 52 deletions(-) create mode 100644 lib/hub/context/git_reader.rb diff --git a/lib/hub/context.rb b/lib/hub/context.rb index 87ab2c984..e052c56a4 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -2,6 +2,8 @@ require 'forwardable' require 'uri' +require_relative 'context/git_reader' + module Hub # Methods for inspecting the environment, such as reading git config, # repository info, and other. @@ -11,58 +13,6 @@ module Context NULL = defined?(File::NULL) ? File::NULL : File.exist?('/dev/null') ? '/dev/null' : 'NUL' # Shells out to git to get output of its commands - class GitReader - attr_reader :executable - - def initialize(executable = nil, &read_proc) - @executable = executable || 'git' - # caches output when shelling out to git - read_proc ||= lambda { |cache, cmd| - result = %x{#{command_to_string(cmd)} 2>#{NULL}}.chomp - cache[cmd] = $?.success? && !result.empty? ? result : nil - } - @cache = Hash.new(&read_proc) - end - - def add_exec_flags(flags) - @executable = Array(executable).concat(flags) - end - - def read_config(cmd, all = false) - config_cmd = ['config', (all ? '--get-all' : '--get'), *cmd] - config_cmd = config_cmd.join(' ') unless cmd.respond_to? :join - read config_cmd - end - - def read(cmd) - @cache[cmd] - end - - def stub_config_value(key, value, get = '--get') - stub_command_output "config #{get} #{key}", value - end - - def stub_command_output(cmd, value) - @cache[cmd] = value.nil? ? nil : value.to_s - end - - def stub!(values) - @cache.update values - end - - private - - def to_exec(args) - args = Shellwords.shellwords(args) if args.respond_to? :to_str - Array(executable) + Array(args) - end - - def command_to_string(cmd) - full_cmd = to_exec(cmd) - full_cmd.respond_to?(:shelljoin) ? full_cmd.shelljoin : full_cmd.join(' ') - end - end - module GitReaderMethods extend Forwardable diff --git a/lib/hub/context/git_reader.rb b/lib/hub/context/git_reader.rb new file mode 100644 index 000000000..d7db60db9 --- /dev/null +++ b/lib/hub/context/git_reader.rb @@ -0,0 +1,55 @@ +module Hub + module Context + class GitReader + attr_reader :executable + + def initialize(executable = nil, &read_proc) + @executable = executable || 'git' + # caches output when shelling out to git + read_proc ||= lambda { |cache, cmd| + result = %x{#{command_to_string(cmd)} 2>#{NULL}}.chomp + cache[cmd] = $?.success? && !result.empty? ? result : nil + } + @cache = Hash.new(&read_proc) + end + + def add_exec_flags(flags) + @executable = Array(executable).concat(flags) + end + + def read_config(cmd, all = false) + config_cmd = ['config', (all ? '--get-all' : '--get'), *cmd] + config_cmd = config_cmd.join(' ') unless cmd.respond_to? :join + read config_cmd + end + + def read(cmd) + @cache[cmd] + end + + def stub_config_value(key, value, get = '--get') + stub_command_output "config #{get} #{key}", value + end + + def stub_command_output(cmd, value) + @cache[cmd] = value.nil? ? nil : value.to_s + end + + def stub!(values) + @cache.update values + end + + private + + def to_exec(args) + args = Shellwords.shellwords(args) if args.respond_to? :to_str + Array(executable) + Array(args) + end + + def command_to_string(cmd) + full_cmd = to_exec(cmd) + full_cmd.respond_to?(:shelljoin) ? full_cmd.shelljoin : full_cmd.join(' ') + end + end + end +end From 73bea934768663672675a179be0fd4e3afb9a082 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 22:58:09 -0200 Subject: [PATCH 02/13] Extract GitReaderMethods to it's own file --- lib/hub/context.rb | 16 ++-------------- lib/hub/context/git_reader_methods.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 lib/hub/context/git_reader_methods.rb diff --git a/lib/hub/context.rb b/lib/hub/context.rb index e052c56a4..78d32e27b 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -3,6 +3,7 @@ require 'uri' require_relative 'context/git_reader' +require_relative 'context/git_reader_methods' module Hub # Methods for inspecting the environment, such as reading git config, @@ -12,19 +13,6 @@ module Context NULL = defined?(File::NULL) ? File::NULL : File.exist?('/dev/null') ? '/dev/null' : 'NUL' - # Shells out to git to get output of its commands - module GitReaderMethods - extend Forwardable - - def_delegator :git_reader, :read_config, :git_config - def_delegator :git_reader, :read, :git_command - - def self.extended(base) - base.extend Forwardable - base.def_delegators :'self.class', :git_config, :git_command - end - end - class Error < RuntimeError; end class FatalError < Error; end @@ -398,7 +386,7 @@ def git_editor module System # Cross-platform web browser command; respects the value set in $BROWSER. - # + # # Returns an array, e.g.: ['open'] def browser_launcher browser = ENV['BROWSER'] || ( diff --git a/lib/hub/context/git_reader_methods.rb b/lib/hub/context/git_reader_methods.rb new file mode 100644 index 000000000..412547f19 --- /dev/null +++ b/lib/hub/context/git_reader_methods.rb @@ -0,0 +1,16 @@ +module Hub + module Context + # Shells out to git to get output of its commands + module GitReaderMethods + extend Forwardable + + def_delegator :git_reader, :read_config, :git_config + def_delegator :git_reader, :read, :git_command + + def self.extended(base) + base.extend Forwardable + base.def_delegators :'self.class', :git_config, :git_command + end + end + end +end From 400ae3485acf358876e0409c2be2d5d578e7eb64 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:04:13 -0200 Subject: [PATCH 03/13] Extract Hub::Context::System to it's own file --- lib/hub/context.rb | 68 +------------------------------------ lib/hub/context/system.rb | 70 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 67 deletions(-) create mode 100644 lib/hub/context/system.rb diff --git a/lib/hub/context.rb b/lib/hub/context.rb index 78d32e27b..26c0bd151 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -4,6 +4,7 @@ require_relative 'context/git_reader' require_relative 'context/git_reader_methods' +require_relative 'context/system' module Hub # Methods for inspecting the environment, such as reading git config, @@ -384,73 +385,6 @@ def git_editor end end - module System - # Cross-platform web browser command; respects the value set in $BROWSER. - # - # Returns an array, e.g.: ['open'] - def browser_launcher - browser = ENV['BROWSER'] || ( - osx? ? 'open' : windows? ? %w[cmd /c start] : - %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm } - ) - - abort "Please set $BROWSER to a web launcher to use this command." unless browser - Array(browser) - end - - def osx? - require 'rbconfig' - RbConfig::CONFIG['host_os'].to_s.include?('darwin') - end - - def windows? - require 'rbconfig' - RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/ - end - - def unix? - require 'rbconfig' - RbConfig::CONFIG['host_os'] =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i - 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 = "#{path}/#{cmd}#{ext}" - return exe if File.executable? exe - } - end - return nil - end - - # Checks whether a command exists on this system in the $PATH. - # - # name - The String name of the command to check for. - # - # Returns a Boolean. - def command?(name) - !which(name).nil? - end - - def tmp_dir - ENV['TMPDIR'] || ENV['TEMP'] || '/tmp' - end - - def terminal_width - if unix? - width = %x{stty size 2>#{NULL}}.split[1].to_i - width = %x{tput cols 2>#{NULL}}.to_i if width.zero? - else - width = 0 - end - width < 10 ? 78 : width - end - end - include System extend System end diff --git a/lib/hub/context/system.rb b/lib/hub/context/system.rb new file mode 100644 index 000000000..f5ba8d3d1 --- /dev/null +++ b/lib/hub/context/system.rb @@ -0,0 +1,70 @@ +module Hub + module Context + module System + # Cross-platform web browser command; respects the value set in $BROWSER. + # + # Returns an array, e.g.: ['open'] + def browser_launcher + browser = ENV['BROWSER'] || ( + osx? ? 'open' : windows? ? %w[cmd /c start] : + %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm } + ) + + abort "Please set $BROWSER to a web launcher to use this command." unless browser + Array(browser) + end + + def osx? + require 'rbconfig' + RbConfig::CONFIG['host_os'].to_s.include?('darwin') + end + + def windows? + require 'rbconfig' + RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/ + end + + def unix? + require 'rbconfig' + RbConfig::CONFIG['host_os'] =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + 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 = "#{path}/#{cmd}#{ext}" + return exe if File.executable? exe + } + end + return nil + end + + # Checks whether a command exists on this system in the $PATH. + # + # name - The String name of the command to check for. + # + # Returns a Boolean. + def command?(name) + !which(name).nil? + end + + def tmp_dir + ENV['TMPDIR'] || ENV['TEMP'] || '/tmp' + end + + def terminal_width + if unix? + width = %x{stty size 2>#{NULL}}.split[1].to_i + width = %x{tput cols 2>#{NULL}}.to_i if width.zero? + else + width = 0 + end + width < 10 ? 78 : width + end + end + end +end From 97baf2a445798b8eb566ff2c5eee2c649f6a9de2 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:04:46 -0200 Subject: [PATCH 04/13] Use Class.new to create error classes --- lib/hub/context.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/hub/context.rb b/lib/hub/context.rb index 26c0bd151..c57914452 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -14,9 +14,8 @@ module Context NULL = defined?(File::NULL) ? File::NULL : File.exist?('/dev/null') ? '/dev/null' : 'NUL' - class Error < RuntimeError; end - class FatalError < Error; end - + Error = Class.new(RuntimeError) + FatalError = Class.new(Error) private def git_reader From 1227f92d73757f32f547a48c5e01ac247be03130 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:11:48 -0200 Subject: [PATCH 05/13] Extract Hub::Context::LocalRepo to it's own file --- lib/hub/context.rb | 157 +-------------------------------- lib/hub/context/local_repo.rb | 159 ++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 155 deletions(-) create mode 100644 lib/hub/context/local_repo.rb diff --git a/lib/hub/context.rb b/lib/hub/context.rb index c57914452..a9a502017 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -5,6 +5,7 @@ require_relative 'context/git_reader' require_relative 'context/git_reader_methods' require_relative 'context/system' +require_relative 'context/local_repo' module Hub # Methods for inspecting the environment, such as reading git config, @@ -16,6 +17,7 @@ module Context Error = Class.new(RuntimeError) FatalError = Class.new(Error) + private def git_reader @@ -54,161 +56,6 @@ def master_branch end end - class LocalRepo < Struct.new(:git_reader, :dir) - include GitReaderMethods - - def name - if project = main_project - project.name - else - File.basename(dir) - end - end - - def repo_owner - if project = main_project - project.owner - end - end - - def repo_host - project = main_project and project.host - end - - def main_project - remote = origin_remote and remote.project - end - - def upstream_project - if branch = current_branch and upstream = branch.upstream and upstream.remote? - remote = remote_by_name upstream.remote_name - remote.project - end - end - - def current_project - upstream_project || main_project - end - - def current_branch - if branch = git_command('symbolic-ref -q HEAD') - Branch.new self, branch - end - end - - def master_branch - if remote = origin_remote - default_branch = git_command("rev-parse --symbolic-full-name #{remote}") - end - Branch.new(self, default_branch || 'refs/heads/master') - end - - def remotes - @remotes ||= begin - # TODO: is there a plumbing command to get a list of remotes? - list = git_command('remote').to_s.split("\n") - # force "origin" to be first in the list - main = list.delete('origin') and list.unshift(main) - list.map { |name| Remote.new self, name } - end - end - - def remotes_group(name) - git_config "remotes.#{name}" - end - - def origin_remote - remotes.first - end - - def remote_by_name(remote_name) - remotes.find {|r| r.name == remote_name } - end - - def known_hosts - hosts = git_config('hub.host', :all).to_s.split("\n") - hosts << default_host - # support ssh.github.com - # https://help.github.com/articles/using-ssh-over-the-https-port - hosts << "ssh.#{default_host}" - end - - def self.default_host - ENV['GITHUB_HOST'] || main_host - end - - def self.main_host - 'github.com' - end - - extend Forwardable - def_delegators :'self.class', :default_host, :main_host - - def ssh_config - @ssh_config ||= SshConfig.new - end - end - - class GithubProject < Struct.new(:local_repo, :owner, :name, :host) - def self.from_url(url, local_repo) - if local_repo.known_hosts.include? url.host - _, owner, name = url.path.split('/', 4) - GithubProject.new(local_repo, owner, name.sub(/\.git$/, ''), url.host) - end - end - - attr_accessor :repo_data - - def initialize(*args) - super - self.name = self.name.tr(' ', '-') - self.host ||= (local_repo || LocalRepo).default_host - self.host = host.sub(/^ssh\./i, '') if 'ssh.github.com' == host.downcase - end - - def private? - repo_data ? repo_data.fetch('private') : - host != (local_repo || LocalRepo).main_host - end - - def owned_by(new_owner) - new_project = dup - new_project.owner = new_owner - new_project - end - - def name_with_owner - "#{owner}/#{name}" - end - - def ==(other) - name_with_owner == other.name_with_owner - end - - def remote - local_repo.remotes.find { |r| r.project == self } - end - - def web_url(path = nil) - project_name = name_with_owner - if project_name.sub!(/\.wiki$/, '') - unless '/wiki' == path - path = if path =~ %r{^/commits/} then '/_history' - else path.to_s.sub(/\w+/, '_\0') - end - path = '/wiki' + path - end - end - "https://#{host}/" + project_name + path.to_s - end - - def git_url(options = {}) - if options[:https] then "https://#{host}/" - elsif options[:private] or private? then "git@#{host}:" - else "git://#{host}/" - end + name_with_owner + '.git' - end - end class GithubURL < URI::HTTPS extend Forwardable diff --git a/lib/hub/context/local_repo.rb b/lib/hub/context/local_repo.rb new file mode 100644 index 000000000..86627efce --- /dev/null +++ b/lib/hub/context/local_repo.rb @@ -0,0 +1,159 @@ +module Hub + module Context + class LocalRepo < Struct.new(:git_reader, :dir) + include GitReaderMethods + + def name + if project = main_project + project.name + else + File.basename(dir) + end + end + + def repo_owner + if project = main_project + project.owner + end + end + + def repo_host + project = main_project and project.host + end + + def main_project + remote = origin_remote and remote.project + end + + def upstream_project + if branch = current_branch and upstream = branch.upstream and upstream.remote? + remote = remote_by_name upstream.remote_name + remote.project + end + end + + def current_project + upstream_project || main_project + end + + def current_branch + if branch = git_command('symbolic-ref -q HEAD') + Branch.new self, branch + end + end + + def master_branch + if remote = origin_remote + default_branch = git_command("rev-parse --symbolic-full-name #{remote}") + end + Branch.new(self, default_branch || 'refs/heads/master') + end + + def remotes + @remotes ||= begin + # TODO: is there a plumbing command to get a list of remotes? + list = git_command('remote').to_s.split("\n") + # force "origin" to be first in the list + main = list.delete('origin') and list.unshift(main) + list.map { |name| Remote.new self, name } + end + end + + def remotes_group(name) + git_config "remotes.#{name}" + end + + def origin_remote + remotes.first + end + + def remote_by_name(remote_name) + remotes.find {|r| r.name == remote_name } + end + + def known_hosts + hosts = git_config('hub.host', :all).to_s.split("\n") + hosts << default_host + # support ssh.github.com + # https://help.github.com/articles/using-ssh-over-the-https-port + hosts << "ssh.#{default_host}" + end + + def self.default_host + ENV['GITHUB_HOST'] || main_host + end + + def self.main_host + 'github.com' + end + + extend Forwardable + def_delegators :'self.class', :default_host, :main_host + + def ssh_config + @ssh_config ||= SshConfig.new + end + end + + class GithubProject < Struct.new(:local_repo, :owner, :name, :host) + def self.from_url(url, local_repo) + if local_repo.known_hosts.include? url.host + _, owner, name = url.path.split('/', 4) + GithubProject.new(local_repo, owner, name.sub(/\.git$/, ''), url.host) + end + end + + attr_accessor :repo_data + + def initialize(*args) + super + self.name = self.name.tr(' ', '-') + self.host ||= (local_repo || LocalRepo).default_host + self.host = host.sub(/^ssh\./i, '') if 'ssh.github.com' == host.downcase + end + + def private? + repo_data ? repo_data.fetch('private') : + host != (local_repo || LocalRepo).main_host + end + + def owned_by(new_owner) + new_project = dup + new_project.owner = new_owner + new_project + end + + def name_with_owner + "#{owner}/#{name}" + end + + def ==(other) + name_with_owner == other.name_with_owner + end + + def remote + local_repo.remotes.find { |r| r.project == self } + end + + def web_url(path = nil) + project_name = name_with_owner + if project_name.sub!(/\.wiki$/, '') + unless '/wiki' == path + path = if path =~ %r{^/commits/} then '/_history' + else path.to_s.sub(/\w+/, '_\0') + end + path = '/wiki' + path + end + end + "https://#{host}/" + project_name + path.to_s + end + + def git_url(options = {}) + if options[:https] then "https://#{host}/" + elsif options[:private] or private? then "git@#{host}:" + else "git://#{host}/" + end + name_with_owner + '.git' + end + end + end +end From 667a433ba561bec30cfbee4afa20f819a0fc3551 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:15:52 -0200 Subject: [PATCH 06/13] Remove useless begin..end --- lib/hub/context.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/hub/context.rb b/lib/hub/context.rb index a9a502017..261df06a7 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -28,13 +28,12 @@ def git_reader private :git_config, :git_command def local_repo(fatal = true) - @local_repo ||= begin + @local_repo ||= if is_repo? LocalRepo.new git_reader, current_dir elsif fatal raise FatalError, "Not a git repository" end - end end repo_methods = [ @@ -56,7 +55,6 @@ def master_branch end end - class GithubURL < URI::HTTPS extend Forwardable From 43fdaa654489f0c96579742705f0f1705d826463 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:20:24 -0200 Subject: [PATCH 07/13] Extract Hub::Context::GitHubURL to it's own file --- lib/hub/context.rb | 29 +---------------------------- lib/hub/context/github_url.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 lib/hub/context/github_url.rb diff --git a/lib/hub/context.rb b/lib/hub/context.rb index 261df06a7..0628265e0 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -6,6 +6,7 @@ require_relative 'context/git_reader_methods' require_relative 'context/system' require_relative 'context/local_repo' +require_relative 'context/github_url' module Hub # Methods for inspecting the environment, such as reading git config, @@ -55,34 +56,6 @@ def master_branch end end - class GithubURL < URI::HTTPS - extend Forwardable - - attr_reader :project - def_delegator :project, :name, :project_name - def_delegator :project, :owner, :project_owner - - def self.resolve(url, local_repo) - u = URI(url) - if %[http https].include? u.scheme and project = GithubProject.from_url(u, local_repo) - self.new(u.scheme, u.userinfo, u.host, u.port, u.registry, - u.path, u.opaque, u.query, u.fragment, project) - end - rescue URI::InvalidURIError - nil - end - - def initialize(*args) - @project = args.pop - super(*args) - end - - # segment of path after the project owner and name - def project_path - path.split('/', 4)[3] - end - end - class Branch < Struct.new(:local_repo, :name) alias to_s name diff --git a/lib/hub/context/github_url.rb b/lib/hub/context/github_url.rb new file mode 100644 index 000000000..80e7b8a06 --- /dev/null +++ b/lib/hub/context/github_url.rb @@ -0,0 +1,31 @@ +module Hub + module Context + class GithubURL < URI::HTTPS + extend Forwardable + + attr_reader :project + def_delegator :project, :name, :project_name + def_delegator :project, :owner, :project_owner + + def self.resolve(url, local_repo) + u = URI(url) + if %[http https].include? u.scheme and project = GithubProject.from_url(u, local_repo) + self.new(u.scheme, u.userinfo, u.host, u.port, u.registry, + u.path, u.opaque, u.query, u.fragment, project) + end + rescue URI::InvalidURIError + nil + end + + def initialize(*args) + @project = args.pop + super(*args) + end + + # segment of path after the project owner and name + def project_path + path.split('/', 4)[3] + end + end + end +end From 7fb2618258660d12b0ba6eaf9d53053de4ece406 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:39:27 -0200 Subject: [PATCH 08/13] Extract Hub::Context::{Branch,Remote} to it's own file --- lib/hub/context.rb | 74 ++------------------------------------- lib/hub/context/branch.rb | 33 +++++++++++++++++ lib/hub/context/remote.rb | 45 ++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 72 deletions(-) create mode 100644 lib/hub/context/branch.rb create mode 100644 lib/hub/context/remote.rb diff --git a/lib/hub/context.rb b/lib/hub/context.rb index 0628265e0..64b90d9e3 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -7,6 +7,8 @@ require_relative 'context/system' require_relative 'context/local_repo' require_relative 'context/github_url' +require_relative 'context/branch' +require_relative 'context/remote' module Hub # Methods for inspecting the environment, such as reading git config, @@ -56,78 +58,6 @@ def master_branch end end - class Branch < Struct.new(:local_repo, :name) - alias to_s name - - def short_name - name.sub(%r{^refs/(remotes/)?.+?/}, '') - end - - def master? - master_name = if local_repo then local_repo.master_branch.short_name - else 'master' - end - short_name == master_name - end - - def upstream - if branch = local_repo.git_command("rev-parse --symbolic-full-name #{short_name}@{upstream}") - Branch.new local_repo, branch - end - end - - def remote? - name.index('refs/remotes/') == 0 - end - - def remote_name - name =~ %r{^refs/remotes/([^/]+)} and $1 or - raise Error, "can't get remote name from #{name.inspect}" - end - end - - class Remote < Struct.new(:local_repo, :name) - alias to_s name - - def ==(other) - other.respond_to?(:to_str) ? name == other.to_str : super - end - - def project - urls.each_value { |url| - if valid = GithubProject.from_url(url, local_repo) - return valid - end - } - nil - end - - def urls - return @urls if defined? @urls - @urls = {} - local_repo.git_command('remote -v').to_s.split("\n").map do |line| - next if line !~ /^(.+?)\t(.+) \((.+)\)$/ - remote, uri, type = $1, $2, $3 - next if remote != self.name - if uri =~ %r{^[\w-]+://} or uri =~ %r{^([^/]+?):} - uri = "ssh://#{$1}/#{$'}" if $1 - begin - @urls[type] = uri_parse(uri) - rescue URI::InvalidURIError - end - end - end - @urls - end - - def uri_parse uri - uri = URI.parse uri - uri.host = local_repo.ssh_config.get_value(uri.host, 'hostname') { uri.host } - uri.user = local_repo.ssh_config.get_value(uri.host, 'user') { uri.user } - uri - end - end - ## helper methods for local repo, GH projects def github_project(name, owner = nil) diff --git a/lib/hub/context/branch.rb b/lib/hub/context/branch.rb new file mode 100644 index 000000000..711aa62fe --- /dev/null +++ b/lib/hub/context/branch.rb @@ -0,0 +1,33 @@ +module Hub + module Context + class Branch < Struct.new(:local_repo, :name) + alias to_s name + + def short_name + name.sub(%r{^refs/(remotes/)?.+?/}, '') + end + + def master? + master_name = if local_repo then local_repo.master_branch.short_name + else 'master' + end + short_name == master_name + end + + def upstream + if branch = local_repo.git_command("rev-parse --symbolic-full-name #{short_name}@{upstream}") + Branch.new local_repo, branch + end + end + + def remote? + name.index('refs/remotes/') == 0 + end + + def remote_name + name =~ %r{^refs/remotes/([^/]+)} and $1 or + raise Error, "can't get remote name from #{name.inspect}" + end + end + end +end diff --git a/lib/hub/context/remote.rb b/lib/hub/context/remote.rb new file mode 100644 index 000000000..5f6ffa511 --- /dev/null +++ b/lib/hub/context/remote.rb @@ -0,0 +1,45 @@ +module Hub + module Context + class Remote < Struct.new(:local_repo, :name) + alias to_s name + + def ==(other) + other.respond_to?(:to_str) ? name == other.to_str : super + end + + def project + urls.each_value { |url| + if valid = GithubProject.from_url(url, local_repo) + return valid + end + } + nil + end + + def urls + return @urls if defined? @urls + @urls = {} + local_repo.git_command('remote -v').to_s.split("\n").map do |line| + next if line !~ /^(.+?)\t(.+) \((.+)\)$/ + remote, uri, type = $1, $2, $3 + next if remote != self.name + if uri =~ %r{^[\w-]+://} or uri =~ %r{^([^/]+?):} + uri = "ssh://#{$1}/#{$'}" if $1 + begin + @urls[type] = uri_parse(uri) + rescue URI::InvalidURIError + end + end + end + @urls + end + + def uri_parse uri + uri = URI.parse uri + uri.host = local_repo.ssh_config.get_value(uri.host, 'hostname') { uri.host } + uri.user = local_repo.ssh_config.get_value(uri.host, 'user') { uri.user } + uri + end + end + end +end From 7193ae3b12e838c706defc9fb636ee8bf115ddb0 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:40:09 -0200 Subject: [PATCH 09/13] Refactor Hub::Context::System style --- lib/hub/context/system.rb | 51 ++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/hub/context/system.rb b/lib/hub/context/system.rb index f5ba8d3d1..8d1cd5091 100644 --- a/lib/hub/context/system.rb +++ b/lib/hub/context/system.rb @@ -1,32 +1,42 @@ +require 'rbconfig' + module Hub module Context module System + GENERAL_BROWSERS = + %w(xdg-open cygstart x-www-browser firefox opera mozilla netscape) + # Cross-platform web browser command; respects the value set in $BROWSER. # # Returns an array, e.g.: ['open'] def browser_launcher - browser = ENV['BROWSER'] || ( - osx? ? 'open' : windows? ? %w[cmd /c start] : - %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm } - ) + browser = + ENV['BROWSER'] || + case + when osx? then 'open' + when windows? then %w(cmd /c start) + else + GENERAL_BROWSERS.find { |command| which command } + end + + unless browser + abort 'Please set $BROWSER to a web launcher to use this command.' + end - abort "Please set $BROWSER to a web launcher to use this command." unless browser Array(browser) end def osx? - require 'rbconfig' RbConfig::CONFIG['host_os'].to_s.include?('darwin') end def windows? - require 'rbconfig' RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/ end def unix? - require 'rbconfig' - RbConfig::CONFIG['host_os'] =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + RbConfig::CONFIG['host_os'] =~ + /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i end # Cross-platform way of finding an executable in the $PATH. @@ -34,13 +44,14 @@ def unix? # 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 = "#{path}/#{cmd}#{ext}" + exts.each do |ext| + exe = "#{ path }/#{ cmd }#{ ext }" + return exe if File.executable? exe - } + end end - return nil end # Checks whether a command exists on this system in the $PATH. @@ -57,12 +68,14 @@ def tmp_dir end def terminal_width - if unix? - width = %x{stty size 2>#{NULL}}.split[1].to_i - width = %x{tput cols 2>#{NULL}}.to_i if width.zero? - else - width = 0 - end + width = + if unix? + %x(stty size 2>#{ NULL }).split[1].to_i.nonzero? || + %x(tput cols 2>#{ NULL }).to_i + else + 0 + end + width < 10 ? 78 : width end end From d5ac4b44f79b2ffdb4e53c5eff730998b3b7cb0a Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Tue, 17 Dec 2013 23:50:33 -0200 Subject: [PATCH 10/13] Remove trailing whitespace --- lib/hub/runner.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/hub/runner.rb b/lib/hub/runner.rb index b5ad9501f..841a73f42 100644 --- a/lib/hub/runner.rb +++ b/lib/hub/runner.rb @@ -6,7 +6,7 @@ module Hub # augment a git command, is kept in the `Hub::Commands` module. class Runner attr_reader :args - + def initialize(*args) @args = Args.new(args) Commands.run(@args) @@ -39,8 +39,8 @@ def commands end # Runs the target git command with an optional callback. Replaces - # the current process. - # + # the current process. + # # If `args` is empty, this will skip calling the git command. This # allows commands to print an error message and cancel their own # execution if they don't make sense. From 2a6ebb5a9a2c51bb74c413569dded3202f6dfb04 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Wed, 18 Dec 2013 00:10:59 -0200 Subject: [PATCH 11/13] Lightly refactor Hub::Args --- lib/hub/args.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/hub/args.rb b/lib/hub/args.rb index 1fee70de6..9d6a65167 100644 --- a/lib/hub/args.rb +++ b/lib/hub/args.rb @@ -1,3 +1,5 @@ +require 'delegate' + module Hub # The Args class exists to make it more convenient to work with # command line arguments intended for git from within the Hub @@ -5,12 +7,13 @@ module Hub # # The ARGV array is converted into an Args instance by the Hub # instance when instantiated. - class Args < Array + class Args < SimpleDelegator attr_accessor :executable def initialize(*args) super - @executable = ENV["GIT"] || "git" + + @executable = ENV['GIT'] || 'git' @skip = @noop = false @original_args = args.first @chain = [nil] @@ -19,18 +22,19 @@ def initialize(*args) # Adds an `after` callback. # A callback can be a command or a proc. def after(cmd_or_args = nil, args = nil, &block) - @chain.insert(-1, normalize_callback(cmd_or_args, args, block)) + @chain << normalize_callback(cmd_or_args, args, block) end # Adds a `before` callback. # A callback can be a command or a proc. def before(cmd_or_args = nil, args = nil, &block) - @chain.insert(@chain.index(nil), normalize_callback(cmd_or_args, args, block)) + @chain.insert( + @chain.index(nil), normalize_callback(cmd_or_args, args, block)) end # Tells if there are multiple (chained) commands or not. def chained? - @chain.size > 1 + @chain.any? end # Returns an array of all commands. @@ -89,11 +93,12 @@ def flags # Tests if arguments were modified since instantiation def changed? - chained? or self != @original_args + chained? || self != @original_args end def has_flag?(*flags) - pattern = flags.flatten.map { |f| Regexp.escape(f) }.join('|') + pattern = flags.flat_map { |f| Regexp.escape(f) }.join('|') + !grep(/^#{pattern}(?:=|$)/).empty? end @@ -109,7 +114,7 @@ def normalize_callback(cmd_or_args, args, block) elsif cmd_or_args cmd_or_args else - raise ArgumentError, "command or block required" + raise ArgumentError, 'command or block required' end end end From 92022b647ff56b9571c35b3d0df63518cc794b3c Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Wed, 18 Dec 2013 02:05:32 -0200 Subject: [PATCH 12/13] Extract Hub::Context::GitHubProject to it's own file --- lib/hub/context.rb | 5 ++- lib/hub/context/github_project.rb | 66 +++++++++++++++++++++++++++++++ lib/hub/context/github_url.rb | 4 +- lib/hub/context/local_repo.rb | 61 ---------------------------- lib/hub/context/remote.rb | 2 +- 5 files changed, 72 insertions(+), 66 deletions(-) create mode 100644 lib/hub/context/github_project.rb diff --git a/lib/hub/context.rb b/lib/hub/context.rb index 64b90d9e3..7e7552322 100644 --- a/lib/hub/context.rb +++ b/lib/hub/context.rb @@ -7,6 +7,7 @@ require_relative 'context/system' require_relative 'context/local_repo' require_relative 'context/github_url' +require_relative 'context/github_project' require_relative 'context/branch' require_relative 'context/remote' @@ -76,7 +77,7 @@ def github_project(name, owner = nil) project.name = name project else - GithubProject.new(local_repo(false), owner, name) + GitHubProject.new(local_repo(false), owner, name) end end @@ -86,7 +87,7 @@ def git_url(owner = nil, name = nil, options = {}) end def resolve_github_url(url) - GithubURL.resolve(url, local_repo) if url =~ /^https?:/ + GitHubURL.resolve(url, local_repo) if url =~ /^https?:/ end # legacy setting diff --git a/lib/hub/context/github_project.rb b/lib/hub/context/github_project.rb new file mode 100644 index 000000000..2c22825eb --- /dev/null +++ b/lib/hub/context/github_project.rb @@ -0,0 +1,66 @@ +module Hub + module Context + class GitHubProject < Struct.new(:local_repo, :owner, :name, :host) + def self.from_url(url, local_repo) + if local_repo.known_hosts.include? url.host + _, owner, name = url.path.split('/', 4) + + new(local_repo, owner, name.sub(/\.git$/, ''), url.host) + end + end + + attr_accessor :repo_data + + def initialize(*args) + super + + self.name = self.name.tr(' ', '-') + self.host ||= (local_repo || LocalRepo).default_host + self.host = host.sub(/^ssh\./i, '') if 'ssh.github.com' == host.downcase + end + + def private? + repo_data ? repo_data.fetch('private') : + host != (local_repo || LocalRepo).main_host + end + + def owned_by(new_owner) + new_project = dup + new_project.owner = new_owner + new_project + end + + def name_with_owner + "#{owner}/#{name}" + end + + def ==(other) + name_with_owner == other.name_with_owner + end + + def remote + local_repo.remotes.find { |r| r.project == self } + end + + def web_url(path = nil) + project_name = name_with_owner + if project_name.sub!(/\.wiki$/, '') + unless '/wiki' == path + path = if path =~ %r{^/commits/} then '/_history' + else path.to_s.sub(/\w+/, '_\0') + end + path = '/wiki' + path + end + end + "https://#{host}/" + project_name + path.to_s + end + + def git_url(options = {}) + if options[:https] then "https://#{host}/" + elsif options[:private] or private? then "git@#{host}:" + else "git://#{host}/" + end + name_with_owner + '.git' + end + end + end +end diff --git a/lib/hub/context/github_url.rb b/lib/hub/context/github_url.rb index 80e7b8a06..1e86881a8 100644 --- a/lib/hub/context/github_url.rb +++ b/lib/hub/context/github_url.rb @@ -1,6 +1,6 @@ module Hub module Context - class GithubURL < URI::HTTPS + class GitHubURL < URI::HTTPS extend Forwardable attr_reader :project @@ -9,7 +9,7 @@ class GithubURL < URI::HTTPS def self.resolve(url, local_repo) u = URI(url) - if %[http https].include? u.scheme and project = GithubProject.from_url(u, local_repo) + if %[http https].include? u.scheme and project = GitHubProject.from_url(u, local_repo) self.new(u.scheme, u.userinfo, u.host, u.port, u.registry, u.path, u.opaque, u.query, u.fragment, project) end diff --git a/lib/hub/context/local_repo.rb b/lib/hub/context/local_repo.rb index 86627efce..9cd140f45 100644 --- a/lib/hub/context/local_repo.rb +++ b/lib/hub/context/local_repo.rb @@ -94,66 +94,5 @@ def ssh_config @ssh_config ||= SshConfig.new end end - - class GithubProject < Struct.new(:local_repo, :owner, :name, :host) - def self.from_url(url, local_repo) - if local_repo.known_hosts.include? url.host - _, owner, name = url.path.split('/', 4) - GithubProject.new(local_repo, owner, name.sub(/\.git$/, ''), url.host) - end - end - - attr_accessor :repo_data - - def initialize(*args) - super - self.name = self.name.tr(' ', '-') - self.host ||= (local_repo || LocalRepo).default_host - self.host = host.sub(/^ssh\./i, '') if 'ssh.github.com' == host.downcase - end - - def private? - repo_data ? repo_data.fetch('private') : - host != (local_repo || LocalRepo).main_host - end - - def owned_by(new_owner) - new_project = dup - new_project.owner = new_owner - new_project - end - - def name_with_owner - "#{owner}/#{name}" - end - - def ==(other) - name_with_owner == other.name_with_owner - end - - def remote - local_repo.remotes.find { |r| r.project == self } - end - - def web_url(path = nil) - project_name = name_with_owner - if project_name.sub!(/\.wiki$/, '') - unless '/wiki' == path - path = if path =~ %r{^/commits/} then '/_history' - else path.to_s.sub(/\w+/, '_\0') - end - path = '/wiki' + path - end - end - "https://#{host}/" + project_name + path.to_s - end - - def git_url(options = {}) - if options[:https] then "https://#{host}/" - elsif options[:private] or private? then "git@#{host}:" - else "git://#{host}/" - end + name_with_owner + '.git' - end - end end end diff --git a/lib/hub/context/remote.rb b/lib/hub/context/remote.rb index 5f6ffa511..123dff10d 100644 --- a/lib/hub/context/remote.rb +++ b/lib/hub/context/remote.rb @@ -9,7 +9,7 @@ def ==(other) def project urls.each_value { |url| - if valid = GithubProject.from_url(url, local_repo) + if valid = GitHubProject.from_url(url, local_repo) return valid end } From d9a153b29cd2500a332502cab0fc7246ced2be87 Mon Sep 17 00:00:00 2001 From: Fuad Saud Date: Wed, 18 Dec 2013 02:18:49 -0200 Subject: [PATCH 13/13] Extract Hub::SSHConfig::HostnamePattern to it's own file and refactor it --- lib/hub/context/local_repo.rb | 2 +- lib/hub/ssh_config.rb | 61 +++++++------------------- lib/hub/ssh_config/hostname_pattern.rb | 40 +++++++++++++++++ test/hub_test.rb | 4 +- 4 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 lib/hub/ssh_config/hostname_pattern.rb diff --git a/lib/hub/context/local_repo.rb b/lib/hub/context/local_repo.rb index 9cd140f45..053de6286 100644 --- a/lib/hub/context/local_repo.rb +++ b/lib/hub/context/local_repo.rb @@ -91,7 +91,7 @@ def self.main_host def_delegators :'self.class', :default_host, :main_host def ssh_config - @ssh_config ||= SshConfig.new + @ssh_config ||= SSHConfig.new end end end diff --git a/lib/hub/ssh_config.rb b/lib/hub/ssh_config.rb index d454ee1e2..9943a4943 100644 --- a/lib/hub/ssh_config.rb +++ b/lib/hub/ssh_config.rb @@ -1,13 +1,17 @@ +require_relative 'ssh_config/hostname_pattern' + module Hub # Reads ssh configuration files and records each setting under its host # pattern so it can be looked up by hostname. - class SshConfig + class SSHConfig CONFIG_FILES = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config) - def initialize files = nil - @settings = Hash.new {|h,k| h[k] = {} } + def initialize(files = nil) + @settings = Hash.new { |h, k| h[k] = {} } + Array(files || CONFIG_FILES).each do |path| file = File.expand_path path + parse_file file if File.readable?(file) end end @@ -15,56 +19,25 @@ def initialize files = nil # Public: Get a setting as it would apply to a specific hostname. # # Yields if not found. - def get_value hostname, key + def get_value(hostname, key) key = key.to_s.downcase - @settings.each do |pattern, settings| - if pattern.match? hostname and found = settings[key] - return found - end - end - yield - end - class HostPattern - def initialize pattern - @pattern = pattern.to_s.downcase - end - - def to_s() @pattern end - def ==(other) other.to_s == self.to_s end - - def matcher - @matcher ||= - if '*' == @pattern - Proc.new { true } - elsif @pattern !~ /[?*]/ - lambda { |hostname| hostname.to_s.downcase == @pattern } - else - re = self.class.pattern_to_regexp @pattern - lambda { |hostname| re =~ hostname } - end - end - - def match? hostname - matcher.call hostname + @settings.each do |pattern, settings| + return settings[key] if pattern.match?(hostname) && settings[key] end - def self.pattern_to_regexp pattern - escaped = Regexp.escape(pattern) - escaped.gsub!('\*', '.*') - escaped.gsub!('\?', '.') - /^#{escaped}$/i - end + yield end - def parse_file file + def parse_file(file) host_patterns = [HostPattern.new('*')] IO.foreach(file) do |line| case line - when /^\s*(#|$)/ then next + when /^\s*(#|$)/ then + next when /^\s*(\S+)\s*=/ - key, value = $1, $' + key, value = Regexp.last_match(1), $' else key, value = line.strip.split(/\s+/, 2) end @@ -75,14 +48,14 @@ def parse_file file value.chomp! if 'host' == key - host_patterns = value.split(/\s+/).map {|p| HostPattern.new p } + host_patterns = value.split(/\s+/).map { |p| HostPattern.new p } else record_setting key, value, host_patterns end end end - def record_setting key, value, patterns + def record_setting(key, value, patterns) patterns.each do |pattern| @settings[pattern][key] ||= value end diff --git a/lib/hub/ssh_config/hostname_pattern.rb b/lib/hub/ssh_config/hostname_pattern.rb new file mode 100644 index 000000000..d5ea3b9b2 --- /dev/null +++ b/lib/hub/ssh_config/hostname_pattern.rb @@ -0,0 +1,40 @@ +module Hub + class SSHConfig + class HostPattern + def initialize(pattern) + @pattern = pattern.to_s.downcase + end + + def to_s + @pattern + end + + def ==(other) + other.to_s == to_s + end + + def matcher + @matcher ||= + if @pattern == '*' + ->(*) { true } + elsif @pattern !~ /[?*]/ + ->(hostname) { hostname.to_s.downcase == @pattern } + else + re = self.class.pattern_to_regexp @pattern + ->(hostname) { re =~ hostname } + end + end + + def match?(hostname) + matcher.call(hostname) + end + + def self.pattern_to_regexp(pattern) + escaped = Regexp.escape(pattern) + escaped.gsub!('\*', '.*') + escaped.gsub!('\?', '.') + /^#{escaped}$/i + end + end + end +end diff --git a/test/hub_test.rb b/test/hub_test.rb index 3bd710c02..1033a05f0 100644 --- a/test/hub_test.rb +++ b/test/hub_test.rb @@ -45,7 +45,7 @@ def setup super COMMANDS.replace %w[open groff] Hub::Context::PWD.replace '/path/to/hub' - Hub::SshConfig::CONFIG_FILES.replace [] + Hub::SSHConfig::CONFIG_FILES.replace [] @prompt_stubs = prompt_stubs = [] @password_prompt_stubs = password_prompt_stubs = [] @@ -546,7 +546,7 @@ def with_ssh_config content config_file.close begin - Hub::SshConfig::CONFIG_FILES.replace [config_file.path] + Hub::SSHConfig::CONFIG_FILES.replace [config_file.path] yield ensure config_file.unlink