hub 1.10.6 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of hub might be problematic. Click here for more details.

data/README.md CHANGED
@@ -31,16 +31,21 @@ Installing on OS X is easiest with Homebrew:
31
31
  $ brew install hub
32
32
  ~~~
33
33
 
34
- ### Standalone
34
+ ### `rake install` from source
35
35
 
36
- `hub` is easily installed as a standalone script:
36
+ This is the preferred installation method when no package manager that
37
+ supports hub is available:
37
38
 
38
39
  ~~~ sh
39
- $ curl http://defunkt.io/hub/standalone -sLo ~/bin/hub &&
40
- chmod +x ~/bin/hub
40
+ # Download or clone the project from GitHub:
41
+ $ git clone git://github.com/github/hub.git
42
+ $ cd hub
43
+ $ rake install
41
44
  ~~~
42
45
 
43
- Assuming "~/bin/" is in your `$PATH`, you're ready to roll:
46
+ On a Unix-based OS, this installs under `PREFIX`, which is `/usr/local` by default.
47
+
48
+ Now you should be ready to roll:
44
49
 
45
50
  ~~~ sh
46
51
  $ hub version
@@ -48,12 +53,9 @@ git version 1.7.6
48
53
  hub version 1.8.3
49
54
  ~~~
50
55
 
51
- #### On Windows
56
+ #### Windows "Git Bash" (msysGit) note
52
57
 
53
- If you have mysysgit, open "Git Bash" and follow the steps above but put the
54
- `hub` executable in `/bin` instead of `~/bin`.
55
-
56
- Avoid aliasing hub as `git` due to the fact that mysysgit automatically
58
+ Avoid aliasing hub as `git` due to the fact that msysGit automatically
57
59
  configures your prompt to include git information, and you want to avoid slowing
