git-process 0.9.1.pre3

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