hubflow 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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