58
60
  that down. See [Is your shell prompt slow?](#is-your-shell-prompt-slow)
59
61
 
@@ -78,16 +80,6 @@ $ hub hub standalone > ~/bin/hub && chmod +x ~/bin/hub
78
80
  This installs a standalone version which doesn't require RubyGems to
79
81
  run, so it's faster.
80
82
 
81
- ### Source
82
-
83
- You can also install from source:
84
-
85
- ~~~ sh
86
- $ git clone git://github.com/defunkt/hub.git
87
- $ cd hub
88
- $ rake install prefix=/usr/local
89
- ~~~
90
-
91
83
  ### Help! It's slow!
92
84
 
93
85
  #### Is `hub` noticeably slower than plain git?
@@ -153,8 +145,8 @@ eval "$(hub alias -s)"
153
145
  hub repository contains tab-completion scripts for bash and zsh. These scripts
154
146
  complement existing completion scripts that ship with git.
155
147
 
156
- * [hub bash completion](https://github.com/defunkt/hub/blob/master/etc/hub.bash_completion.sh)
157
- * [hub zsh completion](https://github.com/defunkt/hub/blob/master/etc/hub.zsh_completion)
148
+ * [hub bash completion](https://github.com/github/hub/blob/master/etc/hub.bash_completion.sh)
149
+ * [hub zsh completion](https://github.com/github/hub/blob/master/etc/hub.zsh_completion)
158
150
 
159
151
 
160
152
  Commands
@@ -213,15 +205,15 @@ superpowers:
213
205
  ### git am, git apply
214
206
 
215
207
  $ git am https://github.com/defunkt/hub/pull/55
216
- > curl https://github.com/defunkt/hub/pull/55.patch -o /tmp/55.patch
208
+ [ downloads patch via API ]
217
209
  > git am /tmp/55.patch
218
210
 
219
211
  $ git am --ignore-whitespace https://github.com/davidbalbert/hub/commit/fdb9921
220
- > curl https://github.com/davidbalbert/hub/commit/fdb9921.patch -o /tmp/fdb9921.patch
212
+ [ downloads patch via API ]
221
213
  > git am --ignore-whitespace /tmp/fdb9921.patch
222
214
 
223
215
  $ git apply https://gist.github.com/8da7fb575debd88c54cf
224
- > curl https://gist.github.com/8da7fb575debd88c54cf.txt -o /tmp/gist-8da7fb575debd88c54cf.txt
216
+ [ downloads patch via API ]
225
217
  > git apply /tmp/gist-8da7fb575debd88c54cf.txt
226
218
 
227
219
  ### git fork
@@ -238,10 +230,7 @@ superpowers:
238
230
  [ opened pull request on GitHub for "YOUR_USER:feature" ]
239
231
 
240
232
  # explicit title, pull base & head:
241
- $ git pull-request "I've implemented feature X" -b defunkt:master -h mislav:feature
242
-
243
- $ git pull-request -i 123
244
- [ attached pull request to issue #123 ]
233
+ $ git pull-request -m "Implemented feature X" -b defunkt:master -h mislav:feature
245
234
 
246
235
  ### git checkout
247
236
 
@@ -326,15 +315,21 @@ superpowers:
326
315
 
327
316
  ### git submodule
328
317
 
329
- $ hub submodule add wycats/bundler vendor/bundler
318
+ $ git submodule add wycats/bundler vendor/bundler
330
319
  > git submodule add git://github.com/wycats/bundler.git vendor/bundler
331
320
 
332
- $ hub submodule add -p wycats/bundler vendor/bundler
321
+ $ git submodule add -p wycats/bundler vendor/bundler
333
322
  > git submodule add git@github.com:wycats/bundler.git vendor/bundler
334
323
 
335
- $ hub submodule add -b ryppl --name pip ryppl/pip vendor/pip
324
+ $ git submodule add -b ryppl --name pip ryppl/pip vendor/pip
336
325
  > git submodule add -b ryppl --name pip git://github.com/ryppl/pip.git vendor/pip
337
326
 
327
+ ### git ci-status
328
+
329
+ $ git ci-status [commit]
330
+ > (prints CI state of commit and exits with appropriate code)
331
+ > One of: success (0), error (1), failure (1), pending (2), no status (3)
332
+
338
333
 
339
334
  ### git help
340
335
 
@@ -369,40 +364,13 @@ $ git clone defunkt/repl
369
364
  ~~~
370
365
 
371
366
 
372
- Contributing
373
- ------------
374
-
375
- These instructions assume that _you already have hub installed_ and aliased as
376
- `git` (see "Aliasing").
377
-
378
- 1. Clone hub:
379
- `git clone defunkt/hub && cd hub`
380
- 1. Ensure Bundler is installed:
381
- `which bundle || gem install bundler`
382
- 1. Install development dependencies:
383
- `bundle install`
384
- 2. Verify that existing tests pass:
385
- `bundle exec rake`
386
- 3. Create a topic branch:
387
- `git checkout -b feature`
388
- 4. **Make your changes.** (It helps a lot if you write tests first.)
389
- 5. Verify that tests still pass:
390
- `bundle exec rake`
391
- 6. Fork hub on GitHub (adds a remote named "YOUR_USER"):
392
- `git fork`
393
- 7. Push to your fork:
394
- `git push -u YOUR_USER feature`
395
- 8. Open a pull request describing your changes:
396
- `git pull-request`
397
-
398
-
399
367
  Meta
400
368
  ----
401
369
 
402
- * Home: <https://github.com/defunkt/hub>
403
- * Bugs: <https://github.com/defunkt/hub/issues>
370
+ * Home: <https://github.com/github/hub>
371
+ * Bugs: <https://github.com/github/hub/issues>
404
372
  * Gem: <https://rubygems.org/gems/hub>
405
- * Authors: <https://github.com/defunkt/hub/contributors>
373
+ * Authors: <https://github.com/github/hub/contributors>
406
374
 
407
375
  ### Prior art
408
376
 
data/Rakefile CHANGED
@@ -7,6 +7,7 @@ require 'rake/testtask'
7
7
  def command?(util)
8
8
  Rake::Task[:load_path].invoke
9
9
  context = Object.new
10
+ require 'uri'
10
11
  require 'hub/context'
11
12
  context.extend Hub::Context
12
13
  context.send(:command?, util)
@@ -31,9 +32,7 @@ task :default => [:test, :features]
31
32
 
32
33
  Rake::TestTask.new do |t|
33
34
  t.libs << 'test'
34
- t.ruby_opts << '-rubygems'
35
35
  t.pattern = 'test/**/*_test.rb'
36
- t.verbose = false
37
36
  end
38
37
 
39
38
  task :features do
@@ -71,7 +70,7 @@ if command? :ronn
71
70
 
72
71
  # generate man page with ronn
73
72
  compile_ronn = lambda { |destination, type, contents|
74
- File.popen("ronn --pipe --#{type} --organization=DEFUNKT --manual='Git Manual'", 'w+') { |io|
73
+ File.popen("ronn --pipe --#{type} --organization=GITHUB --manual='Hub Manual'", 'w+') { |io|
75
74
  io.write contents
76
75
  io.close_write
77
76
  File.open(destination, 'w') { |f| f << io.read }
@@ -97,6 +96,7 @@ end
97
96
 
98
97
  file "hub" => FileList.new("lib/hub.rb", "lib/hub/*.rb", "man/hub.1") do |task|
99
98
  Rake::Task[:load_path].invoke
99
+ require 'hub/version'
100
100
  require 'hub/standalone'
101
101
  Hub::Standalone.save(task.name)
102
102
  end
@@ -104,15 +104,24 @@ end
104
104
  desc "Build standalone script"
105
105
  task :standalone => "hub"
106
106
 
107
- desc "Install standalone script and man pages"
107
+ desc %{Install standalone script and man page.
108
+ On Unix-based OS, installs into PREFIX (default: `/usr/local`).
109
+ On Windows, installs into Ruby's main bin directory.}
108
110
  task :install => "hub" do
109
- prefix = ENV['PREFIX'] || ENV['prefix'] || '/usr/local'
110
-
111
- FileUtils.mkdir_p "#{prefix}/bin"
112
- FileUtils.cp "hub", "#{prefix}/bin", :preserve => true
113
-
114
- FileUtils.mkdir_p "#{prefix}/share/man/man1"
115
- FileUtils.cp "man/hub.1", "#{prefix}/share/man/man1"
111
+ require 'rbconfig'
112
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
113
+ bindir = RbConfig::CONFIG['bindir']
114
+ File.open(File.join(bindir, 'hub.bat'), 'w') { |f| f.write('@"ruby.exe" "%~dpn0" %*') }
115
+ FileUtils.cp 'hub', bindir
116
+ else
117
+ prefix = ENV['PREFIX'] || ENV['prefix'] || '/usr/local'
118
+
119
+ FileUtils.mkdir_p "#{prefix}/bin"
120
+ FileUtils.cp "hub", "#{prefix}/bin", :preserve => true
121
+
122
+ FileUtils.mkdir_p "#{prefix}/share/man/man1"
123
+ FileUtils.cp "man/hub.1", "#{prefix}/share/man/man1"
124
+ end
116
125
  end
117
126
 
118
127
  #
@@ -152,17 +161,17 @@ task :homebrew do
152
161
  sh 'git pull -q origin master'
153
162
 
154
163
  formula_file = 'Library/Formula/hub.rb'
155
- sha = `curl -#L https://github.com/defunkt/hub/tarball/v#{Hub::VERSION} | shasum`.split(/\s+/).first
164
+ sha = `curl -fsSL https://github.com/github/hub/archive/v#{Hub::VERSION}.tar.gz | shasum`.split(/\s+/).first
156
165
  abort unless $?.success? and sha.length == 40
157
166
 
158
167
  formula = File.read formula_file
159
- formula.sub! /\bv\d+(\.\d+)*/, "v#{Hub::VERSION}"
160
- formula.sub! /\b[0-9a-f]{40}\b/, sha
168
+ formula.sub!(/\bv\d+(\.\d+)*/, "v#{Hub::VERSION}")
169
+ formula.sub!(/\b[0-9a-f]{40}\b/, sha)
161
170
  File.open(formula_file, 'w') {|f| f << formula }
162
171
 
163
172
  branch = "hub-v#{Hub::VERSION}"
164
173
  sh "git checkout -q -B #{branch}"
165
- sh "git commit -m 'upgrade hub to v#{Hub::VERSION}' -- #{formula_file}"
174
+ sh "git commit -m 'hub v#{Hub::VERSION}' -- #{formula_file}"
166
175
  sh "git push -u mislav #{branch}"
167
176
  sh "hub pull-request 'upgrade hub to v#{Hub::VERSION}'"
168
177
 
data/bin/bench ADDED
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ iterations=20
5
+
6
+ ruby="$(rbenv which ruby)"
7
+ ruby_args="--disable-gems"
8
+
9
+ unset RUBYLIB
10
+ unset RUBYOPT
11
+
12
+ export TIMEFORMAT='%3R'
13
+
14
+ time_ruby() {
15
+ (time "$ruby" $ruby_args "$@" >/dev/null) 2>&1
16
+ }
17
+
18
+ time_plain() {
19
+ (time "$@" >/dev/null) 2>&1
20
+ }
21
+
22
+ measure() {
23
+ local count="$iterations"
24
+ echo "$@"
25
+ { while [ "$count" -gt 0 ]; do
26
+ # time_ruby "$@"
27
+ time_plain "$@"
28
+ count=$((count - 1))
29
+ done
30
+ } | stdev
31
+ }
32
+
33
+ /usr/bin/ruby -v
34
+ measure tmp/hub-slow version
35
+ measure tmp/hub-slow browse -u
36
+ measure tmp/hub-fast version
37
+ measure tmp/hub-fast browse -u
data/lib/hub.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'hub/version' unless defined?(Hub::VERSION)
2
+ require 'hub/speedy_stdlib'
2
3
  require 'hub/args'
3
4
  require 'hub/ssh_config'
4
5
  require 'hub/github_api'
data/lib/hub/commands.rb CHANGED
@@ -38,7 +38,7 @@ module Hub
38
38
  OWNER_RE = /[a-zA-Z0-9][a-zA-Z0-9-]*/
39
39
  NAME_WITH_OWNER_RE = /^(?:#{NAME_RE}|#{OWNER_RE}\/#{NAME_RE})$/
40
40
 
41
- CUSTOM_COMMANDS = %w[alias create browse compare fork pull-request]
41
+ CUSTOM_COMMANDS = %w[alias create browse compare fork pull-request ci-status]
42
42
 
43
43
  def run(args)
44
44
  slurp_global_flags(args)
@@ -70,16 +70,58 @@ module Hub
70
70
  abort "fatal: #{err.message}"
71
71
  end
72
72
 
73
+
74
+ # $ hub ci-status
75
+ # $ hub ci-status 6f6d9797f9d6e56c3da623a97cfc3f45daf9ae5f
76
+ # $ hub ci-status master
77
+ # $ hub ci-status origin/master
78
+ def ci_status(args)
79
+ args.shift
80
+ ref = args.words.first || 'HEAD'
81
+ verbose = args.include?('-v')
82
+
83
+ unless project = local_repo.main_project
84
+ abort "Aborted: the origin remote doesn't point to a GitHub repository."
85
+ end
86
+
87
+ unless sha = local_repo.git_command("rev-parse -q #{ref}")
88
+ abort "Aborted: no revision could be determined from '#{ref}'"
89
+ end
90
+
91
+ statuses = api_client.statuses(project, sha)
92
+ status = statuses.first
93
+ if status
94
+ ref_state = status['state']
95
+ ref_target_url = status['target_url']
96
+ else
97
+ ref_state = 'no status'
98
+ ref_target_url = nil
99
+ end
100
+
101
+ exit_code = case ref_state
102
+ when 'success' then 0
103
+ when 'failure', 'error' then 1
104
+ when 'pending' then 2
105
+ else 3
106
+ end
107
+
108
+ if verbose and ref_target_url
109
+ $stdout.puts "%s: %s" % [ref_state, ref_target_url]
110
+ else
111
+ $stdout.puts ref_state
112
+ end
113
+ exit exit_code
114
+ end
115
+
73
116
  # $ hub pull-request
74
117
  # $ hub pull-request "My humble contribution"
75
- # $ hub pull-request -i 92
76
118
  # $ hub pull-request https://github.com/rtomayko/tilt/issues/92
77
119
  def pull_request(args)
78
120
  args.shift
79
121
  options = { }
80
122
  force = explicit_owner = false
81
123
  base_project = local_repo.main_project
82
- head_project = local_repo.current_project
124
+ tracked_branch, head_project = remote_branch_and_project(method(:github_user))
83
125
 
84
126
  unless current_branch
85
127
  abort "Aborted: not currently on any branch."
@@ -101,6 +143,13 @@ module Hub
101
143
  case arg
102
144
  when '-f'
103
145
  force = true
146
+ when '-F', '--file'
147
+ file = args.shift
148
+ text = file == '-' ? $stdin.read : File.read(file)
149
+ options[:title], options[:body] = read_msg(text)
150
+ when '-m', '--message'
151
+ text = args.shift
152
+ options[:title], options[:body] = read_msg(text)
104
153
  when '-b'
105
154
  base_project, options[:base] = from_github_ref.call(args.shift, base_project)
106
155
  when '-h'
@@ -113,17 +162,24 @@ module Hub
113
162
  if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/
114
163
  options[:issue] = $1
115
164
  base_project = url.project
116
- elsif !options[:title] then options[:title] = arg
165
+ elsif !options[:title]
166
+ options[:title] = arg
167
+ warn "hub: Specifying pull request title without a flag is deprecated."
168
+ warn "Please use one of `-m' or `-F' options."
117
169
  else
118
170
  abort "invalid argument: #{arg}"
119
171
  end
120
172
  end
121
173
  end
122
174
 
175
+ if options[:issue]
176
+ warn "Warning: Issue to pull request conversion is deprecated and might not work in the future."
177
+ end
178
+
123
179
  options[:project] = base_project
124
180
  options[:base] ||= master_branch.short_name
125
181
 
126
- if tracked_branch = options[:head].nil? && current_branch.upstream
182
+ if options[:head].nil? && tracked_branch
127
183
  if !tracked_branch.remote?
128
184
  # The current branch is tracking another local branch. Pretend there is
129
185
  # no upstream configuration at all.
@@ -136,12 +192,6 @@ module Hub
136
192
  end
137
193
  options[:head] ||= (tracked_branch || current_branch).short_name
138
194
 
139
- # when no tracking, assume remote branch is published under active user's fork
140
- user = github_user(head_project.host)
141
- if head_project.owner != user and !tracked_branch and !explicit_owner
142
- head_project = head_project.owned_by(user)
143
- end
144
-
145
195
  remote_branch = "#{head_project.remote}/#{options[:head]}"
146
196
  options[:head] = "#{head_project.owner}:#{options[:head]}"
147
197
 
@@ -174,8 +224,9 @@ module Hub
174
224
  [format, base_branch, remote_branch]
175
225
  end
176
226
 
177
- options[:title], options[:body] = pullrequest_editmsg(commit_summary) { |msg|
178
- msg.puts default_message if default_message
227
+ options[:title], options[:body] = pullrequest_editmsg(commit_summary) { |msg, initial_message|
228
+ initial_message ||= default_message
229
+ msg.puts initial_message if initial_message
179
230
  msg.puts ""
180
231
  msg.puts "# Requesting a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
181
232
  msg.puts "#"
@@ -196,8 +247,16 @@ module Hub
196
247
  warn "Are you sure that #{base_url} exists?"
197
248
  end
198
249
  exit 1
250
+ else
251
+ delete_editmsg
199
252
  end
200
253
 
254
+ # $ hub e-note
255
+ # $ hub e-note "My humble contribution"
256
+ # $ hub e-note -i 92
257
+ # $ hub e-note https://github.com/rtomayko/tilt/issues/92
258
+ alias_method :e_note, :pull_request
259
+
201
260
  # $ hub clone rtomayko/tilt
202
261
  # > git clone git://github.com/rtomayko/tilt.
203
262
  #
@@ -225,7 +284,10 @@ module Hub
225
284
  name, owner = arg, nil
226
285
  owner, name = name.split('/', 2) if name.index('/')
227
286
  project = github_project(name, owner || github_user)
228
- ssh ||= args[0] != 'submodule' && project.owner == github_user(project.host) { }
287
+ unless ssh || args[0] == 'submodule' || args.noop? || https_protocol?
288
+ repo_info = api_client.repo_info(project)
289
+ ssh = repo_info.success? && (repo_info.data['private'] || repo_info.data['permissions']['push'])
290
+ end
229
291
  args[idx] = project.git_url(:private => ssh, :https => https_protocol?)
230
292
  end
231
293
  break
@@ -381,8 +443,9 @@ module Hub
381
443
 
382
444
  idx = args.index url_arg
383
445
  args.delete_at idx
384
- args.insert idx, merge_head, '--no-ff', '-m',
385
- "Merge pull request ##{pull_id} from #{merge_head}\n\n#{pull_data['title']}"
446
+ args.insert idx, merge_head, '-m', "Merge pull request ##{pull_id} from #{merge_head}\n\n#{pull_data['title']}"
447
+ idx = args.index '-m'
448
+ args.insert idx, '--no-ff' unless args.include?('--ff-only')
386
449
  end
387
450
  end
388
451
 
@@ -421,27 +484,40 @@ module Hub
421
484
  end
422
485
 
423
486
  # $ hub am https://github.com/defunkt/hub/pull/55
424
- # > curl https://github.com/defunkt/hub/pull/55.patch -o /tmp/55.patch
487
+ # ... downloads patch via API ...
425
488
  # > git am /tmp/55.patch
426
489
  def am(args)
427
490
  if url = args.find { |a| a =~ %r{^https?://(gist\.)?github\.com/} }
428
491
  idx = args.index(url)
429
- gist = $1 == 'gist.'
430
- # strip the fragment part of the url
431
- url = url.sub(/#.+/, '')
432
- # strip extra path from "pull/42/files", "pull/42/commits"
433
- url = url.sub(%r{(/pull/\d+)/\w*$}, '\1') unless gist
434
- ext = gist ? '.txt' : '.patch'
435
- url += ext unless File.extname(url) == ext
436
- patch_file = File.join(tmp_dir, "#{gist ? 'gist-' : ''}#{File.basename(url)}")
437
- # TODO: remove dependency on curl
438
- args.before 'curl', ['-#LA', "hub #{Hub::Version}", url, '-o', patch_file]
492
+ if $1 == 'gist.'
493
+ path_parts = $'.sub(/#.*/, '').split('/')
494
+ gist_id = path_parts.last
495
+ patch_name = "gist-#{gist_id}.txt"
496
+ patch = api_client.gist_raw(gist_id)
497
+ else
498
+ gh_url = resolve_github_url(url)
499
+ case gh_url.project_path
500
+ when /^pull\/(\d+)/
501
+ pull_id = $1.to_i
502
+ patch_name = "#{pull_id}.patch"
503
+ patch = api_client.pullrequest_patch(gh_url.project, pull_id)
504
+ when /^commit\/([a-f0-9]{7,40})/
505
+ commit_sha = $1
506
+ patch_name = "#{commit_sha}.patch"
507
+ patch = api_client.commit_patch(gh_url.project, commit_sha)
508
+ else
509
+ raise ArgumentError, url
510
+ end
511
+ end
512
+
513
+ patch_file = File.join(tmp_dir, patch_name)
514
+ File.open(patch_file, 'w') { |file| file.write(patch) }
439
515
  args[idx] = patch_file
440
516
  end
441
517
  end
442
518
 
443
519
  # $ hub apply https://github.com/defunkt/hub/pull/55
444
- # > curl https://github.com/defunkt/hub/pull/55.patch -o /tmp/55.patch
520
+ # ... downloads patch via API ...
445
521
  # > git apply /tmp/55.patch
446
522
  alias_method :apply, :am
447
523
 
@@ -594,8 +670,8 @@ module Hub
594
670
  branch = master_branch
595
671
  else
596
672
  # $ hub browse
597
- project = current_project
598
- branch = current_branch && current_branch.upstream || master_branch
673
+ branch, project = remote_branch_and_project(method(:github_user))
674
+ branch ||= master_branch
599
675
  end
600
676
 
601
677
  abort "Usage: hub browse [<USER>/]<REPOSITORY>" unless project
@@ -603,9 +679,9 @@ module Hub
603
679
  # $ hub browse -- wiki
604
680
  path = case subpage = args.shift
605
681
  when 'commits'
606
- "/commits/#{branch.short_name}"
682
+ "/commits/#{branch_in_url(branch)}"
607
683
  when 'tree', NilClass
608
- "/tree/#{branch.short_name}" if branch and !branch.master?
684
+ "/tree/#{branch_in_url(branch)}" if branch and !branch.master?
609
685
  else
610
686
  "/#{subpage}"
611
687
  end
@@ -625,11 +701,10 @@ module Hub
625
701
  def compare(args)
626
702
  args.shift
627
703
  browse_command(args) do
704
+ branch, project = remote_branch_and_project(method(:github_user))
628
705
  if args.empty?
629
- branch = current_branch.upstream
630
706
  if branch and not branch.master?
631
707
  range = branch.short_name
632
- project = current_project
633
708
  else
634
709
  abort "Usage: hub compare [USER] [<START>...]<END>"
635
710
  end
@@ -637,9 +712,9 @@ module Hub
637
712
  sha_or_tag = /((?:#{OWNER_RE}:)?\w[\w.-]+\w)/
638
713
  # replaces two dots with three: "sha1...sha2"
639
714
  range = args.pop.sub(/^#{sha_or_tag}\.\.#{sha_or_tag}$/, '\1...\2')
640
- project = if owner = args.pop then github_project(nil, owner)
641
- else current_project
642
- end
715
+ if owner = args.pop
716
+ project = project.owned_by(owner)
717
+ end
643
718
  end
644
719
 
645
720
  project.web_url "/compare/#{range}"
@@ -680,23 +755,23 @@ module Hub
680
755
 
681
756
  if script
682
757
  puts "alias git=hub"
683
- if 'zsh' == shell
684
- puts "if type compdef >/dev/null; then"
685
- puts " compdef hub=git"
686
- puts "fi"
687
- end
688
758
  else
689
759
  profile = case shell
690
760
  when 'bash' then '~/.bash_profile'
691
761
  when 'zsh' then '~/.zshrc'
692
762
  when 'ksh' then '~/.profile'
763
+ when 'fish' then '~/.config/fish/config.fish'
693
764
  else
694
765
  'your profile'
695
766
  end
696
767
 
697
768
  puts "# Wrap git automatically by adding the following to #{profile}:"
698
769
  puts
699
- puts 'eval "$(hub alias -s)"'
770
+ if shell == 'fish'
771
+ puts 'eval (hub alias -s)'
772
+ else
773
+ puts 'eval "$(hub alias -s)"'
774
+ end
700
775
  end
701
776
 
702
777
  exit
@@ -715,13 +790,19 @@ module Hub
715
790
  def help(args)
716
791
  command = args.words[1]
717
792
 
718
- if command == 'hub'
793
+ if command == 'hub' || custom_command?(command)
719
794
  puts hub_manpage
720
795
  exit
721
- elsif command.nil? && !args.has_flag?('-a', '--all')
722
- ENV['GIT_PAGER'] = '' unless args.has_flag?('-p', '--paginate') # Use `cat`.
723
- puts improved_help_text
724
- exit
796
+ elsif command.nil?
797
+ if args.has_flag?('-a', '--all')
798
+ # Add the special hub commands to the end of "git help -a" output.
799
+ args.after 'echo', ["\nhub custom commands\n"]
800
+ args.after 'echo', CUSTOM_COMMANDS.map {|cmd| " #{cmd}" }
801
+ else
802
+ ENV['GIT_PAGER'] = '' unless args.has_flag?('-p', '--paginate') # Use `cat`.
803
+ puts improved_help_text
804
+ exit
805
+ end
725
806
  end
726
807
  end
727
808
  alias_method "--help", :help
@@ -732,18 +813,22 @@ module Hub
732
813
  # from the command line.
733
814
  #
734
815
 
816
+ def branch_in_url(branch)
817
+ CGI.escape(branch.short_name).gsub("%2F", "/")
818
+ end
819
+
735
820
  def api_client
736
821
  @api_client ||= begin
737
822
  config_file = ENV['HUB_CONFIG'] || '~/.config/hub'
738
823
  file_store = GitHubAPI::FileStore.new File.expand_path(config_file)
739
824
  file_config = GitHubAPI::Configuration.new file_store
740
- GitHubAPI.new file_config, :app_url => 'http://defunkt.io/hub/'
825
+ GitHubAPI.new file_config, :app_url => 'http://hub.github.com/'
741
826
  end
742
827
  end
743
828
 
744
829
  def github_user host = nil, &block
745
830
  host ||= (local_repo(false) || Context::LocalRepo).default_host
746
- api_client.config.username(host, &block)
831
+ api_client.username_via_auth_dance(host, &block)
747
832
  end
748
833
 
749
834
  def custom_command? cmd
@@ -818,6 +903,7 @@ GitHub Commands:
818
903
  create Create this repository on GitHub and add GitHub as origin
819
904
  browse Open a GitHub page in the default browser
820
905
  compare Open a compare page on GitHub
906
+ ci-status Show the CI status of a commit
821
907
 
822
908
  See 'git help <command>' for more information on a specific command.
823
909
  help
@@ -900,7 +986,8 @@ help
900
986
  # in order to turn our raw roff (manpage markup) into something
901
987
  # readable on the terminal.
902
988
  def groff_command
903
- "groff -Wall -mtty-char -mandoc -Tascii"
989
+ cols = terminal_width
990
+ "groff -Wall -mtty-char -mandoc -Tascii -rLL=#{cols}n -rLT=#{cols}n"
904
991
  end
905
992
 
906
993
  # Returns the raw hub manpage. If we're not running in standalone
@@ -944,7 +1031,8 @@ help
944
1031
  Kernel.select [STDIN]
945
1032
 
946
1033
  pager = ENV['GIT_PAGER'] ||
947
- `git config --get-all core.pager`.split.first || ENV['PAGER'] ||
1034
+ `git config --get-all core.pager`.split("\n").first ||
1035
+ ENV['PAGER'] ||
948
1036
  'less -isr'
949
1037
 
950
1038
  pager = 'cat' if pager.empty?
@@ -962,24 +1050,53 @@ help
962
1050
  end
963
1051
 
964
1052
  def pullrequest_editmsg(changes)
965
- message_file = File.join(git_dir, 'PULLREQ_EDITMSG')
1053
+ message_file = pullrequest_editmsg_file
1054
+
1055
+ if valid_editmsg_file?(message_file)
1056
+ title, body = read_editmsg(message_file)
1057
+ previous_message = [title, body].compact.join("\n\n") if title
1058
+ end
1059
+
966
1060
  File.open(message_file, 'w') { |msg|
967
- yield msg
1061
+ yield msg, previous_message
968
1062
  if changes
969
1063
  msg.puts "#\n# Changes:\n#"
970
1064
  msg.puts changes.gsub(/^/, '# ').gsub(/ +$/, '')
971
1065
  end
972
1066
  }
1067
+
973
1068
  edit_cmd = Array(git_editor).dup
974
- edit_cmd << '-c' << 'set ft=gitcommit' if edit_cmd[0] =~ /^[mg]?vim$/
1069
+ edit_cmd << '-c' << 'set ft=gitcommit tw=0 wrap lbr' if edit_cmd[0] =~ /^[mg]?vim$/
975
1070
  edit_cmd << message_file
976
1071
  system(*edit_cmd)
977
- abort "can't open text editor for pull request message" unless $?.success?
1072
+
1073
+ unless $?.success?
1074
+ # writing was cancelled, or the editor never opened in the first place
1075
+ delete_editmsg(message_file)
1076
+ abort "error using text editor for pull request message"
1077
+ end
1078
+
978
1079
  title, body = read_editmsg(message_file)
979
1080
  abort "Aborting due to empty pull request title" unless title
980
1081
  [title, body]
981
1082
  end
982
1083
 
1084
+ # This unfortunate hack is because older versions of hub never cleaned up
1085
+ # the pullrequest_editmsg_file, which newer hub would pick up and
1086
+ # misinterpret as a message which should be reused after a failed PR.
1087
+ def valid_editmsg_file?(message_file)
1088
+ File.exists?(message_file) &&
1089
+ File.mtime(message_file) > File.mtime(__FILE__)
1090
+ end
1091
+
1092
+ def read_msg(message)
1093
+ message.split("\n\n", 2).each {|s| s.strip! }.reject {|s| s.empty? }
1094
+ end
1095
+
1096
+ def pullrequest_editmsg_file
1097
+ File.join(git_dir, 'PULLREQ_EDITMSG')
1098
+ end
1099
+
983
1100
  def read_editmsg(file)
984
1101
  title, body = '', ''
985
1102
  File.open(file, 'r') { |msg|
@@ -995,6 +1112,10 @@ help
995
1112
  [title =~ /\S/ ? title : nil, body =~ /\S/ ? body : nil]
996
1113
  end
997
1114
 
1115
+ def delete_editmsg(file = pullrequest_editmsg_file)
1116
+ File.delete(file) if File.exist?(file)
1117
+ end
1118
+
998
1119
  def expand_alias(cmd)
999
1120
  if expanded = git_alias_for(cmd)
1000
1121
  if expanded.index('!') != 0
@@ -1003,7 +1124,7 @@ help
1003
1124
  end
1004
1125
  end
1005
1126
  end
1006
-
1127
+
1007
1128
  def display_api_exception(action, response)
1008
1129
  $stderr.puts "Error #{action}: #{response.message.strip} (HTTP #{response.status})"
1009
1130
  if 422 == response.status and response.error_message?