git-process-lib 2.0.0

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 (69) hide show
  1. data/CHANGELOG.md +123 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +57 -0
  4. data/LICENSE +193 -0
  5. data/README.md +342 -0
  6. data/Rakefile +32 -0
  7. data/bin/git-new-fb +39 -0
  8. data/bin/git-pull-request +63 -0
  9. data/bin/git-sync +38 -0
  10. data/bin/git-to-master +44 -0
  11. data/docs/git-new-fb.1.adoc +83 -0
  12. data/docs/git-process.1.adoc +227 -0
  13. data/docs/git-pull-request.1.adoc +166 -0
  14. data/docs/git-sync.1.adoc +120 -0
  15. data/docs/git-to-master.1.adoc +172 -0
  16. data/git-new-fb.gemspec +20 -0
  17. data/git-process-lib.gemspec +25 -0
  18. data/git-process.gemspec +22 -0
  19. data/git-pull-request.gemspec +20 -0
  20. data/git-sync.gemspec +20 -0
  21. data/git-to-master.gemspec +20 -0
  22. data/lib/git-process/abstract_error_builder.rb +53 -0
  23. data/lib/git-process/changed_file_helper.rb +115 -0
  24. data/lib/git-process/git_abstract_merge_error_builder.rb +130 -0
  25. data/lib/git-process/git_branch.rb +105 -0
  26. data/lib/git-process/git_branches.rb +81 -0
  27. data/lib/git-process/git_config.rb +135 -0
  28. data/lib/git-process/git_lib.rb +646 -0
  29. data/lib/git-process/git_logger.rb +84 -0
  30. data/lib/git-process/git_merge_error.rb +28 -0
  31. data/lib/git-process/git_process.rb +159 -0
  32. data/lib/git-process/git_process_error.rb +18 -0
  33. data/lib/git-process/git_process_options.rb +101 -0
  34. data/lib/git-process/git_rebase_error.rb +30 -0
  35. data/lib/git-process/git_remote.rb +222 -0
  36. data/lib/git-process/git_status.rb +108 -0
  37. data/lib/git-process/github_configuration.rb +298 -0
  38. data/lib/git-process/github_pull_request.rb +165 -0
  39. data/lib/git-process/new_fb.rb +49 -0
  40. data/lib/git-process/parked_changes_error.rb +41 -0
  41. data/lib/git-process/pull_request.rb +136 -0
  42. data/lib/git-process/pull_request_error.rb +25 -0
  43. data/lib/git-process/rebase_to_master.rb +148 -0
  44. data/lib/git-process/sync_process.rb +55 -0
  45. data/lib/git-process/syncer.rb +157 -0
  46. data/lib/git-process/uncommitted_changes_error.rb +23 -0
  47. data/lib/git-process/version.rb +22 -0
  48. data/local-build.rb +24 -0
  49. data/spec/FileHelpers.rb +19 -0
  50. data/spec/GitRepoHelper.rb +123 -0
  51. data/spec/changed_file_helper_spec.rb +127 -0
  52. data/spec/git_abstract_merge_error_builder_spec.rb +64 -0
  53. data/spec/git_branch_spec.rb +123 -0
  54. data/spec/git_config_spec.rb +45 -0
  55. data/spec/git_lib_spec.rb +176 -0
  56. data/spec/git_logger_spec.rb +66 -0
  57. data/spec/git_process_spec.rb +208 -0
  58. data/spec/git_remote_spec.rb +227 -0
  59. data/spec/git_status_spec.rb +122 -0
  60. data/spec/github_configuration_spec.rb +152 -0
  61. data/spec/github_pull_request_spec.rb +117 -0
  62. data/spec/github_test_helper.rb +49 -0
  63. data/spec/new_fb_spec.rb +126 -0
  64. data/spec/pull_request_helper.rb +94 -0
  65. data/spec/pull_request_spec.rb +137 -0
  66. data/spec/rebase_to_master_spec.rb +362 -0
  67. data/spec/spec_helper.rb +21 -0
  68. data/spec/sync_spec.rb +1474 -0
  69. metadata +249 -0
