3 # This file is generated code. DO NOT send patches for it.
5 # Original source files with comments are at:
6 # https://github.com/defunkt/hub
10 Version
= VERSION
= '1.10.3'
15 attr_accessor
:executable
19 @executable = ENV["GIT"] || "git"
22 @original_args = args
.first
26 def after(cmd_or_args
= nil, args
= nil, &block
)
27 @chain.insert(-1, normalize_callback(cmd_or_args
, args
, block
))
30 def before(cmd_or_args
= nil, args
= nil, &block
)
31 @chain.insert(@chain.index(nil), normalize_callback(cmd_or_args
, args
, block
))
40 chain
[chain
.index(nil)] = self.to_exec
60 def to_exec(args
= self)
61 Array(executable
) + args
64 def add_exec_flags(flags
)
65 self.executable
= Array(executable
).concat(flags
)
69 reject
{ |arg
| arg
.index('-') == 0 }
77 chained
? or self !
= @original_args
81 pattern
= flags
.flatten
.map
{ |f
| Regexp
.escape(f
) }.join('|')
82 !
grep(/^#{pattern}(?:=|$)/).empty
?
87 def normalize_callback(cmd_or_args
, args
, block
)
91 [cmd_or_args
].concat args
92 elsif Array
=== cmd_or_args
93 self.to_exec cmd_or_args
97 raise ArgumentError
, "command or block required"
105 CONFIG_FILES
= %w(~
/.ssh/config
/etc/ssh_config
/etc/ssh
/ssh_config
)
107 def initialize files
= nil
108 @settings = Hash
.new
{|h
,k
| h
[k
] = {} }
109 Array(files
|| CONFIG_FILES
).each
do |path
|
110 file
= File
.expand_path path
111 parse_file file
if File
.exist
? file
115 def get_value hostname
, key
116 key
= key
.to_s
.downcase
117 @settings.each
do |pattern
, settings
|
118 if pattern
.match
? hostname
and found
= settings
[key
]
126 def initialize pattern
127 @pattern = pattern
.to_s
.downcase
130 def to_s() @pattern end
131 def ==(other
) other
.to_s
== self.to_s
end
137 elsif @pattern !~
/[?*]/
138 lambda
{ |hostname
| hostname
.to_s
.downcase
== @pattern }
140 re
= self.class.pattern_to_regexp
@pattern
141 lambda
{ |hostname
| re
=~ hostname
}
146 matcher
.call hostname
149 def self.pattern_to_regexp pattern
150 escaped
= Regexp
.escape(pattern
)
151 escaped
.gsub!
('\*', '.*')
152 escaped
.gsub!
('\?', '.')
158 host_patterns
= [HostPattern
.new('*')]
160 IO
.foreach(file
) do |line
|
162 when /^\s*(#|$)/ then next
166 key, value = line.strip.split(/\s+/, 2)
171 value
= $1 if value
=~
/^"(.*)"$/
175 host_patterns
= value
.split(/\s+/).map
{|p
| HostPattern
.new p
}
177 record_setting key
, value
, host_patterns
182 def record_setting key
, value
, patterns
183 patterns
.each
do |pattern
|
184 @settings[pattern
][key
] ||= value
192 require 'forwardable'
197 attr_reader
:config, :oauth_app_url
199 def initialize config
, options
201 @oauth_app_url = options
.fetch(:app_url)
205 def self.===(exception
)
206 exception
.class.ancestors
.map
{|a
| a
.to_s
}.include? 'Net::HTTPExceptions'
212 'github.com' == host
? 'api.github.com' : host
215 def repo_info project
216 get
"https://%s/repos/%s/%s" %
217 [api_host(project
.host
), project
.owner
, project
.name
]
220 def repo_exists
? project
221 repo_info(project
).success
?
224 def fork_repo project
225 res
= post
"https://%s/repos/%s/%s/forks" %
226 [api_host(project
.host
), project
.owner
, project
.name
]
227 res
.error!
unless res
.success
?
230 def create_repo project
, options
= {}
231 is_org
= project
.owner !
= config
.username(api_host(project
.host
))
232 params
= { :name => project
.name
, :private => !!options
[:private] }
233 params
[:description] = options
[:description] if options
[:description]
234 params
[:homepage] = options
[:homepage] if options
[:homepage]
237 res
= post
"https://%s/orgs/%s/repos" % [api_host(project
.host
), project
.owner
], params
239 res
= post
"https://%s/user/repos" % api_host(project
.host
), params
241 res
.error!
unless res
.success
?
245 def pullrequest_info project
, pull_id
246 res
= get
"https://%s/repos/%s/%s/pulls/%d" %
247 [api_host(project
.host
), project
.owner
, project
.name
, pull_id
]
248 res
.error!
unless res
.success
?
252 def create_pullrequest options
253 project
= options
.fetch(:project)
255 :base => options
.fetch(:base),
256 :head => options
.fetch(:head)
260 params
[:issue] = options
[:issue]
262 params
[:title] = options
[:title] if options
[:title]
263 params
[:body] = options
[:body] if options
[:body]
266 res
= post
"https://%s/repos/%s/%s/pulls" %
267 [api_host(project
.host
), project
.owner
, project
.name
], params
269 res
.error!
unless res
.success
?
274 module ResponseMethods
275 def status() code
.to_i
end
276 def data?() content_type
=~
/\bjson\b/ end
277 def data() @data ||= JSON
.parse(body
) end
278 def error_message
?() data? and data['errors'] || data['message'] end
279 def error_message() error_sentences
|| data['message'] end
280 def success
?() Net
::HTTPSuccess === self end
282 data['errors'].map
do |err
|
284 when 'custom' then err
['message']
285 when 'missing_field' then "field '%s' is missing" % err
['field']
287 end.compact
if data['errors']
292 perform_request url
, :Get, &block
295 def post url
, params
= nil
296 perform_request url
, :Post do |req
|
298 req
.body
= JSON
.dump params
299 req
['Content-Type'] = 'application/json;charset=utf-8'
301 yield req
if block_given
?
302 req
['Content-Length'] = byte_size req
.body
307 if str
.respond_to
? :bytesize then str
.bytesize
308 elsif str
.respond_to
? :length then str
.length
313 def post_form url
, params
314 post(url
) {|req
| req
.set_form_data params
}
317 def perform_request url
, type
318 url
= URI
.parse url
unless url
.respond_to
? :host
321 req
= Net
::HTTP.const_get(type
).new
request_uri(url
)
322 http
= configure_connection(req
, url
) do |host_url
|
323 create_connection host_url
326 apply_authentication(req
, url
)
327 yield req
if block_given
?
330 res
= http
.start
{ http
.request(req
) }
331 res
.extend ResponseMethods
333 rescue SocketError
=> err
334 raise Context
::FatalError, "error with #{type.to_s.upcase} #{url} (#{err.message})"
339 str
= url
.request_uri
340 str
= '/api/v3' << str
if url
.host !
= 'api.github.com'
344 def configure_connection req
, url
345 if ENV['HUB_TEST_HOST']
346 req
['Host'] = url
.host
349 url
.host
, test_port
= ENV['HUB_TEST_HOST'].split(':')
350 url
.port
= test_port
.to_i
if test_port
355 def apply_authentication req
, url
356 user
= url
.user
|| config
.username(url
.host
)
357 pass
= config
.password(url
.host
, user
)
358 req
.basic_auth user
, pass
361 def create_connection url
362 use_ssl
= 'https' == url
.scheme
365 if proxy
= config
.proxy_uri(use_ssl
)
366 proxy_args
<< proxy
.host
<< proxy
.port
369 proxy_args
.concat proxy
.userinfo
.split(':', 2).map
{|a
| CGI
.unescape a
}
373 http
= Net
::HTTP.new(url
.host
, url
.port
, *proxy_args
)
375 if http
.use_ssl
= use_ssl
376 http
.verify_mode
= OpenSSL
::SSL::VERIFY_NONE
383 def apply_authentication req
, url
384 if (req
.path
=~
/\/authorizations
$/)
387 user
= url
.user
|| config
.username(url
.host
)
388 token
= config
.oauth_token(url
.host
, user
) {
389 obtain_oauth_token url
.host
, user
391 req
['Authorization'] = "token #{token}"
395 def obtain_oauth_token host
, user
396 res
= get
"https://#{user}@#{host}/authorizations"
397 res
.error!
unless res
.success
?
399 if found
= res
.data.find
{|auth
| auth
['app']['url'] == oauth_app_url
}
402 res
= post
"https://#{user}@#{host}/authorizations",
403 :scopes => %w
[repo
], :note => 'hub', :note_url => oauth_app_url
404 res
.error!
unless res
.success
?
415 def_delegator
:@data, :[], :get
416 def_delegator
:@data, :[]=, :set
418 def initialize filename
420 @data = Hash
.new
{|d
, host
| d
[host
] = [] }
421 load
if File
.exist
? filename
425 unless entry
= get(host
).first
427 return nil if user
.nil? or user
.empty
?
428 entry
= entry_for_user(host
, user
)
433 def fetch_value host
, user
, key
434 entry
= entry_for_user host
, user
435 entry
[key
.to_s
] || begin
437 if value
and !value
.empty
?
438 entry
[key
.to_s
] = value
447 def entry_for_user host
, username
449 entries
.find
{|e
| e
['user'] == username
} or
450 (entries
<< {'user' => username
}).last
454 existing_data
= File
.read(@filename)
455 @data.update YAML
.load(existing_data
) unless existing_data
.strip
.empty
?
459 FileUtils
.mkdir_p File
.dirname(@filename)
460 File
.open(@filename, 'w', 0600) {|f
| f
<< YAML
.dump(@data) }
470 def normalize_host host
472 'api.github.com' == host
? 'github.com' : host
476 return ENV['GITHUB_USER'] unless ENV['GITHUB_USER'].to_s
.empty
?
477 host
= normalize_host host
478 @data.fetch_user host
do
479 if block_given
? then yield
480 else prompt
"#{host} username"
485 def api_token host
, user
486 host
= normalize_host host
487 @data.fetch_value host
, user
, :api_token do
488 if block_given
? then yield
489 else prompt
"#{host} API token for #{user}"
494 def password host
, user
495 return ENV['GITHUB_PASSWORD'] unless ENV['GITHUB_PASSWORD'].to_s
.empty
?
496 host
= normalize_host host
497 @password_cache["#{user}@#{host}"] ||= prompt_password host
, user
500 def oauth_token host
, user
, &block
501 @data.fetch_value
normalize_host(host
), user
, :oauth_token, &block
509 def prompt_password host
, user
510 print
"#{host} password for #{user} (never stored): "
521 tty_state
= `stty -g`
522 system
'stty raw -echo -icanon isig' if $
?.success
?
524 while char
= $stdin.getbyte
and !
(char
== 13 or char
== 10)
525 if char
== 127 or char
== 8
526 pass
[-1,1] = '' unless pass
.empty
?
533 system
"stty #{tty_state}" unless tty_state
.empty
?
536 def proxy_uri(with_ssl
)
537 env_name
= "HTTP#{with_ssl ? 'S' : ''}_PROXY"
538 if proxy
= ENV[env_name
] || ENV[env_name
.downcase
] and !proxy
.empty
?
539 proxy
= "http://#{proxy}" unless proxy
.include? '://'
548 require 'forwardable'
555 NULL
= defined?(File
::NULL) ? File
::NULL : File
.exist
?('/dev/null') ? '/dev/null' : 'NUL'
558 attr_reader
:executable
560 def initialize(executable
= nil, &read_proc
)
561 @executable = executable
|| 'git'
562 read_proc
||= lambda
{ |cache
, cmd
|
563 result
= %x{#{command_to_string(cmd)} 2>#{NULL}}.chomp
564 cache
[cmd
] = $
?.success
? && !result
.empty
? ? result
: nil
566 @cache = Hash
.new(&read_proc
)
569 def add_exec_flags(flags
)
570 @executable = Array(executable
).concat(flags
)
573 def read_config(cmd
, all
= false)
574 config_cmd
= ['config', (all
? '--get-all' : '--get'), *cmd
]
575 config_cmd
= config_cmd
.join(' ') unless cmd
.respond_to
? :join
583 def stub_config_value(key
, value
, get
= '--get')
584 stub_command_output
"config #{get} #{key}", value
587 def stub_command_output(cmd
, value
)
588 @cache[cmd
] = value
.nil? ? nil : value
.to_s
598 args
= Shellwords
.shellwords(args
) if args
.respond_to
? :to_str
599 Array(executable
) +
Array(args
)
602 def command_to_string(cmd
)
603 full_cmd
= to_exec(cmd
)
604 full_cmd
.respond_to
?(:shelljoin) ? full_cmd
.shelljoin
: full_cmd
.join(' ')
608 module GitReaderMethods
611 def_delegator
:git_reader, :read_config, :git_config
612 def_delegator
:git_reader, :read, :git_command
614 def self.extended(base
)
615 base
.extend Forwardable
616 base
.def_delegators
:'self.class', :git_config, :git_command
620 class Error
< RuntimeError
; end
621 class FatalError
< Error
; end
626 @git_reader ||= GitReader
.new
ENV['GIT']
629 include GitReaderMethods
630 private :git_config, :git_command
632 def local_repo(fatal
= true)
633 @local_repo ||= begin
635 LocalRepo
.new git_reader
, current_dir
637 raise FatalError
, "Not a git repository"
644 :current_project, :upstream_project,
645 :repo_owner, :repo_host,
646 :remotes, :remotes_group, :origin_remote
648 def_delegator
:local_repo, :name, :repo_name
649 def_delegators
:local_repo, *repo_methods
650 private :repo_name, *repo_methods
654 local_repo
.master_branch
656 Branch
.new
nil, 'refs/heads/master'
660 class LocalRepo
< Struct
.new(:git_reader, :dir)
661 include GitReaderMethods
664 if project
= main_project
672 if project
= main_project
678 project
= main_project
and project
.host
682 remote
= origin_remote
and remote
.project
686 if branch
= current_branch
and upstream
= branch
.upstream
and upstream
.remote
?
687 remote
= remote_by_name upstream
.remote_name
693 upstream_project
|| main_project
697 if branch
= git_command('symbolic-ref -q HEAD')
698 Branch
.new
self, branch
703 Branch
.new
self, 'refs/heads/master'
708 list
= git_command('remote').to_s
.split("\n")
709 main
= list
.delete('origin') and list
.unshift(main
)
710 list
.map
{ |name
| Remote
.new
self, name
}
714 def remotes_group(name
)
715 git_config
"remotes.#{name}"
722 def remote_by_name(remote_name
)
723 remotes
.find
{|r
| r
.name
== remote_name
}
727 hosts
= git_config('hub.host', :all).to_s
.split("\n")
728 hosts
<< default_host
729 hosts
<< "ssh.#{default_host}"
732 def self.default_host
733 ENV['GITHUB_HOST'] || main_host
741 def_delegators
:'self.class', :default_host, :main_host
744 @ssh_config ||= SshConfig
.new
748 class GithubProject
< Struct
.new(:local_repo, :owner, :name, :host)
749 def self.from_url(url
, local_repo
)
750 if local_repo
.known_hosts
.include? url
.host
751 _
, owner
, name
= url
.path
.split('/', 4)
752 GithubProject
.new(local_repo
, owner
, name
.sub(/\.git$/, ''), url
.host
)
756 attr_accessor
:repo_data
758 def initialize(*args
)
760 self.name
= self.name
.tr(' ', '-')
761 self.host
||= (local_repo
|| LocalRepo
).default_host
762 self.host
= host
.sub(/^ssh\./i
, '') if 'ssh.github.com' == host
.downcase
766 repo_data
? repo_data
.fetch('private') :
767 host !
= (local_repo
|| LocalRepo
).main_host
770 def owned_by(new_owner
)
772 new_project
.owner
= new_owner
781 name_with_owner
== other
.name_with_owner
785 local_repo
.remotes
.find
{ |r
| r
.project
== self }
788 def web_url(path
= nil)
789 project_name
= name_with_owner
790 if project_name
.sub!
(/\.wiki$/, '')
791 unless '/wiki' == path
792 path
= if path
=~
%r
{^
/commits/} then '/_history'
793 else path
.to_s
.sub(/\w+/, '_\0')
795 path
= '/wiki' + path
798 "https://#{host}/" + project_name + path
.to_s
801 def git_url(options
= {})
802 if options
[:https] then "https://#{host}/"
803 elsif options
[:private] or private? then "git@#{host}:"
804 else "git://#{host}/"
805 end + name_with_owner +
'.git'
809 class GithubURL
< URI
::HTTPS
813 def_delegator
:project, :name, :project_name
814 def_delegator
:project, :owner, :project_owner
816 def self.resolve(url
, local_repo
)
818 if %[http https
].include? u
.scheme
and project
= GithubProject
.from_url(u
, local_repo
)
819 self.new(u
.scheme
, u
.userinfo
, u
.host
, u
.port
, u
.registry
,
820 u
.path
, u
.opaque
, u
.query
, u
.fragment
, project
)
822 rescue URI
::InvalidURIError
826 def initialize(*args
)
832 path
.split('/', 4)[3]
836 class Branch
< Struct
.new(:local_repo, :name)
840 name
.sub(%r
{^refs
/(remotes
/)?.+
?/}, '')
844 short_name
== 'master'
848 if branch
= local_repo
.git_command("rev-parse --symbolic-full-name #{short_name}@{upstream}")
849 Branch
.new local_repo
, branch
854 name
.index('refs/remotes/') == 0
858 name
=~
%r
{^refs
/remotes
/([^
/]+
)} and $1 or
859 raise Error
, "can't get remote name from #{name.inspect}"
863 class Remote
< Struct
.new(:local_repo, :name)
867 other
.respond_to
?(:to_str) ? name
== other
.to_str
: super
871 urls
.each_value
{ |url
|
872 if valid
= GithubProject
.from_url(url
, local_repo
)
880 return @urls if defined? @urls
882 local_repo
.git_command('remote -v').to_s
.split("\n").map
do |line
|
883 next if line !~
/^(.+?)\t(.+) \((.+)\)$/
884 remote
, uri
, type
= $1, $2, $3
885 next if remote !
= self.name
886 if uri
=~
%r
{^
[\w-
]+
://} or uri
=~
%r
{^
([^
/]+
?):}
887 uri
= "ssh://#{$1}/#{$'}" if $1
889 @urls[type
] = uri_parse(uri
)
890 rescue URI
::InvalidURIError
899 uri
.host
= local_repo
.ssh_config
.get_value(uri
.host
, 'hostname') { uri
.host
}
900 uri
.user
= local_repo
.ssh_config
.get_value(uri
.host
, 'user') { uri
.user
}
906 def github_project(name
, owner
= nil)
907 if owner
and owner
.index('/')
908 owner
, name
= owner
.split('/', 2)
909 elsif name
and name
.index('/')
910 owner
, name
= name
.split('/', 2)
913 owner
||= github_user
916 if local_repo(false) and main_project
= local_repo
.main_project
917 project
= main_project
.dup
918 project
.owner
= owner
922 GithubProject
.new(local_repo(false), owner
, name
)
926 def git_url(owner
= nil, name
= nil, options
= {})
927 project
= github_project(name
, owner
)
928 project
.git_url({:https => https_protocol
?}.update(options
))
931 def resolve_github_url(url
)
932 GithubURL
.resolve(url
, local_repo
) if url
=~
/^https?:/
936 git_config('--bool hub.http-clone') == 'true'
940 git_config('hub.protocol') == 'https' or http_clone
?
943 def git_alias_for(name
)
944 git_config
"alias.#{name}"
948 git_command("rev-list --cherry-pick --right-only --no-merges #{a}...#{b}")
958 git_command
'rev-parse -q --git-dir'
966 editor
= git_command
'var GIT_EDITOR'
967 editor
= ENV[$1] if editor
=~
/^\$(\w+)$/
968 editor
= File
.expand_path editor
if (editor
=~
/^[~.]/ or editor
.index('/')) and editor !~
/["']/
974 browser = ENV['BROWSER'] || (
975 osx? ? 'open' : windows? ? %w[cmd /c start] :
976 %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm }
979 abort "Please set
$BROWSER to a web launcher to use this command
." unless browser
985 RbConfig::CONFIG['host_os'].to_s.include?('darwin')
990 RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/
994 exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
995 ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
997 exe = "#{path}/#{cmd}#{ext}"
998 return exe if File.executable? exe
1015 require 'forwardable'
1018 def self.parse(data) new(data).parse
end
1021 OBJ
= /[{\[]/; HEN
= /\}/; AEN
= /\]/
1022 COL
= /\s*:\s*/; KEY
= /\s*,\s*/
1023 NUM
= /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/
1024 BOL
= /true|false/; NUL
= /null/
1028 attr_reader
:scanner
1029 alias_method
:s, :scanner
1030 def_delegators
:scanner, :scan, :matched
1031 private :s, :scan, :matched
1034 @scanner = StringScanner
.new
data.to_s
1044 def space() scan WSP
end
1046 def endkey() scan(KEY
) or space
end
1049 matched
== '{' ? hash
: array
if scan(OBJ
)
1055 scan(BOL
) ? matched
.size
== 4:
1056 scan(NUM
) ? eval(matched
) :
1063 repeat_until(HEN
) { k
= string
; scan(COL
); obj
[k
] = value
; endkey
}
1070 repeat_until(AEN
) { ary
<< value
; endkey
}
1074 SPEC
= {'b' => "\b", 'f' => "\f", 'n' => "\n", 'r' => "\r", 't' => "\t"}
1075 UNI
= 'u'; CODE
= /[a-fA-F0-9]{4}/
1076 STR
= /"/; STE
= '"'
1081 str
, esc
= '', false
1084 str
<< (c
== UNI
? (s
.scan(CODE
) || error
).to_i(16).chr
: SPEC
[c
] || c
)
1088 when ESC
then esc
= true
1099 raise "parse error at: #{scan(/.{1,10}/m).inspect}"
1102 def repeat_until reg
1106 error
unless s
.pos
> pos
1112 raise ArgumentError
unless obj
.is_a
? Array
or obj
.is_a
? Hash
1119 def generate_type(obj
)
1120 type
= obj
.is_a
?(Numeric
) ? :Numeric : obj
.class.name
1121 begin send(:"generate_#{type}", obj
)
1122 rescue NoMethodError
; raise ArgumentError
, "can't serialize #{type}"
1126 ESC_MAP
= Hash
.new
{|h
,k
| k
}.update \
1133 def generate_String(str
)
1134 escaped
= str
.gsub(/[\r\n\f\t\b"\\]/) { "\\#{ESC_MAP[$&]}"}
1138 def generate_simple(obj
) obj
.inspect
end
1139 alias generate_Numeric generate_simple
1140 alias generate_TrueClass generate_simple
1141 alias generate_FalseClass generate_simple
1143 def generate_Symbol(sym
) generate_String(sym
.to_s
) end
1145 def generate_NilClass(*) 'null' end
1147 def generate_Array(ary
) '[%s]' % ary
.map
{|o
| generate_type(o
) }.join(', ') end
1149 def generate_Hash(hash
)
1150 '{%s}' % hash
.map
{ |key
, value
|
1151 "#{generate_String(key.to_s)}: #{generate_type(value)}"
1161 instance_methods
.each
{ |m
| undef_method(m
) unless m
=~
/(^__|send|to\?$)/ }
1166 NAME_RE
= /[\w.][\w.-]*/
1167 OWNER_RE
= /[a-zA-Z0-9-]+/
1168 NAME_WITH_OWNER_RE
= /^(?:#{NAME_RE}|#{OWNER_RE}\/#{NAME_RE})$/
1170 CUSTOM_COMMANDS
= %w
[alias create browse compare fork pull-request
]
1173 slurp_global_flags(args
)
1175 args
.unshift
'help' if args
.empty
?
1178 if expanded_args
= expand_alias(cmd
)
1179 cmd
= expanded_args
[0]
1180 expanded_args
.concat args
[1..-1]
1183 respect_help_flags(expanded_args
|| args
) if custom_command
? cmd
1185 cmd
= cmd
.gsub(/(\w)-/, '\1_')
1186 if method_defined
?(cmd
) and cmd !
= 'run'
1187 args
.replace expanded_args
if expanded_args
1190 rescue Errno
::ENOENT
1191 if $!
.message
.include? "No such file or directory - git"
1192 abort
"Error: `git` command not found"
1196 rescue Context
::FatalError => err
1197 abort
"fatal: #{err.message}"
1200 def pull_request(args
)
1203 force
= explicit_owner
= false
1204 base_project
= local_repo
.main_project
1205 head_project
= local_repo
.current_project
1208 abort
"Aborted: the origin remote doesn't point to a GitHub repository."
1211 from_github_ref
= lambda
do |ref
, context_project
|
1213 owner
, ref
= ref
.split(':', 2)
1214 project
= github_project(context_project
.name
, owner
)
1216 [project
|| context_project
, ref
]
1219 while arg
= args
.shift
1224 base_project
, options
[:base] = from_github_ref
.call(args
.shift
, base_project
)
1227 explicit_owner
= !!head
.index(':')
1228 head_project
, options
[:head] = from_github_ref
.call(head
, head_project
)
1230 options
[:issue] = args
.shift
1232 if url
= resolve_github_url(arg
) and url
.project_path
=~
/^issues\/(\d+
)/
1233 options
[:issue] = $1
1234 base_project
= url
.project
1235 elsif !options
[:title] then options
[:title] = arg
1237 abort
"invalid argument: #{arg}"
1242 options
[:project] = base_project
1243 options
[:base] ||= master_branch
.short_name
1245 if tracked_branch
= options
[:head].nil? && current_branch
.upstream
1246 if !tracked_branch
.remote
?
1247 tracked_branch
= nil
1248 elsif base_project
== head_project
and tracked_branch
.short_name
== options
[:base]
1249 $stderr.puts
"Aborted: head branch is the same as base (#{options[:base].inspect})"
1250 warn
"(use `-h <branch>` to specify an explicit pull request head)"
1254 options
[:head] ||= (tracked_branch
|| current_branch
).short_name
1256 user
= github_user(head_project
.host
)
1257 if head_project
.owner !
= user
and !tracked_branch
and !explicit_owner
1258 head_project
= head_project
.owned_by(user
)
1261 remote_branch
= "#{head_project.remote}/#{options[:head]}"
1262 options
[:head] = "#{head_project.owner}:#{options[:head]}"
1264 if !force
and tracked_branch
and local_commits
= rev_list(remote_branch
, nil)
1265 $stderr.puts
"Aborted: #{local_commits.split("\n").size} commits are not yet pushed to #{remote_branch}"
1266 warn
"(use `-f` to force submit a pull request anyway)"
1271 puts
"Would request a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
1275 unless options
[:title] or options
[:issue]
1276 base_branch
= "#{base_project.remote}/#{options[:base]}"
1277 commits
= rev_list(base_branch
, remote_branch
).to_s
.split("\n")
1281 default_message
= commit_summary
= nil
1283 format
= '%w(78,0,0)%s%n%+b
'
1284 default_message = git_command "show -s --format='#{format}' #{commits.first}"
1285 commit_summary = nil
1287 format = '%h (%aN
, %ar
)%n
%w(78,3,3)%s
%n
%+b
'
1288 default_message = nil
1289 commit_summary = git_command "log --no-color --format='%s
' --cherry %s...%s" %
1290 [format, base_branch, remote_branch]
1293 options[:title], options[:body] = pullrequest_editmsg(commit_summary) { |msg|
1294 msg.puts default_message if default_message
1296 msg.puts "# Requesting a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
1298 msg.puts "# Write a message for this pull request. The first block"
1299 msg.puts "# of text is the title and the rest is description."
1303 pull = api_client.create_pullrequest(options)
1305 args.executable = 'echo
'
1306 args.replace [pull['html_url
']]
1307 rescue GitHubAPI::Exceptions
1308 display_api_exception("creating pull request", $!.response
)
1313 ssh
= args
.delete('-p')
1314 has_values
= /^(--(upload-pack|template|depth|origin|branch|reference)|-[ubo])$/
1317 while idx
< args
.length
1319 if arg
.index('-') == 0
1320 idx +
= 1 if arg
=~ has_values
1322 if arg
=~ NAME_WITH_OWNER_RE
and !File
.directory
?(arg
)
1323 name
, owner
= arg
, nil
1324 owner
, name
= name
.split('/', 2) if name
.index('/')
1325 project
= github_project(name
, owner
|| github_user
)
1326 ssh
||= args
[0] !
= 'submodule' && project
.owner
== github_user(project
.host
) { }
1327 args
[idx
] = project
.git_url(:private => ssh
, :https => https_protocol
?)
1336 return unless index
= args
.index('add')
1337 args
.delete_at index
1339 branch
= args
.index('-b') || args
.index('--branch')
1341 args
.delete_at branch
1342 branch_name
= args
.delete_at branch
1348 args
.insert branch
, '-b', branch_name
1350 args
.insert index
, 'add'
1354 if %w
[add set-url
].include?(args
[1])
1356 if name
=~
/^(#{OWNER_RE})$/ || name
=~
/^(#{OWNER_RE})\/(#{NAME_RE})$/
1357 user
, repo
= $1, $2 || repo_name
1360 return unless user
# do not touch arguments
1362 ssh
= args
.delete('-p')
1364 if args
.words
[2] == 'origin' && args
.words
[3].nil?
1365 user
, repo
= github_user
, repo_name
1366 elsif args
.words
[-2] == args
.words
[1]
1367 idx
= args
.index( args
.words
[-1] )
1373 args
<< git_url(user
, repo
, :private => ssh
)
1377 if args
.include?('--multiple')
1378 names
= args
.words
[1..-1]
1379 elsif remote_name
= args
.words
[1]
1380 if remote_name
=~
/^\w+(,\w+)+$/
1381 index
= args
.index(remote_name
)
1382 args
.delete(remote_name
)
1383 names
= remote_name
.split(',')
1384 args
.insert(index
, *names
)
1385 args
.insert(index
, '--multiple')
1387 names
= [remote_name
]
1393 projects
= names
.map
{ |name
|
1394 unless name
=~
/\W/ or remotes
.include?(name
) or remotes_group(name
)
1395 project
= github_project(nil, name
)
1396 repo_info
= api_client
.repo_info(project
)
1397 if repo_info
.success
?
1398 project
.repo_data
= repo_info
.data
1405 projects
.each
do |project
|
1406 args
.before
['remote', 'add', project
.owner
, project
.git_url(:https => https_protocol
?)]
1412 _
, url_arg
, new_branch_name
= args
.words
1413 if url
= resolve_github_url(url_arg
) and url
.project_path
=~
/^pull\/(\d+
)/
1415 pull_data
= api_client
.pullrequest_info(url
.project
, pull_id
)
1417 args
.delete new_branch_name
1418 user
, branch
= pull_data
['head']['label'].split(':', 2)
1419 abort
"Error: #{user}'s fork is not available anymore" unless pull_data
['head']['repo']
1420 new_branch_name
||= "#{user}-#{branch}"
1422 if remotes
.include? user
1423 args
.before
['remote', 'set-branches', '--add', user
, branch
]
1424 args
.before
['fetch', user
, "+refs
/heads
/#{branch}:refs/remotes
/#{user}/#{branch}"]
1426 url = github_project(url.project_name, user).git_url(:private => pull_data['head']['repo']['private'],
1427 :https => https_protocol?)
1428 args.before ['remote', 'add', '-f', '-t', branch, user, url]
1430 idx = args.index url_arg
1432 args.insert idx, '--track', '-B', new_branch_name, "#{user}/#{branch}"
1437 _, url_arg = args.words
1438 if url = resolve_github_url(url_arg) and url.project_path =~ /^pull\/(\d+)/
1440 pull_data
= api_client
.pullrequest_info(url
.project
, pull_id
)
1442 user
, branch
= pull_data
['head']['label'].split(':', 2)
1443 abort
"Error: #{user}'s fork is not available anymore" unless pull_data
['head']['repo']
1445 url
= github_project(url
.project_name
, user
).git_url(:private => pull_data
['head']['repo']['private'],
1446 :https => https_protocol
?)
1448 merge_head
= "#{user}/#{branch}"
1449 args
.before
['fetch', url
, "+refs
/heads
/#{branch}:refs/remotes
/#{merge_head}"]
1451 idx = args.index url_arg
1453 args.insert idx, merge_head, '--no-ff', '-m',
1454 "Merge pull request
##{pull_id} from #{merge_head}\n\n#{pull_data['title']}"
1458 def cherry_pick(args
)
1459 unless args
.include?('-m') or args
.include?('--mainline')
1460 ref
= args
.words
.last
1461 if url
= resolve_github_url(ref
) and url
.project_path
=~
/^commit\/([a-f0-9
]{7,40})/
1463 project
= url
.project
1464 elsif ref
=~
/^(#{OWNER_RE})@([a-f0-9]{7,40})$/
1466 project
= local_repo
.main_project
.owned_by(owner
)
1470 args
[args
.index(ref
)] = sha
1472 if remote
= project
.remote
and remotes
.include? remote
1473 args
.before
['fetch', remote
.to_s
]
1475 args
.before
['remote', 'add', '-f', project
.owner
, project
.git_url(:https => https_protocol
?)]
1482 if url
= args
.find
{ |a
| a
=~
%r
{^https
?://(gist\
.)?github\
.com
/} }
1483 idx
= args
.index(url
)
1484 gist
= $1 == 'gist.'
1485 url
= url
.sub(/#.+/, '')
1486 url
= url
.sub(%r
{(/pull/\d+
)/\w
*$
}, '\1') unless gist
1487 ext
= gist
? '.txt' : '.patch'
1488 url +
= ext
unless File
.extname(url
) == ext
1489 patch_file
= File
.join(ENV['TMPDIR'] || '/tmp', "#{gist ? 'gist-' : ''}#{File.basename(url)}")
1490 args
.before
'curl', ['-#LA', "hub #{Hub::Version}", url
, '-o', patch_file
]
1491 args
[idx
] = patch_file
1495 alias_method
:apply, :am
1498 if args
.delete('-g')
1499 project
= github_project(File
.basename(current_dir
))
1500 url
= project
.git_url(:private => true, :https => https_protocol
?)
1501 args
.after
['remote', 'add', 'origin', url
]
1506 unless project
= local_repo
.main_project
1507 abort
"Error: repository under 'origin' remote is not a GitHub project"
1509 forked_project
= project
.owned_by(github_user(project
.host
))
1511 existing_repo
= api_client
.repo_info(forked_project
)
1512 if existing_repo
.success
?
1513 parent_data
= existing_repo
.data['parent']
1514 parent_url
= parent_data
&& resolve_github_url(parent_data
['html_url'])
1515 if !parent_url
or parent_url
.project !
= project
1516 abort
"Error creating fork: %s already exists on %s" %
1517 [ forked_project
.name_with_owner
, forked_project
.host
]
1520 api_client
.fork_repo(project
) unless args
.noop
?
1523 if args
.include?('--no-remote')
1526 url
= forked_project
.git_url(:private => true, :https => https_protocol
?)
1527 args
.replace
%W
"remote add -f #{forked_project.owner} #{url}"
1528 args
.after
'echo', ['new remote:', forked_project
.owner
]
1530 rescue GitHubAPI
::Exceptions
1531 display_api_exception("creating fork", $!
.response
)
1537 abort
"'create' must be run from inside a git repository"
1542 options
[:private] = true if args
.delete('-p')
1546 case arg
= args
.shift
1548 options
[:description] = args
.shift
1550 options
[:homepage] = args
.shift
1552 if arg
=~
/^[^-]/ and new_repo_name
.nil?
1554 owner
, new_repo_name
= new_repo_name
.split('/', 2) if new_repo_name
.index('/')
1556 abort
"invalid argument: #{arg}"
1560 new_repo_name
||= repo_name
1561 new_project
= github_project(new_repo_name
, owner
)
1563 if api_client
.repo_exists
?(new_project
)
1564 warn
"#{new_project.name_with_owner} already exists on #{new_project.host}"
1565 action
= "set remote origin"
1567 action
= "created repository"
1569 repo_data
= api_client
.create_repo(new_project
, options
)
1570 new_project
= github_project(repo_data
['full_name'])
1574 url
= new_project
.git_url(:private => true, :https => https_protocol
?)
1576 if remotes
.first !
= 'origin'
1577 args
.replace
%W
"remote add -f origin #{url}"
1579 args
.replace
%W
"remote -v"
1582 args
.after
'echo', ["#{action}:", new_project
.name_with_owner
]
1584 rescue GitHubAPI
::Exceptions
1585 display_api_exception("creating repository", $!
.response
)
1590 return if args
[1].nil? || !args
[1].index(',')
1592 refs
= args
.words
[2..-1]
1593 remotes
= args
[1].split(',')
1594 args
[1] = remotes
.shift
1597 refs
= [current_branch
.short_name
]
1601 remotes
.each
do |name
|
1602 args
.after
['push', name
, *refs
]
1608 browse_command(args
) do
1610 dest
= nil if dest
== '--'
1613 project
= github_project dest
1614 branch
= master_branch
1616 project
= current_project
1617 branch
= current_branch
&& current_branch
.upstream
|| master_branch
1620 abort
"Usage: hub browse [<USER>/]<REPOSITORY>" unless project
1622 path
= case subpage
= args
.shift
1624 "/commits/#{branch.short_name}"
1625 when 'tree', NilClass
1626 "/tree/#{branch.short_name}" if branch
and !branch
.master
?
1631 project
.web_url(path
)
1637 browse_command(args
) do
1639 branch
= current_branch
.upstream
1640 if branch
and not branch
.master
?
1641 range
= branch
.short_name
1642 project
= current_project
1644 abort
"Usage: hub compare [USER] [<START>...]<END>"
1647 sha_or_tag
= /(\w{1,2}|\w[\w.-]+\w)/
1648 range
= args
.pop
.sub(/^#{sha_or_tag}\.\.#{sha_or_tag}$/, '\1...\2')
1649 project
= if owner
= args
.pop
then github_project(nil, owner
)
1650 else current_project
1654 project
.web_url
"/compare/#{range}"
1659 return help(args
) unless args
[1] == 'standalone'
1660 require 'hub/standalone'
1661 Hub
::Standalone.build
$stdout
1664 abort
"hub is already running in standalone mode."
1666 exit
# ignore broken pipe
1670 shells
= %w
[bash zsh sh ksh csh fish
]
1672 script
= !!args
.delete('-s')
1673 shell
= args
[1] || ENV['SHELL']
1674 abort
"hub alias: unknown shell" if shell
.nil? or shell
.empty
?
1675 shell
= File
.basename shell
1677 unless shells
.include? shell
1678 $stderr.puts
"hub alias: unsupported shell"
1679 warn
"supported shells: #{shells.join(' ')}"
1684 puts
"alias git=hub"
1686 puts
"if type compdef >/dev/null; then"
1687 puts
" compdef hub=git"
1691 profile
= case shell
1692 when 'bash' then '~/.bash_profile'
1693 when 'zsh' then '~/.zshrc'
1694 when 'ksh' then '~/.profile'
1699 puts
"# Wrap git automatically by adding the following to #{profile}:"
1701 puts
'eval "$(hub alias -s)"'
1708 args
.after
'echo', ['hub version', Version
]
1710 alias_method
"--version", :version
1713 command
= args
.words
[1]
1718 elsif command
.nil? && !args
.has_flag
?('-a', '--all')
1719 ENV['GIT_PAGER'] = '' unless args
.has_flag
?('-p', '--paginate') # Use `cat`.
1720 puts improved_help_text
1724 alias_method
"--help", :help
1729 @api_client ||= begin
1730 config_file
= ENV['HUB_CONFIG'] || '~/.config/hub'
1731 file_store
= GitHubAPI
::FileStore.new File
.expand_path(config_file
)
1732 file_config
= GitHubAPI
::Configuration.new file_store
1733 GitHubAPI
.new file_config
, :app_url => 'http://defunkt.io/hub/'
1737 def github_user host
= nil, &block
1738 host
||= (local_repo(false) || Context
::LocalRepo).default_host
1739 api_client
.config
.username(host
, &block
)
1742 def custom_command
? cmd
1743 CUSTOM_COMMANDS
.include? cmd
1746 def respect_help_flags args
1747 return if args
.size
> 2
1750 pattern
= /(git|hub) #{Regexp.escape args[0].gsub('-', '\-')}/
1751 hub_raw_manpage
.each_line
{ |line
|
1753 $stderr.print
"Usage: "
1754 $stderr.puts line
.gsub(/\\f./, '').gsub('\-', '-')
1758 abort
"Error: couldn't find usage help for #{args[0]}"
1765 def improved_help_text
1767 usage: git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
1768 [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
1769 [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
1770 [-c name=value] [--help]
1774 init Create an empty git repository
or reinitialize an existing one
1775 add Add new
or modified files to the staging area
1776 rm Remove files from the working directory
and staging area
1777 mv Move
or rename a file
, a directory
, or a symlink
1778 status Show the status of the working directory
and staging area
1779 commit Record changes to the repository
1782 log Show the commit history log
1783 diff Show changes between commits
, commit
and working tree
, etc
1784 show Show information about commits
, tags
or files
1787 branch List
, create
, or delete branches
1788 checkout Switch the active branch to another branch
1789 merge Join two
or more development
histories (branches
) together
1790 tag Create
, list
, delete
, sign
or verify a tag object
1793 clone Clone a remote repository into a new directory
1794 fetch Download
data, tags
and branches from a remote repository
1795 pull Fetch from
and merge with another repository
or a local branch
1796 push Upload
data, tags
and branches to a remote repository
1797 remote View
and manage a set of remote repositories
1800 reset Reset your staging area
or working directory to another point
1801 rebase Re-apply a series of patches
in one branch onto another
1802 bisect Find by binary search the change that introduced a bug
1803 grep Print files with lines matching a pattern
in your codebase
1806 pull-request Open a pull request on GitHub
1807 fork Make a fork of a remote repository on GitHub
and add as remote
1808 create Create this repository on GitHub
and add GitHub as origin
1809 browse Open a GitHub page
in the default browser
1810 compare Open a compare page on GitHub
1812 See
'git help <command>' for more information on a specific command
.
1816 def slurp_global_flags(args
)
1817 flags
= %w
[ --noop
-c
-p
--paginate
--no-pager
--no-replace-objects
--bare
--version --help
]
1818 flags2
= %w
[ --exec-path
= --git-dir
= --work-tree
= ]
1823 while args
[0] && (flags
.include?(args
[0]) || flags2
.any
? {|f
| args
[0].index(f
) == 0 })
1828 when '--version', '--help'
1829 args
.unshift flag
.sub('--', '')
1831 config_pair
= args
.shift
1832 key
, value
= config_pair
.split('=', 2)
1833 git_reader
.stub_config_value(key
, value
)
1835 globals
<< flag
<< config_pair
1836 when '-p', '--paginate', '--no-pager'
1843 git_reader
.add_exec_flags(globals
)
1844 args
.add_exec_flags(globals
)
1845 args
.add_exec_flags(locals
)
1848 def browse_command(args
)
1849 url_only
= args
.delete('-u')
1850 warn
"Warning: the `-p` flag has no effect anymore" if args
.delete('-p')
1853 args
.executable
= url_only
? 'echo' : browser_launcher
1858 abort
"** Can't find groff(1)" unless command
?('groff')
1862 Open3
.popen3(groff_command
) do |stdin, stdout, _
|
1863 stdin.puts hub_raw_manpage
1865 out
= stdout.read
.strip
1871 "groff -Wall -mtty-char -mandoc -Tascii"
1875 if File
.exists
? file
= File
.dirname(__FILE__
) +
'/../../man/hub.1'
1888 return if not $stdout.tty
? or windows
?
1890 read
, write
= IO
.pipe
1897 ENV['LESS'] = 'FSRX'
1899 Kernel
.select
[STDIN]
1901 pager
= ENV['GIT_PAGER'] ||
1902 `git config --get-all core.pager`.split
.first
|| ENV['PAGER'] ||
1905 pager
= 'cat' if pager
.empty
?
1907 exec pager
rescue exec
"/bin/sh", "-c", pager
1909 $stdout.reopen(write
)
1910 $stderr.reopen(write
) if $stderr.tty
?
1914 rescue NotImplementedError
1917 def pullrequest_editmsg(changes
)
1918 message_file
= File
.join(git_dir
, 'PULLREQ_EDITMSG')
1919 File
.open(message_file
, 'w') { |msg
|
1922 msg
.puts
"#\n# Changes:\n#"
1923 msg
.puts changes
.gsub(/^/, '# ').gsub(/ +$/, '')
1926 edit_cmd
= Array(git_editor
).dup
1927 edit_cmd
<< '-c' << 'set ft=gitcommit' if edit_cmd
[0] =~
/^[mg]?vim$/
1928 edit_cmd
<< message_file
1930 abort
"can't open text editor for pull request message" unless $
?.success
?
1931 title
, body
= read_editmsg(message_file
)
1932 abort
"Aborting due to empty pull request title" unless title
1936 def read_editmsg(file
)
1937 title
, body
= '', ''
1938 File
.open(file
, 'r') { |msg
|
1939 msg
.each_line
do |line
|
1940 next if line
.index('#') == 0
1941 ((body
.empty
? and line
=~
/\S/) ? title
: body
) << line
1944 title
.tr!
("\n", ' ')
1948 [title
=~
/\S/ ? title
: nil, body
=~
/\S/ ? body
: nil]
1951 def expand_alias(cmd
)
1952 if expanded
= git_alias_for(cmd
)
1953 if expanded
.index('!') != 0
1954 require 'shellwords' unless defined?(::Shellwords)
1955 Shellwords
.shellwords(expanded
)
1960 def display_api_exception(action
, response
)
1961 $stderr.puts
"Error #{action}: #{response.message.strip} (HTTP #{response.status})"
1962 if 422 == response
.status
and response
.error_message
?
1963 msg
= response
.error_message
1964 msg
= msg
.join("\n") if msg
.respond_to
? :join
1976 def initialize(*args
)
1977 @args = Args
.new(args
)
1981 def self.execute(*args
)
1994 args
.commands
.map
do |cmd
|
1995 if cmd
.respond_to
?(:join)
1996 cmd
.map
{ |arg
| arg
= arg
.to_s
; (arg
.index(' ') || arg
.empty
?) ? "'#{arg}'" : arg
}.join(' ')
2006 elsif not args
.skip
?
2008 execute_command_chain
2015 def execute_command_chain
2016 commands
= args
.commands
2017 commands
.each_with_index
do |cmd
, i
|
2018 if cmd
.respond_to
?(:call) then cmd
.call
2019 elsif i
== commands
.length
- 1
2022 exit($
?.exitstatus
) unless system(*cmd
)
2029 Hub
::Runner.execute(*ARGV)
2032 .\" generated with Ronn
/v0
.7
.3
2033 .\" http
://github
.com
/rtomayko
/ronn
/tree/0.7.3
2035 .TH
"HUB" "1" "November 2012" "DEFUNKT" "Git Manual"
2038 \fBhub
\fR \
- git + hub
= github
2041 \fBhub
\fR
[\fB\
-\
-noop
\fR
] \fICOMMAND
\fR
\fIOPTIONS
\fR
2044 \fBhub
alias\fR
[\fB\
-s
\fR
] [\fISHELL
\fR
]
2046 .SS
"Expanded git commands:"
2047 \fBgit init \
-g
\fR
\fIOPTIONS
\fR
2050 \fBgit clone
\fR
[\fB\
-p
\fR
] \fIOPTIONS
\fR
[\fIUSER
\fR
/]\fIREPOSITORY
\fR
\fIDIRECTORY
\fR
2053 \fBgit remote add
\fR
[\fB\
-p
\fR
] \fIOPTIONS
\fR
\fIUSER
\fR
[/\fIREPOSITORY
\fR
]
2056 \fBgit remote set\
-url
\fR
[\fB\
-p
\fR
] \fIOPTIONS
\fR
\fIREMOTE\
-NAME
\fR
\fIUSER
\fR
[/\fIREPOSITORY
\fR
]
2059 \fBgit fetch
\fR
\fIUSER\
-1\fR
,[\fIUSER\
-2\fR
,\
.\
.\
.]
2062 \fBgit checkout
\fR
\fIPULLREQ\
-URL
\fR
[\fIBRANCH
\fR
]
2065 \fBgit merge
\fR
\fIPULLREQ\
-URL
\fR
2068 \fBgit cherry\
-pick
\fR
\fIGITHUB\
-REF
\fR
2071 \fBgit am
\fR
\fIGITHUB\
-URL
\fR
2074 \fBgit apply
\fR
\fIGITHUB\
-URL
\fR
2077 \fBgit push
\fR
\fIREMOTE\
-1\fR
,\fIREMOTE\
-2\fR
,\
.\
.\
.,\fIREMOTE\
-N
\fR
[\fIREF
\fR
]
2080 \fBgit submodule add
\fR
[\fB\
-p
\fR
] \fIOPTIONS
\fR
[\fIUSER
\fR
/]\fIREPOSITORY
\fR
\fIDIRECTORY
\fR
2082 .SS
"Custom git commands:"
2083 \fBgit create
\fR
[\fINAME
\fR
] [\fB\
-p
\fR
] [\fB\
-d
\fR
\fIDESCRIPTION
\fR
] [\fB\
-h
\fR
\fIHOMEPAGE
\fR
]
2086 \fBgit browse
\fR
[\fB\
-u
\fR
] [[\fIUSER
\fR
\fB
/\fR
]\fIREPOSITORY
\fR
] [SUBPAGE
]
2089 \fBgit compare
\fR
[\fB\
-u
\fR
] [\fIUSER
\fR
] [\fISTART
\fR\
.\
.\
.]\fIEND
\fR
2092 \fBgit fork
\fR
[\fB\
-\
-no\
-remote
\fR
]
2095 \fBgit pull\
-request
\fR
[\fB\
-f
\fR
] [\fITITLE
\fR
|\fB\
-i
\fR
\fIISSUE
\fR
] [\fB\
-b
\fR
\fIBASE
\fR
] [\fB\
-h
\fR
\fIHEAD
\fR
]
2098 hub enhances various git commands to ease most common workflows with GitHub\
.
2101 \fBhub \
-\
-noop
\fR
\fICOMMAND
\fR
2102 Shows which
command(s
) would be run as a result of the current command\
. Doesn
\'t perform anything\
.
2105 \fBhub
alias\fR
[\fB\
-s
\fR
] [\fISHELL
\fR
]
2106 Shows shell instructions
for wrapping git\
. If given
, \fISHELL
\fR specifies the type of shell
; otherwise defaults to the value of SHELL environment variable\
. With
\fB\
-s
\fR
, outputs shell script suitable
for \fBeval
\fR\
.
2109 \fBgit init
\fR
\fB\
-g
\fR
\fIOPTIONS
\fR
2110 Create a git repository as with git\
-init(1) and add remote
\fBorigin
\fR at
"git@github\.com:\fIUSER\fR/\fIREPOSITORY\fR\.git"; \fIUSER
\fR is your GitHub username
and \fIREPOSITORY
\fR is the current working directory
\'s basename\
.
2113 \fBgit clone
\fR
[\fB\
-p
\fR
] \fIOPTIONS
\fR
[\fIUSER
\fR
\fB
/\fR
]\fIREPOSITORY
\fR
\fIDIRECTORY
\fR
2114 Clone repository
"git://github\.com/\fIUSER\fR/\fIREPOSITORY\fR\.git" into
\fIDIRECTORY
\fR as with git\
-clone(1)\
. When
\fIUSER
\fR
/ is omitted
, assumes your GitHub login\
. With
\fB\
-p
\fR
, clone
private repositories over SSH\
. For repositories under your GitHub login
, \fB\
-p
\fR is implicit\
.
2117 \fBgit remote add
\fR
[\fB\
-p
\fR
] \fIOPTIONS
\fR
\fIUSER
\fR
[\fB
/\fR
\fIREPOSITORY
\fR
]
2118 Add remote
"git://github\.com/\fIUSER\fR/\fIREPOSITORY\fR\.git" as with git\
-remote(1)\
. When
/\fIREPOSITORY\fR is omitted, the basename of the current working directory is used\. With \fB\-p\fR, use private remote "git@github\.com:\fIUSER\fR/\fIREPOSITORY
\fR\
.git
"\. If \fIUSER\fR is "origin
" then uses your GitHub login\.
2121 \fBgit remote set\-url\fR [\fB\-p\fR] \fIOPTIONS\fR \fIREMOTE\-NAME\fR \fIUSER\fR[/\fIREPOSITORY\fR]
2122 Sets the url of remote \fIREMOTE\-NAME\fR using the same rules as \fBgit remote add\fR\.
2125 \fBgit fetch\fR \fIUSER\-1\fR,[\fIUSER\-2\fR,\.\.\.]
2126 Adds missing remote(s) with \fBgit remote add\fR prior to fetching\. New remotes are only added if they correspond to valid forks on GitHub\.
2129 \fBgit checkout\fR \fIPULLREQ\-URL\fR [\fIBRANCH\fR]
2130 Checks out the head of the pull request as a local branch, to allow for reviewing, rebasing and otherwise cleaning up the commits in the pull request before merging\. The name of the local branch can explicitly be set with \fIBRANCH\fR\.
2133 \fBgit merge\fR \fIPULLREQ\-URL\fR
2134 Merge the pull request with a commit message that includes the pull request ID and title, similar to the GitHub Merge Button\.
2137 \fBgit cherry\-pick\fR \fIGITHUB\-REF\fR
2138 Cherry\-pick a commit from a fork using either full URL to the commit or GitHub\-flavored Markdown notation, which is \fBuser@sha\fR\. If the remote doesn\'t yet exist, it will be added\. A \fBgit fetch <user>\fR is issued prior to the cherry\-pick attempt\.
2141 \fBgit [am|apply]\fR \fIGITHUB\-URL\fR
2142 Downloads the patch file for the pull request or commit at the URL and applies that patch from disk with \fBgit am\fR or \fBgit apply\fR\. Similar to \fBcherry\-pick\fR, but doesn\'t add new remotes\. \fBgit am\fR creates commits while preserving authorship info while \fBapply\fR only applies the patch to the working copy\.
2145 \fBgit push\fR \fIREMOTE\-1\fR,\fIREMOTE\-2\fR,\.\.\.,\fIREMOTE\-N\fR [\fIREF\fR]
2146 Push \fIREF\fR to each of \fIREMOTE\-1\fR through \fIREMOTE\-N\fR by executing multiple \fBgit push\fR commands\.
2149 \fBgit submodule add\fR [\fB\-p\fR] \fIOPTIONS\fR [\fIUSER\fR/]\fIREPOSITORY\fR \fIDIRECTORY\fR
2150 Submodule repository "git
://github\
.com
/\fIUSER
\fR
/\fIREPOSITORY
\fR\
.git
" into \fIDIRECTORY\fR as with git\-submodule(1)\. When \fIUSER\fR/ is omitted, assumes your GitHub login\. With \fB\-p\fR, use private remote "git
@github\
.com
:\fIUSER
\fR
/\fIREPOSITORY
\fR\
.git
"\.
2154 Display enhanced git\-help(1)\.
2157 hub also adds some custom commands that are otherwise not present in git:
2160 \fBgit create\fR [\fINAME\fR] [\fB\-p\fR] [\fB\-d\fR \fIDESCRIPTION\fR] [\fB\-h\fR \fIHOMEPAGE\fR]
2161 Create a new public GitHub repository from the current git repository and add remote \fBorigin\fR at "git
@github\
.com
:\fIUSER
\fR
/\fIREPOSITORY\fR\.git"; \fIUSER\fR is your GitHub username and \fIREPOSITORY\fR is the current working directory name\. To explicitly name the new repository, pass in \fINAME\fR, optionally in \fIORGANIZATION\fR/\fINAME
\fR form to create under an organization you
\'re a member of\
. With
\fB\
-p
\fR
, create a
private repository
, and with
\fB\
-d
\fR
and \fB\
-h
\fR set the repository
\'s description
and homepage URL
, respectively\
.
2164 \fBgit browse
\fR
[\fB\
-u
\fR
] [[\fIUSER
\fR
\fB
/\fR
]\fIREPOSITORY
\fR
] [SUBPAGE
]
2165 Open repository
\'s GitHub page
in the system
\'s default web browser using
\fBopen
(1)\fR
or the
\fBBROWSER
\fR
env variable\
. If the repository isn
\'t specified
, \fBbrowse
\fR opens the page of the repository found
in the current directory\
. If SUBPAGE is specified
, the browser will open on the specified subpage
: one of
"wiki", "commits", "issues" or other (the default is
"tree")\
.
2168 \fBgit compare
\fR
[\fB\
-u
\fR
] [\fIUSER
\fR
] [\fISTART
\fR\
.\
.\
.]\fIEND
\fR
2169 Open a GitHub compare view page
in the system
\'s default web browser\
. \fISTART
\fR to
\fIEND
\fR are branch names
, tag names
, or commit SHA1s specifying the range of history to compare\
. If a range with two
dots (\fBa\
.\
.b
\fR
) is given
, it will be transformed into one with three dots\
. If
\fISTART
\fR is omitted
, GitHub will compare against the base
branch (the default is
"master")\
.
2172 \fBgit fork
\fR
[\fB\
-\
-no\
-remote
\fR
]
2173 Forks the original
project (referenced by
"origin" remote
) on GitHub
and adds a new remote
for it under your username\
.
2176 \fBgit pull\
-request
\fR
[\fB\
-f
\fR
] [\fITITLE
\fR
|\fB\
-i
\fR
\fIISSUE
\fR
|\fIISSUE\
-URL
\fR
] [\fB\
-b
\fR
\fIBASE
\fR
] [\fB\
-h
\fR
\fIHEAD
\fR
]
2177 Opens a pull request on GitHub
for the project that the
"origin" remote points to\
. The default head of the pull request is the current branch\
. Both base
and head of the pull request can be explicitly given
in one of the following formats
: "branch", "owner:branch", "owner/repo:branch"\
. This command will abort operation
if it detects that the current topic branch has local commits that are
not yet pushed to its upstream branch on the remote\
. To skip this check
, use
\fB\
-f
\fR\
.
2180 If
\fITITLE
\fR is omitted
, a text editor will open
in which title
and body of the pull request can be entered
in the same manner as git commit message\
.
2183 If instead of normal
\fITITLE
\fR an issue number is given with
\fB\
-i
\fR
, the pull request will be attached to an existing GitHub issue\
. Alternatively
, instead of title you can paste a full URL to an issue on GitHub\
.
2186 Hub will prompt
for GitHub username
& password the first time it needs to access the API
and exchange it
for an OAuth token
, which it saves
in "~/\.config/hub"\
.
2189 To avoid being prompted
, use
\fIGITHUB_USER
\fR
and \fIGITHUB_PASSWORD
\fR environment variables\
.
2192 If you prefer the HTTPS protocol
for GitHub repositories
, you can set
"hub\.protocol" to
"https"\
. This will affect
\fBclone
\fR
, \fBfork
\fR
, \fBremote add
\fR
and other operations that expand references to GitHub repositories as full URLs that otherwise use git
and ssh protocols\
.
2198 $ git config \
-\
-global hub\
.protocol https
2204 .SS
"GitHub Enterprise"
2205 By default
, hub will only work with repositories that have remotes which point to github\
.com\
. GitHub Enterprise hosts need to be whitelisted to configure hub to treat such remotes same as github\
.com
:
2211 $ git config \
-\
-global \
-\
-add hub\
.host my\
.git\
.org
2218 The default host
for commands like
\fBinit
\fR
and \fBclone
\fR is still github\
.com
, but this can be affected with the
\fIGITHUB_HOST
\fR environment variable
:
2224 $ GITHUB_HOST
=my\
.git\
.org git clone myproject
2236 $ git clone schacon
/ticgit
2237 > git clone git
://github\
.com
/schacon
/ticgit\
.git
2239 $ git clone \
-p schacon
/ticgit
2240 > git clone git
@github\
.com
:schacon/ticgit\
.git
2243 > git clone git
@github\
.com
/YOUR_USER
/resque\
.git
2247 .SS
"git remote add"
2251 $ git remote add rtomayko
2252 > git remote add rtomayko git
://github\
.com
/rtomayko
/CURRENT_REPO\
.git
2254 $ git remote add \
-p rtomayko
2255 > git remote add rtomayko git
@github\
.com
:rtomayko/CURRENT_REPO\
.git
2257 $ git remote add origin
2258 > git remote add origin git
://github\
.com
/YOUR_USER
/CURRENT_REPO\
.git
2267 > git remote add mislav git
://github\
.com
/mislav
/REPO\
.git
2270 $ git fetch mislav
,xoebus
2271 > git remote add mislav \
.\
.\
.
2272 > git remote add xoebus \
.\
.\
.
2273 > git fetch \
-\
-multiple mislav xoebus
2277 .SS
"git cherry\-pick"
2281 $ git cherry\
-pick http
://github\
.com
/mislav
/REPO
/commit/SHA
2282 > git remote add \
-f mislav git
://github\
.com
/mislav
/REPO\
.git
2283 > git cherry\
-pick SHA
2285 $ git cherry\
-pick mislav
@SHA
2286 > git remote add \
-f mislav git
://github\
.com
/mislav
/CURRENT_REPO\
.git
2287 > git cherry\
-pick SHA
2289 $ git cherry\
-pick mislav
@SHA
2291 > git cherry\
-pick SHA
2295 .SS
"git am, git apply"
2299 $ git am https
://github\
.com
/defunkt
/hub
/pull/55
2300 > curl https
://github\
.com
/defunkt
/hub
/pull/55\
.patch \
-o
/tmp/55\
.patch
2301 > git am
/tmp/55\
.patch
2303 $ git am \
-\
-ignore\
-whitespace https
://github\
.com
/davidbalbert
/hub
/commit/fdb9921
2304 > curl https
://github\
.com
/davidbalbert
/hub
/commit/fdb9921\
.patch \
-o
/tmp/fdb9921\
.patch
2305 > git am \
-\
-ignore\
-whitespace
/tmp/fdb9921\
.patch
2307 $ git apply https
://gist\
.github\
.com
/8da7fb575debd88c54cf
2308 > curl https
://gist\
.github\
.com
/8da7fb575debd88c54cf\
.txt \
-o
/tmp
/gist\
-8da7fb575debd88c54cf\
.txt
2309 > git apply
/tmp/gist\
-8da7fb575debd88c54cf\
.txt
2318 [ repo forked on GitHub
]
2319 > git remote add \
-f YOUR_USER git
@github\
.com
:YOUR_USER/CURRENT_REPO\
.git
2323 .SS
"git pull\-request"
2327 # while on a topic branch called "feature":
2329 [ opens text editor to edit title
& body
for the request
]
2330 [ opened pull request on GitHub
for "YOUR_USER:feature" ]
2332 # explicit title, pull base & head:
2333 $ git pull\
-request
"I\'ve implemented feature X" \
-b defunkt
:master \
-h mislav
:feature
2335 $ git pull\
-request \
-i
123
2336 [ attached pull request to issue
#123 ]
2344 $ git checkout https
://github\
.com
/defunkt
/hub
/pull/73
2345 > git remote add \
-f \
-t feature git
://github
:com/mislav
/hub\
.git
2346 > git checkout \
-\
-track \
-B mislav\
-feature mislav
/feature
2348 $ git checkout https
://github\
.com
/defunkt
/hub
/pull/73 custom\
-branch\
-name
2356 $ git merge https
://github\
.com
/defunkt
/hub
/pull/73
2357 > git fetch git
://github\
.com
/mislav
/hub\
.git +refs
/heads
/feature
:refs/remotes
/mislav/feature
2358 > git merge mislav
/feature \-\-no\-ff \-m \'Merge pull request #73 from mislav/feature\
.\
.\
.\'
2367 [ repo created on GitHub
]
2368 > git remote add origin git
@github\
.com
:YOUR_USER/CURRENT_REPO\
.git
2371 $ git create \
-d
\'It shall be mine
, all mine!
\'
2373 $ git create recipes
2374 [ repo created on GitHub
]
2375 > git remote add origin git
@github\
.com
:YOUR_USER/recipes\
.git
2377 $ git create sinatra
/recipes
2378 [ repo created
in GitHub organization
]
2379 > git remote add origin git
@github\
.com
:sinatra/recipes\
.git
2389 > git remote add origin git
@github\
.com
:YOUR_USER/REPO\
.git
2397 $ git push origin
,staging
,qa bert_timeout
2398 > git push origin bert_timeout
2399 > git push staging bert_timeout
2400 > git push qa bert_timeout
2409 > open https
://github\
.com
/YOUR_USER
/CURRENT_REPO
2411 $ git browse \
-\
- commit
/SHA
2412 > open https
://github\
.com
/YOUR_USER
/CURRENT_REPO
/commit/SHA
2414 $ git browse \
-\
- issues
2415 > open https
://github\
.com
/YOUR_USER
/CURRENT_REPO
/issues
2417 $ git browse schacon
/ticgit
2418 > open https
://github\
.com
/schacon
/ticgit
2420 $ git browse schacon
/ticgit commit/SHA
2421 > open https
://github\
.com
/schacon
/ticgit
/commit/SHA
2424 > open https
://github\
.com
/YOUR_USER
/resque
2426 $ git browse resque network
2427 > open https
://github\
.com
/YOUR_USER
/resque
/network
2435 $ git compare refactor
2436 > open https
://github\
.com
/CURRENT_REPO
/compare
/refactor
2438 $ git compare
1\
.0\
.\
.1\
.1
2439 > open https
://github\
.com
/CURRENT_REPO
/compare
/1\
.0\
.\
.\
.1\
.1
2441 $ git compare \
-u fix
2442 > (https
://github\
.com
/CURRENT_REPO
/compare
/fix
)
2444 $ git compare other\
-user patch
2445 > open https
://github\
.com
/other\
-user
/REPO
/compare/patch
2453 $ hub submodule add wycats
/bundler vendor/bundler
2454 > git submodule add git
://github\
.com
/wycats
/bundler\
.git vendor
/bundler
2456 $ hub submodule add \
-p wycats
/bundler vendor/bundler
2457 > git submodule add git
@github\
.com
:wycats/bundler\.git vendor/bundler
2459 $ hub submodule add \
-b ryppl ryppl
/pip vendor/pip
2460 > git submodule add \
-b ryppl git
://github\
.com
/ryppl
/pip\
.git vendor
/pip
2469 > (improved git help
)
2476 \fIhttps
://github\
.com
/defunkt
/hub
/issues
\fR
2479 \fIhttps
://github\
.com
/defunkt
/hub
/contributors
\fR
2482 git(1), git\
-clone(1), git\
-remote(1), git\
-init(1), \fIhttp
://github\
.com
\fR
, \fIhttps
://github\
.com
/defunkt
/hub
\fR