The Commands module houses the git commands that hub lovingly wraps. If a method exists here, it is expected to have a corresponding git command which either gets run before or after the method executes.
The typical flow is as follows:
hub is invoked from the command line: $ hub clone rtomayko/tilt
The Runner class is initialized: >> Hub::Runner.new('clone', 'rtomayko/tilt')
The method representing the git subcommand is executed with the full args: >> #clone(['clone', 'rtomayko/tilt'])
That method rewrites the args as it sees fit: >> args = “git://github.com/” + args + “.git”
The new args are used to run `git`: >> exec “git”, “clone”, “git://github.com/rtomayko/tilt.git”
An optional `after` callback can be set. If so, it is run after step 5 (which then performs a `system` call rather than an `exec`). See `Hub::Args` for more information on the `after` callback.
# File lib/hub/commands.rb, line 761 def alias(args) shells = %w[bash zsh sh ksh csh fish] script = !!args.delete('-s') shell = args[1] || ENV['SHELL'] abort "hub alias: unknown shell" if shell.nil? or shell.empty? shell = File.basename shell unless shells.include? shell $stderr.puts "hub alias: unsupported shell" warn "supported shells: #{shells.join(' ')}" abort end if script puts "alias git=hub" else profile = case shell when 'bash' then '~/.bash_profile' when 'zsh' then '~/.zshrc' when 'ksh' then '~/.profile' when 'fish' then '~/.config/fish/config.fish' else 'your profile' end puts "# Wrap git automatically by adding the following to #{profile}:" puts if shell == 'fish' puts 'eval (hub alias -s)' else puts 'eval "$(hub alias -s)"' end end exit end
$ hub am github.com/defunkt/hub/pull/55 … downloads patch via API … > git am /tmp/55.patch
# File lib/hub/commands.rb, line 500 def am(args) if url = args.find { |a| a =~ %r{^https?://(gist\.)?github\.com/} } idx = args.index(url) if $1 == 'gist.' path_parts = $'.sub(/#.*/, '').split('/') gist_id = path_parts.last patch_name = "gist-#{gist_id}.txt" patch = api_client.gist_raw(gist_id) else gh_url = resolve_github_url(url) case gh_url.project_path when /^pull\/(\d+)/ pull_id = $1.to_i patch_name = "#{pull_id}.patch" patch = api_client.pullrequest_patch(gh_url.project, pull_id) when /^commit\/([a-f0-9]{7,40})/ commit_sha = $1 patch_name = "#{commit_sha}.patch" patch = api_client.commit_patch(gh_url.project, commit_sha) else raise ArgumentError, url end end patch_file = Tempfile.new(patch_name).path File.open(patch_file, 'w') { |file| file.write(patch) } args[idx] = patch_file end end
$ hub apply github.com/defunkt/hub/pull/55 … downloads patch via API … > git apply /tmp/55.patch
$ hub browse > open github.com/CURRENT_REPO
$ hub browse – issues > open github.com/CURRENT_REPO/issues
$ hub browse pjhyett/github-services > open github.com/pjhyett/github-services
$ hub browse github-services > open github.com/YOUR_LOGIN/github-services
$ hub browse github-services wiki > open github.com/YOUR_LOGIN/github-services/wiki
# File lib/hub/commands.rb, line 673 def browse(args) args.shift browse_command(args) do dest = args.shift dest = nil if dest == '--' # $ hub browse -- wiki subpage = args.shift if dest # $ hub browse pjhyett/github-services # $ hub browse github-services project = github_project dest branch = master_branch elsif subpage && !%w[commits tree blob settings].include?(subpage) branch = master_branch project = local_repo.main_project else # $ hub browse prefer_upstream = current_branch.master? branch, project = remote_branch_and_project(method(:github_user), prefer_upstream) branch ||= master_branch end abort "Usage: hub browse [<USER>/]<REPOSITORY>" unless project path = case subpage when 'commits' "/commits/#{branch_in_url(branch)}" when 'tree', NilClass "/tree/#{branch_in_url(branch)}" if branch and !branch.master? else "/#{subpage}" end project.web_url(path, api_client.config.method(:protocol)) end end
$ git checkout github.com/defunkt/hub/pull/73 > git remote add -f -t feature git://github:com/mislav/hub.git > git checkout –track -B mislav-feature mislav/feature
# File lib/hub/commands.rb, line 411 def checkout(args) _, url_arg, new_branch_name = args.words if url = resolve_github_url(url_arg) and url.project_path =~ /^pull\/(\d+)/ pull_id = $1 pull_data = api_client.pullrequest_info(url.project, pull_id) args.delete new_branch_name user, branch = pull_data['head']['label'].split(':', 2) abort "Error: #{user}'s fork is not available anymore" unless pull_data['head']['repo'] new_branch_name ||= "#{user}-#{branch}" if remotes.include? user args.before ['remote', 'set-branches', '--add', user, branch] args.before ['fetch', user, "+refs/heads/#{branch}:refs/remotes/#{user}/#{branch}"] else url = github_project(url.project_name, user).git_url(:private => pull_data['head']['repo']['private'], :https => https_protocol?) args.before ['remote', 'add', '-f', '-t', branch, user, url] end idx = args.index url_arg args.delete_at idx args.insert idx, '--track', '-B', new_branch_name, "#{user}/#{branch}" end end
$ git cherry-pick github.com/mislav/hub/commit/a319d88#comments > git remote add -f mislav git://github.com/mislav/hub.git > git cherry-pick a319d88
$ git cherry-pick mislav@a319d88 > git remote add -f mislav git://github.com/mislav/hub.git > git cherry-pick a319d88
$ git cherry-pick mislav@SHA > git fetch mislav > git cherry-pick SHA
# File lib/hub/commands.rb, line 474 def cherry_pick(args) unless args.include?('-m') or args.include?('--mainline') ref = args.words.last if url = resolve_github_url(ref) and url.project_path =~ /^commit\/([a-f0-9]{7,40})/ sha = $1 project = url.project elsif ref =~ /^(#{OWNER_RE})@([a-f0-9]{7,40})$/ owner, sha = $1, $2 project = local_repo.main_project.owned_by(owner) end if project args[args.index(ref)] = sha if remote = project.remote and remotes.include? remote args.before ['fetch', remote.to_s] else args.before ['remote', 'add', '-f', project.owner, project.git_url(:https => https_protocol?)] end end end end
$ hub ci-status $ hub ci-status 6f6d9797f9d6e56c3da623a97cfc3f45daf9ae5f $ hub ci-status master $ hub ci-status origin/master
# File lib/hub/commands.rb, line 80 def ci_status(args) args.shift ref = args.words.first || 'HEAD' verbose = args.include?('-v') unless project = local_repo.main_project abort "Aborted: the origin remote doesn't point to a GitHub repository." end unless sha = local_repo.git_command("rev-parse -q #{ref}") abort "Aborted: no revision could be determined from '#{ref}'" end statuses = api_client.statuses(project, sha) status = statuses.first if status ref_state = status['state'] ref_target_url = status['target_url'] else ref_state = 'no status' ref_target_url = nil end exit_code = case ref_state when 'success' then 0 when 'failure', 'error' then 1 when 'pending' then 2 else 3 end if verbose and ref_target_url $stdout.puts "%s: %s" % [ref_state, ref_target_url] else $stdout.puts ref_state end exit exit_code rescue GitHubAPI::Exceptions response = $!.response display_api_exception("fetching CI status", response) exit 1 end
$ hub clone rtomayko/tilt > git clone git://github.com/rtomayko/tilt.
$ hub clone -p kneath/hemingway > git clone git@github.com:kneath/hemingway.git
$ hub clone tilt > git clone git://github.com/YOUR_LOGIN/tilt.
$ hub clone -p github > git clone git@github.com:YOUR_LOGIN/hemingway.git
# File lib/hub/commands.rb, line 281 def clone(args) ssh = args.delete('-p') has_values = /^(--(upload-pack|template|depth|origin|branch|reference|name)|-[ubo])$/ idx = 1 while idx < args.length arg = args[idx] if arg.index('-') == 0 idx += 1 if arg =~ has_values else # $ hub clone rtomayko/tilt # $ hub clone tilt if arg =~ NAME_WITH_OWNER_RE and !File.directory?(arg) name, owner = arg, nil owner, name = name.split('/', 2) if name.index('/') project = github_project(name, owner || github_user) unless ssh || args[0] == 'submodule' || args.noop? || https_protocol? repo_info = api_client.repo_info(project) ssh = repo_info.success? && (repo_info.data['private'] || repo_info.data['permissions']['push']) end args[idx] = project.git_url(:private => ssh, :https => https_protocol?) end break end idx += 1 end end
$ hub compare 1.0..fix > open github.com/CURRENT_REPO/compare/1.0…fix $ hub compare refactor > open github.com/CURRENT_REPO/compare/refactor $ hub compare myfork feature > open github.com/myfork/REPO/compare/feature $ hub compare -u 1.0…2.0 “github.com/CURRENT_REPO/compare/1.0…2.0”
# File lib/hub/commands.rb, line 719 def compare(args) args.shift browse_command(args) do branch, project = remote_branch_and_project(method(:github_user)) if args.empty? if branch and not branch.master? range = branch.short_name else abort "Usage: hub compare [USER] [<START>...]<END>" end else sha_or_tag = /((?:#{OWNER_RE}:)?\w[\w.-]+\w)/ # replaces two dots with three: "sha1...sha2" range = args.pop.sub(/^#{sha_or_tag}\.\.#{sha_or_tag}$/, '\1...\2') if owner = args.pop project = project.owned_by(owner) end end path = '/compare/%s' % range.tr('/', ';') project.web_url(path, api_client.config.method(:protocol)) end end
$ hub create … create repo on github … > git remote add -f origin git@github.com:YOUR_USER/CURRENT_REPO.git
# File lib/hub/commands.rb, line 584 def create(args) if !is_repo? abort "'create' must be run from inside a git repository" else owner = github_user args.shift options = {} options[:private] = true if args.delete('-p') new_repo_name = nil until args.empty? case arg = args.shift when '-d' options[:description] = args.shift when '-h' options[:homepage] = args.shift else if arg =~ /^[^-]/ and new_repo_name.nil? new_repo_name = arg owner, new_repo_name = new_repo_name.split('/', 2) if new_repo_name.index('/') else abort "invalid argument: #{arg}" end end end new_repo_name ||= repo_name new_project = github_project(new_repo_name, owner) if api_client.repo_exists?(new_project) warn "#{new_project.name_with_owner} already exists on #{new_project.host}" action = "set remote origin" else action = "created repository" unless args.noop? repo_data = api_client.create_repo(new_project, options) new_project = github_project(repo_data['full_name']) end end url = new_project.git_url(:private => true, :https => https_protocol?) if remotes.first != 'origin' args.replace %Wremote add -f origin #{url}" else args.replace %Wremote -v" end args.after 'echo', ["#{action}:", new_project.name_with_owner] end rescue GitHubAPI::Exceptions display_api_exception("creating repository", $!.response) exit 1 end
$ hub e-note $ hub e-note “My humble contribution” $ hub e-note -i 92 $ hub e-note github.com/rtomayko/tilt/issues/92
$ hub fetch mislav > git remote add mislav git://github.com/mislav/REPO.git > git fetch mislav
$ hub fetch –multiple mislav xoebus > git remote add mislav … > git remote add xoebus … > git fetch –multiple mislav xoebus
# File lib/hub/commands.rb, line 370 def fetch(args) # $ hub fetch --multiple <name1>, <name2>, ... if args.include?('--multiple') names = args.words[1..-1] # $ hub fetch <name> elsif remote_name = args.words[1] # $ hub fetch <name1>,<name2>,... if remote_name =~ /^\w+(,\w+)+$/ index = args.index(remote_name) args.delete(remote_name) names = remote_name.split(',') args.insert(index, *names) args.insert(index, '--multiple') else names = [remote_name] end else names = [] end projects = names.map { |name| unless name !~ /^#{OWNER_RE}$/ or remotes.include?(name) or remotes_group(name) project = github_project(nil, name) repo_info = api_client.repo_info(project) if repo_info.success? project.repo_data = repo_info.data project end end }.compact if projects.any? projects.each do |project| args.before ['remote', 'add', project.owner, project.git_url(:https => https_protocol?)] end end end
$ hub fork … hardcore forking action … > git remote add -f YOUR_USER git@github.com:YOUR_USER/CURRENT_REPO.git
# File lib/hub/commands.rb, line 549 def fork(args) unless project = local_repo.main_project abort "Error: repository under 'origin' remote is not a GitHub project" end forked_project = project.owned_by(github_user(project.host)) existing_repo = api_client.repo_info(forked_project) if existing_repo.success? parent_data = existing_repo.data['parent'] parent_url = parent_data && resolve_github_url(parent_data['html_url']) if !parent_url or parent_url.project != project abort "Error creating fork: %s already exists on %s" % [ forked_project.name_with_owner, forked_project.host ] end else api_client.fork_repo(project) unless args.noop? end if args.include?('--no-remote') exit else origin_url = project.remote.github_url url = forked_project.git_url(:private => true, :https => https_protocol?) args.replace %Wremote add -f #{forked_project.owner} #{origin_url}" args.after %Wremote set-url #{forked_project.owner} #{url}" args.after 'echo', ['new remote:', forked_project.owner] end rescue GitHubAPI::Exceptions display_api_exception("creating fork", $!.response) exit 1 end
$ hub help (print improved help text)
# File lib/hub/commands.rb, line 809 def help(args) command = args.words[1] if command == 'hub' || custom_command?(command) puts hub_manpage exit elsif command.nil? if args.has_flag?('-a', '--all') # Add the special hub commands to the end of "git help -a" output. args.after 'echo', ["\nhub custom commands\n"] args.after 'echo', CUSTOM_COMMANDS.map {|cmd| " #{cmd}" } else ENV['GIT_PAGER'] = '' unless args.has_flag?('-p', '--paginate') # Use `cat`. puts improved_help_text exit end end end
$ hub hub standalone Prints the “standalone” version of hub for an easy, memorable installation sequence:
$ gem install hub $ hub hub standalone > ~/bin/hub && chmod 755 ~/bin/hub $ gem uninstall hub
# File lib/hub/commands.rb, line 750 def hub(args) return help(args) unless args[1] == 'standalone' require 'hub/standalone' Hub::Standalone.build $stdout exit rescue LoadError abort "hub is already running in standalone mode." rescue Errno::EPIPE exit # ignore broken pipe end
$ hub init -g > git init > git remote add origin git@github.com:USER/REPO.git
# File lib/hub/commands.rb, line 538 def init(args) if args.delete('-g') project = github_project(File.basename(current_dir)) url = project.git_url(:private => true, :https => https_protocol?) args.after ['remote', 'add', 'origin', url] end end
$ git merge github.com/defunkt/hub/pull/73 > git fetch git://github.com/mislav/hub.git +refs/heads/feature:refs/remotes/mislav/feature > git merge mislav/feature –no-ff -m 'Merge pull request #73 from mislav/feature…'
# File lib/hub/commands.rb, line 439 def merge(args) _, url_arg = args.words if url = resolve_github_url(url_arg) and url.project_path =~ /^pull\/(\d+)/ pull_id = $1 pull_data = api_client.pullrequest_info(url.project, pull_id) user, branch = pull_data['head']['label'].split(':', 2) abort "Error: #{user}'s fork is not available anymore" unless pull_data['head']['repo'] repo_name = pull_data['head']['repo']['name'] url = github_project(repo_name, user).git_url(:private => pull_data['head']['repo']['private'], :https => https_protocol?) merge_head = "#{user}/#{branch}" args.before ['fetch', url, "+refs/heads/#{branch}:refs/remotes/#{merge_head}"] idx = args.index url_arg args.delete_at idx args.insert idx, merge_head, '-m', "Merge pull request ##{pull_id} from #{merge_head}\n\n#{pull_data['title']}" idx = args.index '-m' args.insert idx, '--no-ff' unless args.include?('--ff-only') end end
$ hub pull-request $ hub pull-request “My humble contribution” $ hub pull-request github.com/rtomayko/tilt/issues/92
# File lib/hub/commands.rb, line 125 def pull_request(args) args.shift options = { } force = explicit_owner = false base_project = local_repo.main_project tracked_branch, head_project = remote_branch_and_project(method(:github_user)) unless current_branch abort "Aborted: not currently on any branch." end unless base_project abort "Aborted: the origin remote doesn't point to a GitHub repository." end from_github_ref = lambda do |ref, context_project| if ref.index(':') owner, ref = ref.split(':', 2) project = github_project(context_project.name, owner) end [project || context_project, ref] end while arg = args.shift case arg when '-f' force = true when '-F', '--file' file = args.shift text = file == '-' ? $stdin.read : File.read(file) options[:title], options[:body] = read_msg(text) when '-m', '--message' text = args.shift options[:title], options[:body] = read_msg(text) when '-b' base_project, options[:base] = from_github_ref.call(args.shift, base_project) when '-h' head = args.shift explicit_owner = !!head.index(':') head_project, options[:head] = from_github_ref.call(head, head_project) when '-i' options[:issue] = args.shift when '-o', '--browse' open_with_browser = true else if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/ options[:issue] = $1 base_project = url.project elsif !options[:title] && arg.index('-') != 0 options[:title] = arg warn "hub: Specifying pull request title without a flag is deprecated." warn "Please use one of `-m' or `-F' options." else abort "invalid argument: #{arg}" end end end if options[:issue] warn "Warning: Issue to pull request conversion is deprecated and might not work in the future." end options[:project] = base_project options[:base] ||= master_branch.short_name if options[:head].nil? && tracked_branch if !tracked_branch.remote? # The current branch is tracking another local branch. Pretend there is # no upstream configuration at all. tracked_branch = nil elsif base_project == head_project and tracked_branch.short_name == options[:base] $stderr.puts "Aborted: head branch is the same as base (#{options[:base].inspect})" warn "(use `-h <branch>` to specify an explicit pull request head)" abort end end options[:head] ||= (tracked_branch || current_branch).short_name remote_branch = "#{head_project.remote}/#{options[:head]}" options[:head] = "#{head_project.owner}:#{options[:head]}" if !force and tracked_branch and local_commits = rev_list(remote_branch, nil) $stderr.puts "Aborted: #{local_commits.split("\n").size} commits are not yet pushed to #{remote_branch}" warn "(use `-f` to force submit a pull request anyway)" abort end if args.noop? puts "Would request a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}" exit end unless options[:title] or options[:issue] base_branch = "#{base_project.remote}/#{options[:base]}" commits = rev_list(base_branch, remote_branch).to_s.split("\n") case commits.size when 0 default_message = commit_summary = nil when 1 format = '%s%n%+b' default_message = git_command "show -s --format='#{format}' #{commits.first}" commit_summary = nil else format = '%h (%aN, %ar)%n%w(78,3,3)%s%n%+b' default_message = nil commit_summary = git_command "log --no-color --format='%s' --cherry %s...%s" % [format, base_branch, remote_branch] end options[:title], options[:body] = pullrequest_editmsg(commit_summary) { |msg, initial_message, cc| initial_message ||= default_message msg.puts initial_message if initial_message msg.puts "" msg.puts "#{cc} Requesting a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}" msg.puts "#{cc}" msg.puts "#{cc} Write a message for this pull request. The first block" msg.puts "#{cc} of text is the title and the rest is description." } end pull = api_client.create_pullrequest(options) args.push('-u') unless open_with_browser browse_command(args) do pull['html_url'] end rescue GitHubAPI::Exceptions response = $!.response display_api_exception("creating pull request", response) if 404 == response.status base_url = base_project.web_url.split('://', 2).last warn "Are you sure that #{base_url} exists?" end exit 1 else delete_editmsg end
$ hub push origin,staging cool-feature > git push origin cool-feature > git push staging cool-feature
# File lib/hub/commands.rb, line 641 def push(args) return if args[1].nil? || !args[1].index(',') refs = args.words[2..-1] remotes = args[1].split(',') args[1] = remotes.shift if refs.empty? # add current branch as explicit ref when there are no refs specified refs = [current_branch.short_name] args.concat refs end remotes.each do |name| args.after ['push', name, *refs] end end
$ hub remote add pjhyett > git remote add pjhyett git://github.com/pjhyett/THIS_REPO.git
$ hub remote add -p mojombo > git remote add mojombo git@github.com:mojombo/THIS_REPO.git
$ hub remote add origin > git remote add origin git://github.com/YOUR_LOGIN/THIS_REPO.git
# File lib/hub/commands.rb, line 333 def remote(args) if %w[add set-url].include?(args[1]) name = args.last if name =~ /^(#{OWNER_RE})$/ || name =~ /^(#{OWNER_RE})\/(#{NAME_RE})$/ user, repo = $1, $2 || repo_name end end return unless user # do not touch arguments ssh = args.delete('-p') if args.words[2] == 'origin' && args.words[3].nil? # Origin special case triggers default user/repo user, repo = github_user, repo_name elsif args.words[-2] == args.words[1] # rtomayko/tilt => rtomayko # Make sure you dance around flags. idx = args.index( args.words[-1] ) args[idx] = user else # They're specifying the remote name manually (e.g. # git remote add blah rtomayko/tilt), so just drop the last # argument. args.pop end args << git_url(user, repo, :private => ssh) end
# File lib/hub/commands.rb, line 45 def run(args) slurp_global_flags(args) # Hack to emulate git-style args.unshift 'help' if args.empty? cmd = args[0] if expanded_args = expand_alias(cmd) cmd = expanded_args[0] expanded_args.concat args[1..-1] end respect_help_flags(expanded_args || args) if custom_command? cmd # git commands can have dashes cmd = cmd.gsub(/(\w)-/, '\1_') if method_defined?(cmd) and cmd != 'run' args.replace expanded_args if expanded_args send(cmd, args) end rescue Errno::ENOENT if $!.message.include? "No such file or directory - git" abort "Error: `git` command not found" else raise end rescue Context::FatalError => err abort "fatal: #{err.message}" end
$ hub submodule add wycats/bundler vendor/bundler > git submodule add git://github.com/wycats/bundler.git vendor/bundler
$ hub submodule add -p wycats/bundler vendor/bundler > git submodule add git@github.com:wycats/bundler.git vendor/bundler
$ hub submodule add -b ryppl –name pip ryppl/pip vendor/pip > git submodule add -b ryppl –name pip git://github.com/ryppl/pip.git vendor/pip
# File lib/hub/commands.rb, line 317 def submodule(args) return unless index = args.index('add') args.delete_at index clone(args) args.insert index, 'add' end
$ hub version > git version (print hub version)
# File lib/hub/commands.rb, line 802 def version(args) args.after 'echo', ['hub version', Version] end
# File lib/hub/commands.rb, line 839 def api_client @api_client ||= begin config_file = ENV['HUB_CONFIG'] || '~/.config/hub' file_store = GitHubAPI::FileStore.new File.expand_path(config_file) file_config = GitHubAPI::Configuration.new file_store GitHubAPI.new file_config, :app_url => 'http://hub.github.com/', :verbose => !ENV['HUB_VERBOSE'].to_s.empty? end end
Helper methods are private so they cannot be invoked from the command line.
# File lib/hub/commands.rb, line 835 def branch_in_url(branch) CGI.escape(branch.short_name).gsub("%2F", "/") end
Handles common functionality of browser commands like `browse` and `compare`. Yields a block that returns params for `github_url`.
# File lib/hub/commands.rb, line 982 def browse_command(args) url_only = args.delete('-u') warn "Warning: the `-p` flag has no effect anymore" if args.delete('-p') url = yield args.executable = url_only ? 'echo' : browser_launcher args.push url end
# File lib/hub/commands.rb, line 855 def custom_command? cmd CUSTOM_COMMANDS.include? cmd end
# File lib/hub/commands.rb, line 1137 def delete_editmsg(file = pullrequest_editmsg_file) File.delete(file) if File.exist?(file) end
# File lib/hub/commands.rb, line 1150 def display_api_exception(action, response) $stderr.puts "Error #{action}: #{response.message.strip} (HTTP #{response.status})" if 422 == response.status and response.error_message? # display validation errors msg = response.error_message msg = msg.join("\n") if msg.respond_to? :join warn msg end end
# File lib/hub/commands.rb, line 1141 def expand_alias(cmd) if expanded = git_alias_for(cmd) if expanded.index('!') != 0 require 'shellwords' unless defined?(::Shellwords) Shellwords.shellwords(expanded) end end end
# File lib/hub/commands.rb, line 850 def github_user host = nil, &block host ||= (local_repo(false) || Context::LocalRepo).default_host api_client.username_via_auth_dance(host, &block) end
The groff command complete with crazy arguments we need to run in order to turn our raw roff (manpage markup) into something readable on the terminal.
# File lib/hub/commands.rb, line 1009 def groff_command cols = terminal_width "groff -Wall -mtty-char -mandoc -Tascii -rLL=#{cols}n -rLT=#{cols}n" end
Returns the terminal-formatted manpage, ready to be printed to the screen.
# File lib/hub/commands.rb, line 993 def hub_manpage abort "** Can't find groff(1)" unless command?('groff') require 'open3' out = nil Open3.popen3(groff_command) do |stdin, stdout, _| stdin.puts hub_raw_manpage stdin.close out = stdout.read.strip end out end
Returns the raw hub manpage. If we're not running in standalone mode, it's a file sitting at the root under the `man` directory.
If we are running in standalone mode the manpage will be included after the __END__ of the file so we can grab it using DATA.
# File lib/hub/commands.rb, line 1021 def hub_raw_manpage if File.exist? file = File.dirname(__FILE__) + '/../../man/hub.1' File.read(file) else DATA.read end end
The text print when `hub help` is run, kept in its own method for the convenience of the author.
# File lib/hub/commands.rb, line 881 def improved_help_text <<-help usage: git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>] [-c name=value] [--help] <command> [<args>] Basic Commands: init Create an empty git repository or reinitialize an existing one add Add new or modified files to the staging area rm Remove files from the working directory and staging area mv Move or rename a file, a directory, or a symlink status Show the status of the working directory and staging area commit Record changes to the repository History Commands: log Show the commit history log diff Show changes between commits, commit and working tree, etc show Show information about commits, tags or files Branching Commands: branch List, create, or delete branches checkout Switch the active branch to another branch merge Join two or more development histories (branches) together tag Create, list, delete, sign or verify a tag object Remote Commands: clone Clone a remote repository into a new directory fetch Download data, tags and branches from a remote repository pull Fetch from and merge with another repository or a local branch push Upload data, tags and branches to a remote repository remote View and manage a set of remote repositories Advanced Commands: reset Reset your staging area or working directory to another point rebase Re-apply a series of patches in one branch onto another bisect Find by binary search the change that introduced a bug grep Print files with lines matching a pattern in your codebase GitHub Commands: pull-request Open a pull request on GitHub fork Make a fork of a remote repository on GitHub and add as remote create Create this repository on GitHub and add GitHub as origin browse Open a GitHub page in the default browser compare Open a compare page on GitHub ci-status Show the CI status of a commit See 'git help <command>' for more information on a specific command. help end
nex-3.com/posts/73-git-style-automatic-paging-in-ruby
# File lib/hub/commands.rb, line 1037 def page_stdout return if not $stdout.tty? or windows? read, write = IO.pipe if Kernel.fork # Parent process, become pager $stdin.reopen(read) read.close write.close # Don't page if the input is short enough ENV['LESS'] = 'FSRX' # Wait until we have input before we start the pager Kernel.select [STDIN] pager = ENV['GIT_PAGER'] || %xgit config --get-all core.pager`.split("\n").first || ENV['PAGER'] || 'less -isr' pager = 'cat' if pager.empty? exec pager rescue exec "/bin/sh", "-c", pager else # Child process $stdout.reopen(write) $stderr.reopen(write) if $stderr.tty? read.close write.close end rescue NotImplementedError # fork might not available, such as in JRuby end
# File lib/hub/commands.rb, line 1073 def pullrequest_editmsg(changes) message_file = pullrequest_editmsg_file cc = git_commentchar if valid_editmsg_file?(message_file) title, body = read_editmsg(message_file, cc) previous_message = [title, body].compact.join("\n\n") if title end File.open(message_file, 'w') { |msg| yield msg, previous_message, cc if changes msg.puts "#{cc}\n#{cc} Changes:\n#{cc}" msg.puts changes.gsub(/^/, "#{cc} ").gsub(/ +$/, '') end } edit_cmd = Array(git_editor).dup edit_cmd << '-c' << 'set ft=gitcommit tw=0 wrap lbr' if edit_cmd[0] =~ /^[mg]?vim$/ edit_cmd << message_file system(*edit_cmd) unless $?.success? # writing was cancelled, or the editor never opened in the first place delete_editmsg(message_file) abort "error using text editor for pull request message" end title, body = read_editmsg(message_file, cc) abort "Aborting due to empty pull request title" unless title [title, body] end
# File lib/hub/commands.rb, line 1118 def pullrequest_editmsg_file File.join(git_dir, 'PULLREQ_EDITMSG') end
All calls to `puts` in after hooks or commands are paged, git-style.
# File lib/hub/commands.rb, line 1031 def puts(*args) page_stdout super end
# File lib/hub/commands.rb, line 1122 def read_editmsg(file, commentchar) title, body = '', '' File.open(file, 'r') { |msg| msg.each_line do |line| next if line.index(commentchar) == 0 ((title.empty? and line =~ /\S/) ? title : body) << line end } title.tr!("\n", ' ') title.strip! body.strip! [title =~ /\S/ ? title : nil, body =~ /\S/ ? body : nil] end
# File lib/hub/commands.rb, line 1114 def read_msg(message) message.split("\n\n", 2).each {|s| s.strip! }.reject {|s| s.empty? } end
Show short usage help for `-h` flag, and open man page for `–help`
# File lib/hub/commands.rb, line 860 def respect_help_flags args return if args.size > 2 case args[1] when '-h' pattern = /(git|hub) #{Regexp.escape args[0].gsub('-', '\-')}/ hub_raw_manpage.each_line { |line| if line =~ pattern $stderr.print "Usage: " $stderr.puts line.gsub(/\f./, '').gsub('\-', '-') abort end } abort "Error: couldn't find usage help for #{args[0]}" when '--help' puts hub_manpage exit end end
Extract global flags from the front of the arguments list. Makes sure important ones are supplied for calls to subcommands.
Known flags are:
--version --exec-path=<path> --html-path -p|--paginate|--no-pager --no-replace-objects --bare --git-dir=<path> --work-tree=<path> -c name=value --help
Special: `–version`, `–help` are replaced with “version” and “help”. Ignored: `–exec-path`, `–html-path` are kept in args list untouched.
# File lib/hub/commands.rb, line 944 def slurp_global_flags(args) flags = %w[ --noop -c -p --paginate --no-pager --no-replace-objects --bare --version --help ] flags2 = %w[ --exec-path= --git-dir= --work-tree= ] # flags that should be present in subcommands, too globals = [] # flags that apply only to main command locals = [] while args[0] && (flags.include?(args[0]) || flags2.any? {|f| args[0].index(f) == 0 }) flag = args.shift case flag when '--noop' args.noop! when '--version', '--help' args.unshift flag.sub('--', '') when '-c' # slurp one additional argument config_pair = args.shift # add configuration to our local cache key, value = config_pair.split('=', 2) git_reader.stub_config_value(key, value) globals << flag << config_pair when '-p', '--paginate', '--no-pager' locals << flag else globals << flag end end git_reader.add_exec_flags(globals) args.add_exec_flags(globals) args.add_exec_flags(locals) end
This unfortunate hack is because older versions of hub never cleaned up the #pullrequest_editmsg_file, which newer hub would pick up and misinterpret as a message which should be reused after a failed PR.
# File lib/hub/commands.rb, line 1109 def valid_editmsg_file?(message_file) File.exist?(message_file) && File.mtime(message_file) > File.mtime(__FILE__) end