hubflow 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,367 @@
1
+ require 'shellwords'
2
+ require 'forwardable'
3
+
4
+ module Hub
5
+ # Provides methods for inspecting the environment, such as GitHub user/token
6
+ # settings, repository info, and similar.
7
+ module Context
8
+ extend Forwardable
9
+
10
+ NULL = defined?(File::NULL) ? File::NULL : File.exist?('/dev/null') ? '/dev/null' : 'NUL'
11
+
12
+ # Shells out to git to get output of its commands
13
+ class GitReader
14
+ attr_reader :executable
15
+
16
+ def initialize(executable = nil, &read_proc)
17
+ @executable = executable || 'git'
18
+ # caches output when shelling out to git
19
+ read_proc ||= lambda { |cache, cmd|
20
+ result = %x{#{command_to_string(cmd)} 2>#{NULL}}.chomp
21
+ cache[cmd] = $?.success? && !result.empty? ? result : nil
22
+ }
23
+ @cache = Hash.new(&read_proc)
24
+ end
25
+
26
+ def add_exec_flags(flags)
27
+ @executable = Array(executable).concat(flags)
28
+ end
29
+
30
+ def read_config(cmd, all = false)
31
+ config_cmd = ['config', (all ? '--get-all' : '--get'), *cmd]
32
+ config_cmd = config_cmd.join(' ') unless cmd.respond_to? :join
33
+ read config_cmd
34
+ end
35
+
36
+ def read(cmd)
37
+ @cache[cmd]
38
+ end
39
+
40
+ def stub_config_value(key, value, get = '--get')
41
+ stub_command_output "config #{get} #{key}", value
42
+ end
43
+
44
+ def stub_command_output(cmd, value)
45
+ @cache[cmd] = value.nil? ? nil : value.to_s
46
+ end
47
+
48
+ def stub!(values)
49
+ @cache.update values
50
+ end
51
+
52
+ private
53
+
54
+ def to_exec(args)
55
+ args = Shellwords.shellwords(args) if args.respond_to? :to_str
56
+ Array(executable) + Array(args)
57
+ end
58
+
59
+ def command_to_string(cmd)
60
+ full_cmd = to_exec(cmd)
61
+ full_cmd.respond_to?(:shelljoin) ? full_cmd.shelljoin : full_cmd.join(' ')
62
+ end
63
+ end
64
+
65
+ module GitReaderMethods
66
+ extend Forwardable
67
+
68
+ def_delegator :git_reader, :read_config, :git_config
69
+ def_delegator :git_reader, :read, :git_command
70
+
71
+ def self.extended(base)
72
+ base.extend Forwardable
73
+ base.def_delegators :'self.class', :git_config, :git_command
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def git_reader
80
+ @git_reader ||= GitReader.new ENV['GIT']
81
+ end
82
+
83
+ include GitReaderMethods
84
+ private :git_config, :git_command
85
+
86
+ def local_repo
87
+ @local_repo ||= begin
88
+ LocalRepo.new git_reader, current_dir if is_repo?
89
+ end
90
+ end
91
+
92
+ repo_methods = [
93
+ :current_branch, :master_branch,
94
+ :current_project, :upstream_project,
95
+ :repo_owner,
96
+ :remotes, :remotes_group, :origin_remote
97
+ ]
98
+ def_delegator :local_repo, :name, :repo_name
99
+ def_delegators :local_repo, *repo_methods
100
+ private :repo_name, *repo_methods
101
+
102
+ class LocalRepo < Struct.new(:git_reader, :dir)
103
+ include GitReaderMethods
104
+
105
+ def name
106
+ if project = main_project
107
+ project.name
108
+ else
109
+ File.basename(dir)
110
+ end
111
+ end
112
+
113
+ def repo_owner
114
+ if project = main_project
115
+ project.owner
116
+ end
117
+ end
118
+
119
+ def main_project
120
+ remote = origin_remote and remote.project
121
+ end
122
+
123
+ def upstream_project
124
+ if upstream = current_branch.upstream
125
+ remote = remote_by_name upstream.remote_name
126
+ remote.project
127
+ end
128
+ end
129
+
130
+ def current_project
131
+ upstream_project || main_project
132
+ end
133
+
134
+ def current_branch
135
+ if branch = git_command('symbolic-ref -q HEAD')
136
+ Branch.new self, branch
137
+ end
138
+ end
139
+
140
+ def master_branch
141
+ Branch.new self, 'refs/heads/master'
142
+ end
143
+
144
+ def remotes
145
+ @remotes ||= begin
146
+ # TODO: is there a plumbing command to get a list of remotes?
147
+ list = git_command('remote').to_s.split("\n")
148
+ # force "origin" to be first in the list
149
+ main = list.delete('origin') and list.unshift(main)
150
+ list.map { |name| Remote.new self, name }
151
+ end
152
+ end
153
+
154
+ def remotes_group(name)
155
+ git_config "remotes.#{name}"
156
+ end
157
+
158
+ def origin_remote
159
+ remotes.first
160
+ end
161
+
162
+ def remote_by_name(remote_name)
163
+ remotes.find {|r| r.name == remote_name }
164
+ end
165
+ end
166
+
167
+ class GithubProject < Struct.new(:local_repo, :owner, :name)
168
+ def name_with_owner
169
+ "#{owner}/#{name}"
170
+ end
171
+
172
+ def ==(other)
173
+ name_with_owner == other.name_with_owner
174
+ end
175
+
176
+ def remote
177
+ local_repo.remotes.find { |r| r.project == self }
178
+ end
179
+
180
+ def web_url(path = nil)
181
+ project_name = name_with_owner
182
+ if project_name.sub!(/\.wiki$/, '')
183
+ unless '/wiki' == path
184
+ path = if path =~ %r{^/commits/} then '/_history'
185
+ else path.to_s.sub(/\w+/, '_\0')
186
+ end
187
+ path = '/wiki' + path
188
+ end
189
+ end
190
+ 'https://github.com/' + project_name + path.to_s
191
+ end
192
+
193
+ def git_url(options = {})
194
+ if options[:https] then 'https://github.com/'
195
+ elsif options[:private] then 'git@github.com:'
196
+ else 'git://github.com/'
197
+ end + name_with_owner + '.git'
198
+ end
199
+ end
200
+
201
+ class Branch < Struct.new(:local_repo, :name)
202
+ alias to_s name
203
+
204
+ def short_name
205
+ name.split('/').last
206
+ end
207
+
208
+ def master?
209
+ short_name == 'master'
210
+ end
211
+
212
+ def upstream
213
+ if branch = local_repo.git_command("rev-parse --symbolic-full-name #{short_name}@{upstream}")
214
+ Branch.new local_repo, branch
215
+ end
216
+ end
217
+
218
+ def remote?
219
+ name.index('refs/remotes/') == 0
220
+ end
221
+
222
+ def remote_name
223
+ name =~ %r{^refs/remotes/([^/]+)} and $1 or
224
+ raise "can't get remote name from #{name.inspect}"
225
+ end
226
+ end
227
+
228
+ class Remote < Struct.new(:local_repo, :name)
229
+ alias to_s name
230
+
231
+ def ==(other)
232
+ other.respond_to?(:to_str) ? name == other.to_str : super
233
+ end
234
+
235
+ def project
236
+ if urls.find { |u| u =~ %r{\bgithub\.com[:/](.+)/(.+).git$} }
237
+ GithubProject.new local_repo, $1, $2
238
+ end
239
+ end
240
+
241
+ def urls
242
+ @urls ||= local_repo.git_config("remote.#{name}.url", :all).to_s.split("\n")
243
+ end
244
+ end
245
+
246
+ ## helper methods for local repo, GH projects
247
+
248
+ def github_project(name, owner = nil)
249
+ if owner and owner.index('/')
250
+ owner, name = owner.split('/', 2)
251
+ elsif name and name.index('/')
252
+ owner, name = name.split('/', 2)
253
+ else
254
+ name ||= repo_name
255
+ owner ||= github_user
256
+ end
257
+
258
+ GithubProject.new local_repo, owner, name
259
+ end
260
+
261
+ def git_url(owner = nil, name = nil, options = {})
262
+ project = github_project(name, owner)
263
+ project.git_url({:https => https_protocol?}.update(options))
264
+ end
265
+
266
+ LGHCONF = "http://help.github.com/set-your-user-name-email-and-github-token/"
267
+
268
+ # Either returns the GitHub user as set by git-config(1) or aborts
269
+ # with an error message.
270
+ def github_user(fatal = true)
271
+ if user = ENV['GITHUB_USER'] || git_config('github.user')
272
+ user
273
+ elsif fatal
274
+ abort("** No GitHub user set. See #{LGHCONF}")
275
+ end
276
+ end
277
+
278
+ def github_token(fatal = true)
279
+ if token = ENV['GITHUB_TOKEN'] || git_config('github.token')
280
+ token
281
+ elsif fatal
282
+ abort("** No GitHub token set. See #{LGHCONF}")
283
+ end
284
+ end
285
+
286
+ # legacy setting
287
+ def http_clone?
288
+ git_config('--bool hub.http-clone') == 'true'
289
+ end
290
+
291
+ def https_protocol?
292
+ git_config('hub.protocol') == 'https' or http_clone?
293
+ end
294
+
295
+ def git_alias_for(name)
296
+ git_config "alias.#{name}"
297
+ end
298
+
299
+ PWD = Dir.pwd
300
+
301
+ def current_dir
302
+ PWD
303
+ end
304
+
305
+ def git_dir
306
+ git_command 'rev-parse -q --git-dir'
307
+ end
308
+
309
+ def is_repo?
310
+ !!git_dir
311
+ end
312
+
313
+ def git_editor
314
+ # possible: ~/bin/vi, $SOME_ENVIRONMENT_VARIABLE, "C:\Program Files\Vim\gvim.exe" --nofork
315
+ editor = git_command 'var GIT_EDITOR'
316
+ editor = ENV[$1] if editor =~ /^\$(\w+)$/
317
+ editor = File.expand_path editor if (editor =~ /^[~.]/ or editor.index('/')) and editor !~ /["']/
318
+ editor.shellsplit
319
+ end
320
+
321
+ # Cross-platform web browser command; respects the value set in $BROWSER.
322
+ #
323
+ # Returns an array, e.g.: ['open']
324
+ def browser_launcher
325
+ browser = ENV['BROWSER'] || (
326
+ osx? ? 'open' : windows? ? 'start' :
327
+ %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm }
328
+ )
329
+
330
+ abort "Please set $BROWSER to a web launcher to use this command." unless browser
331
+ Array(browser)
332
+ end
333
+
334
+ def osx?
335
+ require 'rbconfig'
336
+ RbConfig::CONFIG['host_os'].to_s.include?('darwin')
337
+ end
338
+
339
+ def windows?
340
+ require 'rbconfig'
341
+ RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/
342
+ end
343
+
344
+ # Cross-platform way of finding an executable in the $PATH.
345
+ #
346
+ # which('ruby') #=> /usr/bin/ruby
347
+ def which(cmd)
348
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
349
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
350
+ exts.each { |ext|
351
+ exe = "#{path}/#{cmd}#{ext}"
352
+ return exe if File.executable? exe
353
+ }
354
+ end
355
+ return nil
356
+ end
357
+
358
+ # Checks whether a command exists on this system in the $PATH.
359
+ #
360
+ # name - The String name of the command to check for.
361
+ #
362
+ # Returns a Boolean.
363
+ def command?(name)
364
+ !which(name).nil?
365
+ end
366
+ end
367
+ end
@@ -0,0 +1,73 @@
1
+ module Hub
2
+ # The Hub runner expects to be initialized with `ARGV` and primarily
3
+ # exists to run a git command.
4
+ #
5
+ # The actual functionality, that is, the code it runs when it needs to
6
+ # augment a git command, is kept in the `Hub::Commands` module.
7
+ class Runner
8
+ attr_reader :args
9
+
10
+ def initialize(*args)
11
+ @args = Args.new(args)
12
+ Commands.run(@args)
13
+ end
14
+
15
+ # Shortcut
16
+ def self.execute(*args)
17
+ new(*args).execute
18
+ end
19
+
20
+ # A string representation of the command that would run.
21
+ def command
22
+ if args.skip?
23
+ ''
24
+ else
25
+ commands.join('; ')
26
+ end
27
+ end
28
+
29
+ # An array of all commands as strings.
30
+ def commands
31
+ args.commands.map do |cmd|
32
+ if cmd.respond_to?(:join)
33
+ # a simplified `Shellwords.join` but it's OK since this is only used to inspect
34
+ cmd.map { |arg| arg = arg.to_s; (arg.index(' ') || arg.empty?) ? "'#{arg}'" : arg }.join(' ')
35
+ else
36
+ cmd.to_s
37
+ end
38
+ end
39
+ end
40
+
41
+ # Runs the target git command with an optional callback. Replaces
42
+ # the current process.
43
+ #
44
+ # If `args` is empty, this will skip calling the git command. This
45
+ # allows commands to print an error message and cancel their own
46
+ # execution if they don't make sense.
47
+ def execute
48
+ if args.noop?
49
+ puts commands
50
+ elsif not args.skip?
51
+ if args.chained?
52
+ execute_command_chain
53
+ else
54
+ exec(*args.to_exec)
55
+ end
56
+ end
57
+ end
58
+
59
+ # Runs multiple commands in succession; exits at first failure.
60
+ def execute_command_chain
61
+ commands = args.commands
62
+ commands.each_with_index do |cmd, i|
63
+ if cmd.respond_to?(:call) then cmd.call
64
+ elsif i == commands.length - 1
65
+ # last command in chain
66
+ exec(*cmd)
67
+ else
68
+ exit($?.exitstatus) unless system(*cmd)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,58 @@
1
+ module Hub
2
+ module Standalone
3
+ extend self
4
+
5
+ RUBY_BIN = if File.executable? '/usr/bin/ruby' then '/usr/bin/ruby'
6
+ else
7
+ require 'rbconfig'
8
+ File.join RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']
9
+ end
10
+
11
+ PREAMBLE = <<-preamble
12
+ #!#{RUBY_BIN}
13
+ #
14
+ # This file, hub, is generated code.
15
+ # Please DO NOT EDIT or send patches for it.
16
+ #
17
+ # Please take a look at the source from
18
+ # https://github.com/defunkt/hub
19
+ # and submit patches against the individual files
20
+ # that build hub.
21
+ #
22
+
23
+ preamble
24
+
25
+ POSTAMBLE = "Hub::Runner.execute(*ARGV)\n"
26
+ __DIR__ = File.dirname(__FILE__)
27
+ MANPAGE = "__END__\n#{File.read(__DIR__ + '/../../man/hub.1')}"
28
+
29
+ def save(filename, path = '.')
30
+ target = File.join(File.expand_path(path), filename)
31
+ File.open(target, 'w') do |f|
32
+ f.puts build
33
+ f.chmod 0755
34
+ end
35
+ end
36
+
37
+ def build
38
+ root = File.dirname(__FILE__)
39
+
40
+ standalone = ''
41
+ standalone << PREAMBLE
42
+
43
+ files = Dir["#{root}/*.rb"].sort - [__FILE__]
44
+ # ensure context.rb appears before others
45
+ ctx = files.find {|f| f['context.rb'] } and files.unshift(files.delete(ctx))
46
+
47
+ files.each do |file|
48
+ File.readlines(file).each do |line|
49
+ standalone << line if line !~ /^\s*#/
50
+ end
51
+ end
52
+
53
+ standalone << POSTAMBLE
54
+ standalone << MANPAGE
55
+ standalone
56
+ end
57
+ end
58
+ end