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.
Files changed (44) hide show
  1. data/.gitignore +16 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +4 -0
  4. data/Gemfile +22 -0
  5. data/Gemfile.lock +56 -0
  6. data/LICENSE +22 -0
  7. data/README.md +80 -0
  8. data/Rakefile +16 -0
  9. data/bin/git-new-fb +21 -0
  10. data/bin/git-pull-request +21 -0
  11. data/bin/git-sync +21 -0
  12. data/bin/git-to-master +21 -0
  13. data/git-process.gemspec +21 -0
  14. data/lib/git-process/abstract-error-builder.rb +46 -0
  15. data/lib/git-process/git-abstract-merge-error-builder.rb +115 -0
  16. data/lib/git-process/git-branch.rb +86 -0
  17. data/lib/git-process/git-branches.rb +53 -0
  18. data/lib/git-process/git-lib.rb +413 -0
  19. data/lib/git-process/git-merge-error.rb +31 -0
  20. data/lib/git-process/git-new-fb-options.rb +34 -0
  21. data/lib/git-process/git-process-error.rb +10 -0
  22. data/lib/git-process/git-process-options.rb +82 -0
  23. data/lib/git-process/git-process.rb +194 -0
  24. data/lib/git-process/git-pull-request-options.rb +42 -0
  25. data/lib/git-process/git-rebase-error.rb +31 -0
  26. data/lib/git-process/git-status.rb +72 -0
  27. data/lib/git-process/git-sync-options.rb +34 -0
  28. data/lib/git-process/git-to-master-options.rb +18 -0
  29. data/lib/git-process/github-client.rb +73 -0
  30. data/lib/git-process/github-service.rb +156 -0
  31. data/lib/git-process/parked-changes-error.rb +32 -0
  32. data/lib/git-process/pull-request.rb +38 -0
  33. data/lib/git-process/uncommitted-changes-error.rb +15 -0
  34. data/lib/git-process/version.rb +12 -0
  35. data/spec/FileHelpers.rb +18 -0
  36. data/spec/GitRepoHelper.rb +86 -0
  37. data/spec/git-abstract-merge-error-builder_spec.rb +113 -0
  38. data/spec/git-lib_spec.rb +118 -0
  39. data/spec/git-process_spec.rb +328 -0
  40. data/spec/git-status_spec.rb +101 -0
  41. data/spec/github-service_spec.rb +209 -0
  42. data/spec/pull-request_spec.rb +57 -0
  43. data/spec/spec_helper.rb +1 -0
  44. 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,10 @@
1
+ module Git
2
+
3
+ class Process
4
+
5
+ class GitProcessError < RuntimeError
6
+ end
7
+
8
+ end
9
+
10
+ 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