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