@@ -0,0 +1,81 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ require 'set'
14
+ require 'git-process/git_branch'
15
+
16
+ module GitProc
17
+
18
+ class GitBranches
19
+ include Enumerable
20
+
21
+
22
+ def initialize(lib, opt = {})
23
+ @lib = lib
24
+ branch_opts = {:no_color => true}
25
+ if opt[:remote]
26
+ branch_opts[:remote] = true
27
+ elsif opt[:local]
28
+ branch_opts[:local] = true
29
+ else
30
+ branch_opts[:all] = true
31
+ end
32
+ branch_lines = lib.branch(nil, branch_opts).split("\n")
33
+ @items = SortedSet.new
34
+ branch_lines.each do |bl|
35
+ @items << GitBranch.new(bl[2..-1], bl[0..0] == '*', lib)
36
+ end
37
+ end
38
+
39
+
40
+ def <<(item)
41
+ @items << item
42
+ end
43
+
44
+
45
+ def each(&block)
46
+ @items.each { |b| block.call(b) }
47
+ end
48
+
49
+
50
+ def names
51
+ @items.map { |b| b.name }
52
+ end
53
+
54
+
55
+ def current
56
+ @items.find { |b| b.current? }
57
+ end
58
+
59
+
60
+ def parking
61
+ @items.find { |b| b.name == '_parking_' }
62
+ end
63
+
64
+
65
+ def include?(branch_name)
66
+ @items.find { |b| b.name == branch_name } != nil
67
+ end
68
+
69
+
70
+ def [](branch_name)
71
+ branch_name = current.name if branch_name == 'HEAD'
72
+ br = @items.find { |b| b.name == branch_name }
73
+ if br.nil? and branch_name !~ /origin\// and branch_name != '_parking_'
74
+ @lib.logger.warn { "Could not find '#{branch_name}' in #{@items.map { |i| i.name }.join(',')}" }
75
+ end
76
+ br
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,135 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ require 'git-process/git_logger'
14
+ require 'git-process/git_branch'
15
+ require 'git-process/git_branches'
16
+ require 'git-process/git_status'
17
+ require 'git-process/git_process_error'
18
+
19
+
20
+ class String
21
+
22
+ def to_boolean
23
+ return false if self == false || self.nil? || self =~ (/(false|f|no|n|0)$/i)
24
+ return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
25
+ raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
26
+ end
27
+
28
+ end
29
+
30
+
31
+ class NilClass
32
+ def to_boolean
33
+ false
34
+ end
35
+ end
36
+
37
+
38
+ module GitProc
39
+
40
+ #
41
+ # Provides Git configuration
42
+ #
43
+ class GitConfig
44
+
45
+ def initialize(lib)
46
+ @lib = lib
47
+ end
48
+
49
+
50
+ def [](key)
51
+ value = config_hash[key]
52
+ unless value
53
+ value = @lib.command(:config, ['--get', key])
54
+ value = nil if value.empty?
55
+ config_hash[key] = value unless config_hash.empty?
56
+ end
57
+ value
58
+ end
59
+
60
+
61
+ def []=(key, value)
62
+ @lib.command(:config, [key, value])
63
+ config_hash[key] = value unless config_hash.empty?
64
+ value
65
+ end
66
+
67
+
68
+ def set_global(key, value)
69
+ @lib.command(:config, ['--global', key, value])
70
+ config_hash[key] = value unless config_hash.empty?
71
+ value
72
+ end
73
+
74
+
75
+ def gitlib
76
+ @lib
77
+ end
78
+
79
+
80
+ def logger
81
+ gitlib.logger
82
+ end
83
+
84
+
85
+ #
86
+ # @return true if no value has been set; the value of the config otherwise
87
+ def default_rebase_sync?
88
+ val = self['gitProcess.defaultRebaseSync']
89
+ val.nil? or val.to_boolean
90
+ end
91
+
92
+
93
+ def default_rebase_sync(re, global = true)
94
+ if global
95
+ set_global('gitProcess.defaultRebaseSync', re)
96
+ else
97
+ self['gitProcess.defaultRebaseSync'] = re
98
+ end
99
+ end
100
+
101
+
102
+ def default_rebase_sync=(re)
103
+ default_rebase_sync(re, false)
104
+ end
105
+
106
+
107
+ def master_branch
108
+ @master_branch ||= self['gitProcess.integrationBranch'] || 'master'
109
+ end
110
+
111
+
112
+ def remote_master_branch
113
+ remote.master_branch_name
114
+ end
115
+
116
+
117
+ def integration_branch
118
+ remote.exists? ? remote_master_branch : self.master_branch
119
+ end
120
+
121
+
122
+ private
123
+
124
+ def remote
125
+ gitlib.remote
126
+ end
127
+
128
+
129
+ def config_hash
130
+ @config_hash ||= {}
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,646 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ require 'logger'
14
+ require 'git-process/git_branch'
15
+ require 'git-process/git_branches'
16
+ require 'git-process/git_remote'
17
+ require 'git-process/git_status'
18
+ require 'git-process/git_process_error'
19
+
20
+
21
+ module GitProc
22
+
23
+ class GitExecuteError < GitProcessError
24
+ end
25
+
26
+
27
+ #
28
+ # Provides Git commands
29
+ #
30
+ #noinspection RubyTooManyMethodsInspection
31
+ class GitLib
32
+
33
+ # @param [Dir] dir
34
+ def initialize(dir, opts)
35
+ self.log_level = GitLib.log_level(opts)
36
+ self.workdir = dir
37
+ end
38
+
39
+
40
+ def logger
41
+ if @logger.nil?
42
+ @logger = GitLogger.new(log_level)
43
+ end
44
+ @logger
45
+ end
46
+
47
+
48
+ def self.log_level(opts)
49
+ if opts[:log_level]
50
+ opts[:log_level]
51
+ elsif opts[:quiet]
52
+ Logger::ERROR
53
+ elsif opts[:verbose]
54
+ Logger::DEBUG
55
+ else
56
+ Logger::INFO
57
+ end
58
+ end
59
+
60
+
61
+ def log_level
62
+ @log_level || Logger::WARN
63
+ end
64
+
65
+
66
+ def log_level=(lvl)
67
+ @log_level = lvl
68
+ end
69
+
70
+
71
+ def workdir
72
+ @workdir
73
+ end
74
+
75
+
76
+ def workdir=(dir)
77
+ workdir = GitLib.find_workdir(dir)
78
+ if workdir.nil?
79
+ @workdir = dir
80
+ logger.info { "Initializing new repository at #{dir}" }
81
+ command(:init)
82
+ else
83
+ @workdir = workdir
84
+ logger.debug { "Opening existing repository at #{dir}" }
85
+ end
86
+ end
87
+
88
+
89
+ def self.find_workdir(dir)
90
+ if dir == File::SEPARATOR
91
+ nil
92
+ elsif File.directory?(File.join(dir, '.git'))
93
+ dir
94
+ else
95
+ find_workdir(File.expand_path("#{dir}#{File::SEPARATOR}.."))
96
+ end
97
+ end
98
+
99
+
100
+ def fetch_remote_changes(remote_name = nil)
101
+ if remote.exists?
102
+ fetch(remote_name || remote.name)
103
+ else
104
+ logger.debug 'Can not fetch latest changes because there is no remote defined'
105
+ end
106
+ end
107
+
108
+
109
+ def proc_rebase(base, opts = {})
110
+ begin
111
+ rebase(base, opts)
112
+ rescue GitExecuteError => rebase_error
113
+ raise RebaseError.new(rebase_error.message, self)
114
+ end
115
+ end
116
+
117
+
118
+ def proc_merge(base, opts = {})
119
+ begin
120
+ merge(base, opts)
121
+ rescue GitExecuteError => merge_error
122
+ raise MergeError.new(merge_error.message, self)
123
+ end
124
+ end
125
+
126
+
127
+ # @return [String] the previous remote sha ONLY IF it is not the same as the new remote sha; otherwise nil
128
+ def previous_remote_sha(current_branch, remote_branch)
129
+ return nil unless has_a_remote?
130
+ return nil unless remote_branches.include?(remote_branch)
131
+
132
+ control_file_sha = read_sync_control_file(current_branch)
133
+ old_sha = control_file_sha || remote_branch_sha(remote_branch)
134
+ fetch_remote_changes
135
+ new_sha = remote_branch_sha(remote_branch)
136
+
137
+ if old_sha != new_sha
138
+ logger.info('The remote branch has changed since the last time')
139
+ old_sha
140
+ else
141
+ logger.debug 'The remote branch has not changed since the last time'
142
+ nil
143
+ end
144
+ end
145
+
146
+
147
+ def remote_branch_sha(remote_branch)
148
+ logger.debug {"getting sha for remotes/#{remote_branch}"}
149
+ rev_parse("remotes/#{remote_branch}") rescue ''
150
+ end
151
+
152
+
153
+ def is_parked?
154
+ mybranches = self.branches()
155
+ mybranches.parking == mybranches.current
156
+ end
157
+
158
+
159
+ def push_to_server(local_branch, remote_branch, opts = {})
160
+ if opts[:local]
161
+ logger.debug('Not pushing to the server because the user selected local-only.')
162
+ elsif not has_a_remote?
163
+ logger.debug('Not pushing to the server because there is no remote.')
164
+ elsif local_branch == config.master_branch
165
+ logger.warn('Not pushing to the server because the current branch is the mainline branch.')
166
+ else
167
+ opts[:prepush].call if opts[:prepush]
168
+
169
+ push(remote.name, local_branch, remote_branch, :force => opts[:force])
170
+
171
+ opts[:postpush].call if opts[:postpush]
172
+ end
173
+ end
174
+
175
+
176
+ def config
177
+ if @config.nil?
178
+ @config = GitConfig.new(self)
179
+ end
180
+ @config
181
+ end
182
+
183
+
184
+ def remote
185
+ if @remote.nil?
186
+ @remote = GitProc::GitRemote.new(config)
187
+ end
188
+ @remote
189
+ end
190
+
191
+
192
+ # @return [Boolean] does this have a remote defined?
193
+ def has_a_remote?
194
+ remote.exists?
195
+ end
196
+
197
+
198
+ def add(file)
199
+ logger.info { "Adding #{[*file].join(', ')}" }
200
+ command(:add, ['--', file])
201
+ end
202
+
203
+
204
+ def commit(msg = nil)
205
+ logger.info 'Committing changes'
206
+ command(:commit, msg.nil? ? nil : ['-m', msg])
207
+ end
208
+
209
+
210
+ def rebase(upstream, opts = {})
211
+ args = []
212
+ if opts[:interactive]
213
+ logger.info { "Interactively rebasing #{branches.current.name} against #{upstream}" }
214
+ args << '-i'
215
+ args << upstream
216
+ elsif opts[:oldbase]
217
+ logger.info { "Doing rebase from #{opts[:oldbase]} against #{upstream} on #{branches.current.name}" }
218
+ args << '--onto' << upstream << opts[:oldbase] << branches.current.name
219
+ else
220
+ logger.info { "Rebasing #{branches.current.name} against #{upstream}" }
221
+ args << upstream
222
+ end
223
+ command('rebase', args)
224
+ end
225
+
226
+
227
+ def merge(base, opts= {})
228
+ logger.info { "Merging #{branches.current.name} with #{base}" }
229
+ args = []
230
+ args << '-s' << opts[:merge_strategy] if opts[:merge_strategy]
231
+ args << base
232
+ command(:merge, args)
233
+ end
234
+
235
+
236
+ def fetch(name = remote.name)
237
+ logger.info 'Fetching the latest changes from the server'
238
+ output = self.command(:fetch, ['-p', name])
239
+
240
+ log_fetch_changes(fetch_changes(output))
241
+
242
+ output
243
+ end
244
+
245
+
246
+ # @param [Hash] changes a hash of the changes that were made
247
+ #
248
+ # @return [void]
249
+ def log_fetch_changes(changes)
250
+ changes.each do |key, v|
251
+ unless v.empty?
252
+ logger.info { " #{key.to_s.sub(/_/, ' ')}: #{v.join(', ')}" }
253
+ end
254
+ end
255
+ end
256
+
257
+
258
+ # @return [Hash]
259
+ def fetch_changes(output)
260
+ changed = output.split("\n")
261
+
262
+ changes = {:new_branch => [], :new_tag => [], :force_updated => [], :deleted => [], :updated => []}
263
+
264
+ line = changed.shift
265
+
266
+ until line.nil? do
267
+ case line
268
+ when /^\s\s\s/
269
+ m = /^\s\s\s(\S+)\s+(\S+)\s/.match(line)
270
+ changes[:updated] << "#{m[2]} (#{m[1]})"
271
+ when /^\s\*\s\[new branch\]/
272
+ m = /^\s\*\s\[new branch\]\s+(\S+)\s/.match(line)
273
+ changes[:new_branch] << m[1]
274
+ when /^\s\*\s\[new tag\]/
275
+ m = /^\s\*\s\[new tag\]\s+(\S+)\s/.match(line)
276
+ changes[:new_tag] << m[1]
277
+ when /^\sx\s/
278
+ m = /^\sx\s\[deleted\]\s+\(none\)\s+->\s+[^\/]+\/(\S+)/.match(line)
279
+ changes[:deleted] << m[1]
280
+ when /^\s\+\s/
281
+ m = /^\s\+\s(\S+)\s+(\S+)\s/.match(line)
282
+ changes[:force_updated] << "#{m[2]} (#{m[1]})"
283
+ else
284
+ # ignore the line
285
+ end
286
+ line = changed.shift
287
+ end
288
+
289
+ changes
290
+ end
291
+
292
+
293
+ def branches
294
+ GitProc::GitBranches.new(self)
295
+ end
296
+
297
+
298
+ def remote_branches
299
+ GitProc::GitBranches.new(self, :remote => true)
300
+ end
301
+
302
+
303
+ #
304
+ # Does branch manipulation.
305
+ #
306
+ # @param [String] branch_name the name of the branch
307
+ #
308
+ # @option opts [Boolean] :delete delete the remote branch
309
+ # @option opts [Boolean] :force force the update
310
+ # @option opts [Boolean] :all list all branches, local and remote
311
+ # @option opts [Boolean] :no_color force not using any ANSI color codes
312
+ # @option opts [String] :rename the new name for the branch
313
+ # @option opts [String] :upstream the new branch to track
314
+ # @option opts [String] :base_branch the branch to base the new branch off of;
315
+ # defaults to 'master'
316
+ #
317
+ # @return [String] the output of running the git command
318
+ def branch(branch_name, opts = {})
319
+ if opts[:delete]
320
+ delete_branch(branch_name, opts[:force])
321
+ elsif opts[:rename]
322
+ rename_branch(branch_name, opts[:rename])
323
+ elsif opts[:upstream]
324
+ set_upstream_branch(branch_name, opts[:upstream])
325
+ elsif branch_name
326
+ if opts[:force]
327
+ change_branch(branch_name, opts[:base_branch])
328
+ else
329
+ create_branch(branch_name, opts[:base_branch])
330
+ end
331
+ else
332
+ #list_branches(opts)
333
+ list_branches(opts[:all], opts[:remote], opts[:no_color])
334
+ end
335
+ end
336
+
337
+
338
+ #
339
+ # Pushes the given branch to the server.
340
+ #
341
+ # @param [String] remote_name the repository name; nil -> 'origin'
342
+ # @param [String] local_branch the local branch to push; nil -> the current branch
343
+ # @param [String] remote_branch the name of the branch to push to; nil -> same as local_branch
344
+ #
345
+ # @option opts [Boolean, String] :delete delete the remote branch
346
+ # @option opts [Boolean] :force force the update, even if not a fast-forward
347
+ #
348
+ # @return [void]
349
+ #
350
+ # @raise [ArgumentError] if :delete is true, but no branch name is given
351
+ def push(remote_name, local_branch, remote_branch, opts = {})
352
+ remote_name ||= 'origin'
353
+
354
+ args = [remote_name]
355
+
356
+ if opts[:delete]
357
+ if remote_branch
358
+ rb = remote_branch
359
+ elsif local_branch
360
+ rb = local_branch
361
+ elsif !(opts[:delete].is_a? TrueClass)
362
+ rb = opts[:delete]
363
+ else
364
+ raise ArgumentError.new('Need a branch name to delete.')
365
+ end
366
+
367
+ int_branch = config.master_branch
368
+ if rb == int_branch
369
+ raise GitProc::GitProcessError.new("Can not delete the integration branch '#{int_branch}'")
370
+ end
371
+
372
+ logger.info { "Deleting remote branch '#{rb}' on '#{remote_name}'." }
373
+ args << '--delete' << rb
374
+ else
375
+ local_branch ||= branches.current
376
+ remote_branch ||= local_branch
377
+ args << '-f' if opts[:force]
378
+
379
+ logger.info do
380
+ if local_branch == remote_branch
381
+ "Pushing to '#{remote_branch}' on '#{remote_name}'."
382
+ else
383
+ "Pushing #{local_branch} to '#{remote_branch}' on '#{remote_name}'."
384
+ end
385
+ end
386
+
387
+ args << "#{local_branch}:#{remote_branch}"
388
+ end
389
+ command(:push, args)
390
+ end
391
+
392
+
393
+ def rebase_continue
394
+ command(:rebase, '--continue')
395
+ end
396
+
397
+
398
+ def stash_save
399
+ command(:stash, %w(save))
400
+ end
401
+
402
+
403
+ def stash_pop
404
+ command(:stash, %w(pop))
405
+ end
406
+
407
+
408
+ def show(refspec)
409
+ command(:show, refspec)
410
+ end
411
+
412
+
413
+ # @param [String] branch_name the name of the branch to checkout/create
414
+ # @option opts [Boolean] :no_track do not track the base branch
415
+ # @option opts [String] :new_branch the name of the base branch
416
+ #
417
+ # @return [void]
418
+ def checkout(branch_name, opts = {})
419
+ args = []
420
+ args << '--no-track' if opts[:no_track]
421
+ args << '-b' if opts[:new_branch]
422
+ args << branch_name
423
+ args << opts[:new_branch] if opts[:new_branch]
424
+ branches = branches()
425
+ command(:checkout, args)
426
+
427
+ branches << GitBranch.new(branch_name, opts[:new_branch] != nil, self)
428
+
429
+ if block_given?
430
+ yield
431
+ command(:checkout, branches.current.name)
432
+ branches.current
433
+ else
434
+ branches[branch_name]
435
+ end
436
+ end
437
+
438
+
439
+ def log_count
440
+ command(:log, '--oneline').split(/\n/).length
441
+ end
442
+
443
+
444
+ def remove(files, opts = {})
445
+ args = []
446
+ args << '-f' if opts[:force]
447
+ args << [*files]
448
+ command(:rm, args)
449
+ end
450
+
451
+
452
+ #
453
+ # Returns the status of the git repository.
454
+ #
455
+ # @return [Status]
456
+ def status
457
+ GitStatus.new(self)
458
+ end
459
+
460
+
461
+ # @return [String] the raw porcelain status string
462
+ def porcelain_status
463
+ command(:status, '--porcelain')
464
+ end
465
+
466
+
467
+ def reset(rev_name, opts = {})
468
+ args = []
469
+ args << '--hard' if opts[:hard]
470
+ args << rev_name
471
+
472
+ logger.info { "Resetting #{opts[:hard] ? '(hard)' : ''} to #{rev_name}" }
473
+
474
+ command(:reset, args)
475
+ end
476
+
477
+
478
+ def rev_list(start_revision, end_revision, opts ={})
479
+ args = []
480
+ args << "-#{opts[:num_revs]}" if opts[:num_revs]
481
+ args << '--oneline' if opts[:oneline]
482
+ args << "#{start_revision}..#{end_revision}"
483
+ command('rev-list', args)
484
+ end
485
+
486
+
487
+ def rev_parse(name)
488
+ command('rev-parse', name)
489
+ end
490
+
491
+
492
+ alias sha rev_parse
493
+
494
+
495
+ # @return [String]
496
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
497
+ ENV['GIT_INDEX_FILE'] = File.join(workdir, '.git', 'index')
498
+ ENV['GIT_DIR'] = File.join(workdir, '.git')
499
+ ENV['GIT_WORK_TREE'] = workdir
500
+ path = workdir
501
+
502
+ git_cmd = create_git_command(cmd, opts, redirect)
503
+
504
+ out = command_git_cmd(path, git_cmd, chdir, block)
505
+
506
+ if logger
507
+ logger.debug(git_cmd)
508
+ logger.debug(out)
509
+ end
510
+
511
+ handle_exitstatus($?, git_cmd, out)
512
+ end
513
+
514
+
515
+ def write_sync_control_file(branch_name)
516
+ latest_sha = rev_parse(branch_name)
517
+ filename = sync_control_filename(branch_name)
518
+ logger.debug { "Writing sync control file, #{filename}, with #{latest_sha}" }
519
+ File.open(filename, 'w') { |f| f.puts latest_sha }
520
+ end
521
+
522
+
523
+ def read_sync_control_file(branch_name)
524
+ filename = sync_control_filename(branch_name)
525
+ if File.exists?(filename)
526
+ sha = File.new(filename).readline.chop
527
+ logger.debug "Read sync control file, #{filename}: #{sha}"
528
+ sha
529
+ else
530
+ logger.debug "Sync control file, #{filename}, was not found"
531
+ nil
532
+ end
533
+ end
534
+
535
+
536
+ def delete_sync_control_file!(branch_name)
537
+ filename = sync_control_filename(branch_name)
538
+ logger.debug { "Deleting sync control file, #{filename}" }
539
+ File.delete(filename)
540
+ end
541
+
542
+
543
+ def sync_control_file_exists?(branch_name)
544
+ filename = sync_control_filename(branch_name)
545
+ File.exist?(filename)
546
+ end
547
+
548
+
549
+ private
550
+
551
+
552
+ def create_git_command(cmd, opts, redirect)
553
+ opts = [opts].flatten.map { |s| escape(s) }.join(' ')
554
+ "git #{cmd} #{opts} #{redirect} 2>&1"
555
+ end
556
+
557
+
558
+ def command_git_cmd(path, git_cmd, chdir, block)
559
+ out = nil
560
+ if chdir and (Dir.getwd != path)
561
+ Dir.chdir(path) { out = run_command(git_cmd, &block) }
562
+ else
563
+ out = run_command(git_cmd, &block)
564
+ end
565
+ out
566
+ end
567
+
568
+
569
+ # @return [String]
570
+ def handle_exitstatus(proc_status, git_cmd, out)
571
+ if proc_status.exitstatus > 0
572
+ unless proc_status.exitstatus == 1 && out == ''
573
+ raise GitProc::GitExecuteError.new(git_cmd + ':' + out.to_s)
574
+ end
575
+ end
576
+ out
577
+ end
578
+
579
+
580
+ def run_command(git_cmd, &block)
581
+ if block_given?
582
+ IO.popen(git_cmd, &block)
583
+ else
584
+ `#{git_cmd}`.chomp
585
+ end
586
+ end
587
+
588
+
589
+ def escape(s)
590
+ escaped = s.to_s.gsub('\'', '\'\\\'\'')
591
+ %Q{"#{escaped}"}
592
+ end
593
+
594
+
595
+ def change_branch(branch_name, base_branch)
596
+ raise ArgumentError.new('Need :base_branch when using :force for a branch.') unless base_branch
597
+ logger.info { "Changing branch '#{branch_name}' to point to '#{base_branch}'." }
598
+
599
+ command(:branch, ['-f', branch_name, base_branch])
600
+ end
601
+
602
+
603
+ def create_branch(branch_name, base_branch)
604
+ logger.info { "Creating new branch '#{branch_name}' based on '#{base_branch}'." }
605
+
606
+ command(:branch, [branch_name, (base_branch || 'master')])
607
+ end
608
+
609
+
610
+ def list_branches(all_branches, remote_branches, no_color)
611
+ args = []
612
+ args << '-a' if all_branches
613
+ args << '-r' if remote_branches
614
+ args << '--no-color' if no_color
615
+ command(:branch, args)
616
+ end
617
+
618
+
619
+ def delete_branch(branch_name, force)
620
+ logger.info { "Deleting local branch '#{branch_name}'." } unless branch_name == '_parking_'
621
+
622
+ command(:branch, [force ? '-D' : '-d', branch_name])
623
+ end
624
+
625
+
626
+ def rename_branch(branch_name, new_name)
627
+ logger.info { "Renaming branch '#{branch_name}' to '#{new_name}'." }
628
+
629
+ command(:branch, ['-m', branch_name, new_name])
630
+ end
631
+
632
+
633
+ def set_upstream_branch(branch_name, upstream)
634
+ logger.info { "Setting upstream/tracking for branch '#{branch_name}' to '#{upstream}'." }
635
+
636
+ command(:branch, ['--set-upstream-to', upstream, branch_name])
637
+ end
638
+
639
+
640
+ def sync_control_filename(branch_name)
641
+ File.join(File.join(workdir, '.git'), "gitprocess-sync-#{remote.name}--#{branch_name}")
642
+ end
643
+
644
+ end
645
+
646
+ end