git-process 0.9.1.pre3
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.
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +22 -0
- data/README.md +80 -0
- data/Rakefile +16 -0
- data/bin/git-new-fb +21 -0
- data/bin/git-pull-request +21 -0
- data/bin/git-sync +21 -0
- data/bin/git-to-master +21 -0
- data/git-process.gemspec +21 -0
- data/lib/git-process/abstract-error-builder.rb +46 -0
- data/lib/git-process/git-abstract-merge-error-builder.rb +115 -0
- data/lib/git-process/git-branch.rb +86 -0
- data/lib/git-process/git-branches.rb +53 -0
- data/lib/git-process/git-lib.rb +413 -0
- data/lib/git-process/git-merge-error.rb +31 -0
- data/lib/git-process/git-new-fb-options.rb +34 -0
- data/lib/git-process/git-process-error.rb +10 -0
- data/lib/git-process/git-process-options.rb +82 -0
- data/lib/git-process/git-process.rb +194 -0
- data/lib/git-process/git-pull-request-options.rb +42 -0
- data/lib/git-process/git-rebase-error.rb +31 -0
- data/lib/git-process/git-status.rb +72 -0
- data/lib/git-process/git-sync-options.rb +34 -0
- data/lib/git-process/git-to-master-options.rb +18 -0
- data/lib/git-process/github-client.rb +73 -0
- data/lib/git-process/github-service.rb +156 -0
- data/lib/git-process/parked-changes-error.rb +32 -0
- data/lib/git-process/pull-request.rb +38 -0
- data/lib/git-process/uncommitted-changes-error.rb +15 -0
- data/lib/git-process/version.rb +12 -0
- data/spec/FileHelpers.rb +18 -0
- data/spec/GitRepoHelper.rb +86 -0
- data/spec/git-abstract-merge-error-builder_spec.rb +113 -0
- data/spec/git-lib_spec.rb +118 -0
- data/spec/git-process_spec.rb +328 -0
- data/spec/git-status_spec.rb +101 -0
- data/spec/github-service_spec.rb +209 -0
- data/spec/pull-request_spec.rb +57 -0
- data/spec/spec_helper.rb +1 -0
- metadata +133 -0
@@ -0,0 +1,413 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require 'git-branch'
|
6
|
+
require 'git-branches'
|
7
|
+
require 'git-status'
|
8
|
+
|
9
|
+
|
10
|
+
class String
|
11
|
+
|
12
|
+
def to_boolean
|
13
|
+
return false if self == false || self.nil? || self =~ (/(false|f|no|n|0)$/i)
|
14
|
+
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
|
15
|
+
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
module Git
|
22
|
+
|
23
|
+
class GitExecuteError < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# @!attribute [r] logger
|
28
|
+
# @return [Logger] a logger
|
29
|
+
# @!attribute [r] git
|
30
|
+
# @return [Git] an instance of the Git library
|
31
|
+
class GitLib
|
32
|
+
attr_reader :logger
|
33
|
+
|
34
|
+
def initialize(dir, options = {})
|
35
|
+
initialize_logger(options[:logger], options[:log_level])
|
36
|
+
initialize_git(dir)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def initialize_logger(logger, log_level)
|
41
|
+
if logger
|
42
|
+
@logger = logger
|
43
|
+
else
|
44
|
+
@logger = Logger.new(STDOUT)
|
45
|
+
@logger.level = log_level || Logger::WARN
|
46
|
+
@logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
47
|
+
f = Logger::Formatter.new
|
48
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
49
|
+
"#{msg}\n"
|
50
|
+
# "#{severity[0..0]}: #{datetime.strftime(@logger.datetime_format)}: #{msg}\n"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def initialize_git(dir, git = nil)
|
57
|
+
if dir
|
58
|
+
@workdir = File.expand_path(dir)
|
59
|
+
unless File.directory?(File.join(workdir, '.git'))
|
60
|
+
logger.info { "Initializing new repository at #{workdir}" }
|
61
|
+
command(:init)
|
62
|
+
else
|
63
|
+
logger.debug { "Opening existing repository at #{workdir}" }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
private :initialize_logger, :initialize_git
|
70
|
+
|
71
|
+
|
72
|
+
def workdir
|
73
|
+
@workdir
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def has_a_remote?
|
78
|
+
if @remote == nil
|
79
|
+
@remote = (command('remote') != '')
|
80
|
+
end
|
81
|
+
@remote
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def add(file)
|
86
|
+
command(:add, ['--', file])
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def commit(msg)
|
91
|
+
command(:commit, ['-m', msg])
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def rebase(base)
|
96
|
+
command('rebase', base)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def merge(base)
|
101
|
+
command(:merge, [base])
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def fetch
|
106
|
+
command(:fetch, ['-p', Process.server_name])
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def branches
|
111
|
+
GitBranches.new(self)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
#
|
116
|
+
# Does branch manipulation.
|
117
|
+
#
|
118
|
+
# @param [String] branch_name the name of the branch
|
119
|
+
#
|
120
|
+
# @option opts [Boolean] :delete delete the remote branch
|
121
|
+
# @option opts [Boolean] :force force the update
|
122
|
+
# @option opts [Boolean] :all list all branches, local and remote
|
123
|
+
# @option opts [Boolean] :no_color force not using any ANSI color codes
|
124
|
+
# @option opts [String] :rename the new name for the branch
|
125
|
+
# @option opts [String] :base_branch the branch to base the new branch off of;
|
126
|
+
# defaults to 'master'
|
127
|
+
#
|
128
|
+
# @return [String] the output of running the git command
|
129
|
+
def branch(branch_name, opts = {})
|
130
|
+
args = []
|
131
|
+
if opts[:delete]
|
132
|
+
logger.info { "Deleting local branch '#{branch_name}'."}
|
133
|
+
|
134
|
+
args << (opts[:force] ? '-D' : '-d')
|
135
|
+
args << branch_name
|
136
|
+
elsif opts[:rename]
|
137
|
+
logger.info { "Renaming branch '#{branch_name}' to '#{opts[:rename]}'."}
|
138
|
+
|
139
|
+
args << '-m' << branch_name << opts[:rename]
|
140
|
+
elsif branch_name
|
141
|
+
if opts[:force]
|
142
|
+
raise ArgumentError.new("Need :base_branch when using :force for a branch.") unless opts[:base_branch]
|
143
|
+
logger.info { "Changing branch '#{branch_name}' to point to '#{opts[:base_branch]}'."}
|
144
|
+
|
145
|
+
args << '-f' << branch_name << opts[:base_branch]
|
146
|
+
else
|
147
|
+
logger.info { "Creating new branch '#{branch_name}' based on '#{opts[:base_branch]}'."}
|
148
|
+
|
149
|
+
args << branch_name
|
150
|
+
args << (opts[:base_branch] ? opts[:base_branch] : 'master')
|
151
|
+
end
|
152
|
+
else
|
153
|
+
args << '-a' if opts[:all]
|
154
|
+
args << '--no-color' if opts[:no_color]
|
155
|
+
end
|
156
|
+
command(:branch, args)
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
#
|
161
|
+
# Pushes the given branch to the server.
|
162
|
+
#
|
163
|
+
# @param [String] remote_name the repository name; nil -> 'origin'
|
164
|
+
# @param [String] local_branch the local branch to push; nil -> the current branch
|
165
|
+
# @param [String] remote_branch the name of the branch to push to; nil -> same as local_branch
|
166
|
+
#
|
167
|
+
# @option opts [Boolean, String] :delete delete the remote branch
|
168
|
+
# @option opts [Boolean] :force force the update, even if not a fast-forward
|
169
|
+
#
|
170
|
+
# @return [void]
|
171
|
+
#
|
172
|
+
# @raise [ArgumentError] if :delete is true, but no branch name is given
|
173
|
+
def push(remote_name, local_branch, remote_branch, opts = {})
|
174
|
+
remote_name ||= 'origin'
|
175
|
+
|
176
|
+
args = [remote_name]
|
177
|
+
|
178
|
+
if opts[:delete]
|
179
|
+
if remote_branch
|
180
|
+
opts[:delete] = remote_branch
|
181
|
+
elsif local_branch
|
182
|
+
opts[:delete] = local_branch
|
183
|
+
else
|
184
|
+
raise ArgumentError.new("Need a branch name to delete.") if opts[:delete].is_a? TrueClass
|
185
|
+
end
|
186
|
+
logger.info { "Deleting remote branch '#{opts[:delete]}' on '#{remote_name}'."}
|
187
|
+
args << '--delete' << opts[:delete]
|
188
|
+
else
|
189
|
+
local_branch ||= branches.current
|
190
|
+
remote_branch ||= local_branch
|
191
|
+
args << '-f' if opts[:force]
|
192
|
+
|
193
|
+
logger.info do
|
194
|
+
if local_branch == remote_branch
|
195
|
+
"Pushing to '#{remote_branch}' on '#{remote_name}'."
|
196
|
+
else
|
197
|
+
"Pushing #{local_branch} to '#{remote_branch}' on '#{remote_name}'."
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
args << "#{local_branch}:#{remote_branch}"
|
202
|
+
end
|
203
|
+
command(:push, args)
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
def rebase_continue
|
208
|
+
command(:rebase, '--continue')
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
def checkout(branch_name, opts = {}, &block)
|
213
|
+
args = []
|
214
|
+
args << '--no-track' if opts[:no_track]
|
215
|
+
args << '-b' if opts[:new_branch]
|
216
|
+
args << branch_name
|
217
|
+
args << opts[:new_branch] if opts[:new_branch]
|
218
|
+
branches = branches()
|
219
|
+
command(:checkout, args)
|
220
|
+
|
221
|
+
branches << GitBranch.new(branch_name, opts[:new_branch] != nil, self)
|
222
|
+
|
223
|
+
if block_given?
|
224
|
+
yield
|
225
|
+
command(:checkout, branches.current.name)
|
226
|
+
branches.current
|
227
|
+
else
|
228
|
+
branches[branch_name]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
def log_count
|
234
|
+
command(:log, '--oneline').split(/\n/).length
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
def remove(file, opts = {})
|
239
|
+
args = []
|
240
|
+
args << '-f' if opts[:force]
|
241
|
+
args << file
|
242
|
+
command('rm', args)
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
def config_hash
|
247
|
+
@config_hash ||= {}
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
def config(key = nil, value = nil)
|
252
|
+
if key and value
|
253
|
+
command('config', [key, value])
|
254
|
+
config_hash[key] = value
|
255
|
+
value
|
256
|
+
elsif key
|
257
|
+
value = config_hash[key]
|
258
|
+
unless value
|
259
|
+
value = command('config', ['--get', key])
|
260
|
+
config_hash[key] = value
|
261
|
+
end
|
262
|
+
value
|
263
|
+
else
|
264
|
+
if config_hash.empty?
|
265
|
+
str = command('config', '--list')
|
266
|
+
lines = str.split("\n")
|
267
|
+
lines.each do |line|
|
268
|
+
(key, *values) = line.split('=')
|
269
|
+
config_hash[key] = values.join('=')
|
270
|
+
end
|
271
|
+
end
|
272
|
+
config_hash
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
def repo_name
|
278
|
+
unless @repo_name
|
279
|
+
origin_url = config['remote.origin.url']
|
280
|
+
raise Git::Process::GitProcessError.new("There is not origin url set up.") if origin_url.empty?
|
281
|
+
@repo_name = origin_url.sub(/^.*:(.*?)(.git)?$/, '\1')
|
282
|
+
end
|
283
|
+
@repo_name
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
#
|
288
|
+
# Returns the status of the git repository.
|
289
|
+
#
|
290
|
+
# @return [Status]
|
291
|
+
def status
|
292
|
+
GitStatus.new(self)
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
# @return [String] the raw porcelain status string
|
297
|
+
def porcelain_status
|
298
|
+
command(:status, '--porcelain')
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
def reset(rev_name, opts = {})
|
303
|
+
args = []
|
304
|
+
args << '--hard' if opts[:hard]
|
305
|
+
args << rev_name
|
306
|
+
|
307
|
+
logger.info { "Resetting #{opts[:hard] ? '(hard)' : ''} to #{rev_name}" }
|
308
|
+
|
309
|
+
command(:reset, args)
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
def rerere_enabled?
|
314
|
+
re = command('config', ['--get', 'rerere.enabled'])
|
315
|
+
re && re.to_boolean
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
def rerere_enabled(re, global = true)
|
320
|
+
args = []
|
321
|
+
args << '--global' if global
|
322
|
+
args << 'rerere.enabled' << re
|
323
|
+
command(:config, args)
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
def rerere_autoupdate?
|
328
|
+
re = command('config', ['--get', 'rerere.autoupdate'])
|
329
|
+
re && re.to_boolean
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
def rerere_autoupdate(re, global = true)
|
334
|
+
args = []
|
335
|
+
args << '--global' if global
|
336
|
+
args << 'rerere.autoupdate' << re
|
337
|
+
command(:config, args)
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
def rev_list(start_revision, end_revision, opts ={})
|
342
|
+
args = []
|
343
|
+
args << "-#{opts[:num_revs]}" if opts[:num_revs]
|
344
|
+
args << '--oneline' if opts[:oneline]
|
345
|
+
args << "#{start_revision}..#{end_revision}"
|
346
|
+
command('rev-list', args)
|
347
|
+
end
|
348
|
+
|
349
|
+
|
350
|
+
def rev_parse(name)
|
351
|
+
command('rev-parse', name)
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
alias sha rev_parse
|
356
|
+
|
357
|
+
|
358
|
+
def add_remote(remote_name, url)
|
359
|
+
command(:remote, ['add', remote_name, url])
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
private
|
364
|
+
|
365
|
+
|
366
|
+
def command(cmd, opts = [], chdir = true, redirect = '', &block)
|
367
|
+
ENV['GIT_DIR'] = File.join(workdir, '.git')
|
368
|
+
ENV['GIT_INDEX_FILE'] = File.join(workdir, '.git', 'index')
|
369
|
+
ENV['GIT_WORK_TREE'] = workdir
|
370
|
+
path = workdir
|
371
|
+
|
372
|
+
opts = [opts].flatten.map {|s| escape(s) }.join(' ')
|
373
|
+
git_cmd = "git #{cmd} #{opts} #{redirect} 2>&1"
|
374
|
+
|
375
|
+
out = nil
|
376
|
+
if chdir and (Dir.getwd != path)
|
377
|
+
Dir.chdir(path) { out = run_command(git_cmd, &block) }
|
378
|
+
else
|
379
|
+
out = run_command(git_cmd, &block)
|
380
|
+
end
|
381
|
+
|
382
|
+
if logger
|
383
|
+
logger.debug(git_cmd)
|
384
|
+
logger.debug(out)
|
385
|
+
end
|
386
|
+
|
387
|
+
if $?.exitstatus > 0
|
388
|
+
if $?.exitstatus == 1 && out == ''
|
389
|
+
return ''
|
390
|
+
end
|
391
|
+
raise Git::GitExecuteError.new(git_cmd + ':' + out.to_s)
|
392
|
+
end
|
393
|
+
out
|
394
|
+
end
|
395
|
+
|
396
|
+
|
397
|
+
def run_command(git_cmd, &block)
|
398
|
+
if block_given?
|
399
|
+
IO.popen(git_cmd, &block)
|
400
|
+
else
|
401
|
+
`#{git_cmd}`.chomp
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
|
406
|
+
def escape(s)
|
407
|
+
escaped = s.to_s.gsub('\'', '\'\\\'\'')
|
408
|
+
%Q{"#{escaped}"}
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'git-process-error'
|
2
|
+
require 'git-abstract-merge-error-builder'
|
3
|
+
|
4
|
+
module Git
|
5
|
+
|
6
|
+
class Process
|
7
|
+
|
8
|
+
class MergeError < GitProcessError
|
9
|
+
include Git::AbstractMergeErrorBuilder
|
10
|
+
|
11
|
+
attr_reader :error_message, :lib
|
12
|
+
|
13
|
+
def initialize(merge_error_message, lib)
|
14
|
+
@lib = lib
|
15
|
+
@error_message = merge_error_message
|
16
|
+
|
17
|
+
msg = build_message
|
18
|
+
|
19
|
+
super(msg)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def continue_command
|
24
|
+
'git commit'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'git-process-options'
|
2
|
+
|
3
|
+
module Git
|
4
|
+
|
5
|
+
class Process
|
6
|
+
|
7
|
+
class NewFeatureBranchOptions
|
8
|
+
include GitProcessOptions
|
9
|
+
|
10
|
+
attr_reader :branch_name
|
11
|
+
|
12
|
+
def initialize(filename, argv)
|
13
|
+
@filename = filename
|
14
|
+
argv << "-h" if argv.empty?
|
15
|
+
parse(filename, argv)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def extend_opts(opts)
|
20
|
+
opts.banner = "Usage: #{@filename} [ options ] branch_name"
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def extend_args(argv)
|
25
|
+
raise OptionParser::ParseError.new("Must have exactly one branch name.") if argv.length != 1
|
26
|
+
|
27
|
+
@branch_name = argv.pop
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Git
|
4
|
+
|
5
|
+
class Process
|
6
|
+
|
7
|
+
module GitProcessOptions
|
8
|
+
|
9
|
+
attr_reader :quiet, :verbose
|
10
|
+
|
11
|
+
|
12
|
+
def quiet
|
13
|
+
@quiet
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def verbose
|
18
|
+
@verbose
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def log_level
|
23
|
+
if quiet
|
24
|
+
Logger::ERROR
|
25
|
+
elsif verbose
|
26
|
+
Logger::DEBUG
|
27
|
+
else
|
28
|
+
Logger::INFO
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def parse(filename, argv)
|
34
|
+
OptionParser.new do |opts|
|
35
|
+
opts.banner = "Usage: #{filename} [ options ]"
|
36
|
+
|
37
|
+
opts.on("-q", "--quiet", "Quiet") do
|
38
|
+
@quiet = true
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-v", "--verbose", "Verbose") do
|
42
|
+
@verbose = true
|
43
|
+
@quiet = false
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-h", "--help", "Show this message") do
|
47
|
+
puts opts
|
48
|
+
exit(-1)
|
49
|
+
end
|
50
|
+
|
51
|
+
extend_opts(opts)
|
52
|
+
|
53
|
+
begin
|
54
|
+
begin
|
55
|
+
opts.parse!(argv)
|
56
|
+
|
57
|
+
extend_args(argv)
|
58
|
+
rescue OptionParser::ParseError => e
|
59
|
+
raise "#{e.message}\n#{opts}"
|
60
|
+
end
|
61
|
+
rescue RuntimeError => e
|
62
|
+
STDERR.puts e.message
|
63
|
+
exit(-1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def extend_opts(opts)
|
70
|
+
# extension point - does nothing by default
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def extend_args(argv)
|
75
|
+
# extension point - does nothing by default
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|