sigterm_extensions 0.0.4

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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.md +0 -0
  5. data/README.md +0 -0
  6. data/bin/ctxirb +156 -0
  7. data/lib/git.rb +166 -0
  8. data/lib/git/LICENSE +21 -0
  9. data/lib/git/author.rb +14 -0
  10. data/lib/git/base.rb +551 -0
  11. data/lib/git/base/factory.rb +75 -0
  12. data/lib/git/branch.rb +126 -0
  13. data/lib/git/branches.rb +71 -0
  14. data/lib/git/config.rb +22 -0
  15. data/lib/git/diff.rb +159 -0
  16. data/lib/git/index.rb +5 -0
  17. data/lib/git/lib.rb +1041 -0
  18. data/lib/git/log.rb +128 -0
  19. data/lib/git/object.rb +312 -0
  20. data/lib/git/path.rb +31 -0
  21. data/lib/git/remote.rb +36 -0
  22. data/lib/git/repository.rb +6 -0
  23. data/lib/git/stash.rb +27 -0
  24. data/lib/git/stashes.rb +55 -0
  25. data/lib/git/status.rb +199 -0
  26. data/lib/git/version.rb +5 -0
  27. data/lib/git/working_directory.rb +4 -0
  28. data/lib/sigterm_extensions.rb +75 -0
  29. data/lib/sigterm_extensions/all.rb +12 -0
  30. data/lib/sigterm_extensions/backtrace_cleaner.rb +129 -0
  31. data/lib/sigterm_extensions/callbacks.rb +847 -0
  32. data/lib/sigterm_extensions/concern.rb +169 -0
  33. data/lib/sigterm_extensions/configurable.rb +38 -0
  34. data/lib/sigterm_extensions/core_ext.rb +4 -0
  35. data/lib/sigterm_extensions/core_ext/array.rb +3 -0
  36. data/lib/sigterm_extensions/core_ext/array/extract.rb +19 -0
  37. data/lib/sigterm_extensions/core_ext/array/extract_options.rb +29 -0
  38. data/lib/sigterm_extensions/core_ext/class.rb +3 -0
  39. data/lib/sigterm_extensions/core_ext/class/attribute.rb +139 -0
  40. data/lib/sigterm_extensions/core_ext/class/attribute_accessors.rb +4 -0
  41. data/lib/sigterm_extensions/core_ext/class/subclasses.rb +52 -0
  42. data/lib/sigterm_extensions/core_ext/custom.rb +12 -0
  43. data/lib/sigterm_extensions/core_ext/digest.rb +3 -0
  44. data/lib/sigterm_extensions/core_ext/digest/uuid.rb +51 -0
  45. data/lib/sigterm_extensions/core_ext/enumerable.rb +232 -0
  46. data/lib/sigterm_extensions/core_ext/file.rb +3 -0
  47. data/lib/sigterm_extensions/core_ext/file/atomic.rb +68 -0
  48. data/lib/sigterm_extensions/core_ext/hash.rb +3 -0
  49. data/lib/sigterm_extensions/core_ext/hash/deep_merge.rb +41 -0
  50. data/lib/sigterm_extensions/core_ext/hash/deep_transform_values.rb +44 -0
  51. data/lib/sigterm_extensions/core_ext/hash/except.rb +22 -0
  52. data/lib/sigterm_extensions/core_ext/hash/keys.rb +141 -0
  53. data/lib/sigterm_extensions/core_ext/hash/reverse_merge.rb +23 -0
  54. data/lib/sigterm_extensions/core_ext/hash/slice.rb +24 -0
  55. data/lib/sigterm_extensions/core_ext/kernel.rb +3 -0
  56. data/lib/sigterm_extensions/core_ext/kernel/concern.rb +12 -0
  57. data/lib/sigterm_extensions/core_ext/kernel/reporting.rb +43 -0
  58. data/lib/sigterm_extensions/core_ext/kernel/singleton_class.rb +6 -0
  59. data/lib/sigterm_extensions/core_ext/load_error.rb +7 -0
  60. data/lib/sigterm_extensions/core_ext/module.rb +3 -0
  61. data/lib/sigterm_extensions/core_ext/module/aliasing.rb +29 -0
  62. data/lib/sigterm_extensions/core_ext/module/anonymous.rb +28 -0
  63. data/lib/sigterm_extensions/core_ext/module/attr_internal.rb +36 -0
  64. data/lib/sigterm_extensions/core_ext/module/attribute_accessors.rb +208 -0
  65. data/lib/sigterm_extensions/core_ext/module/attribute_accessors_per_thread.rb +146 -0
  66. data/lib/sigterm_extensions/core_ext/module/concerning.rb +132 -0
  67. data/lib/sigterm_extensions/core_ext/module/delegation.rb +319 -0
  68. data/lib/sigterm_extensions/core_ext/module/redefine_method.rb +38 -0
  69. data/lib/sigterm_extensions/core_ext/module/remove_method.rb +15 -0
  70. data/lib/sigterm_extensions/core_ext/name_error.rb +36 -0
  71. data/lib/sigterm_extensions/core_ext/object.rb +3 -0
  72. data/lib/sigterm_extensions/core_ext/object/blank.rb +153 -0
  73. data/lib/sigterm_extensions/core_ext/object/colors.rb +39 -0
  74. data/lib/sigterm_extensions/core_ext/object/duplicable.rb +47 -0
  75. data/lib/sigterm_extensions/core_ext/object/inclusion.rb +27 -0
  76. data/lib/sigterm_extensions/core_ext/object/instance_variables.rb +28 -0
  77. data/lib/sigterm_extensions/core_ext/object/methods.rb +61 -0
  78. data/lib/sigterm_extensions/core_ext/object/with_options.rb +80 -0
  79. data/lib/sigterm_extensions/core_ext/range.rb +3 -0
  80. data/lib/sigterm_extensions/core_ext/range/compare_range.rb +74 -0
  81. data/lib/sigterm_extensions/core_ext/range/conversions.rb +39 -0
  82. data/lib/sigterm_extensions/core_ext/range/overlaps.rb +8 -0
  83. data/lib/sigterm_extensions/core_ext/securerandom.rb +43 -0
  84. data/lib/sigterm_extensions/core_ext/string.rb +3 -0
  85. data/lib/sigterm_extensions/core_ext/string/access.rb +93 -0
  86. data/lib/sigterm_extensions/core_ext/string/filters.rb +143 -0
  87. data/lib/sigterm_extensions/core_ext/string/starts_ends_with.rb +4 -0
  88. data/lib/sigterm_extensions/core_ext/string/strip.rb +25 -0
  89. data/lib/sigterm_extensions/core_ext/tryable.rb +132 -0
  90. data/lib/sigterm_extensions/descendants_tracker.rb +108 -0
  91. data/lib/sigterm_extensions/gem_methods.rb +47 -0
  92. data/lib/sigterm_extensions/hash_binding.rb +16 -0
  93. data/lib/sigterm_extensions/inflector.rb +339 -0
  94. data/lib/sigterm_extensions/inflector/acronyms.rb +42 -0
  95. data/lib/sigterm_extensions/inflector/inflections.rb +249 -0
  96. data/lib/sigterm_extensions/inflector/inflections/defaults.rb +117 -0
  97. data/lib/sigterm_extensions/inflector/rules.rb +37 -0
  98. data/lib/sigterm_extensions/inflector/version.rb +8 -0
  99. data/lib/sigterm_extensions/interactive_editor.rb +120 -0
  100. data/lib/sigterm_extensions/lazy.rb +34 -0
  101. data/lib/sigterm_extensions/lazy_load_hooks.rb +79 -0
  102. data/lib/sigterm_extensions/option_merger.rb +32 -0
  103. data/lib/sigterm_extensions/ordered_hash.rb +48 -0
  104. data/lib/sigterm_extensions/ordered_options.rb +83 -0
  105. data/lib/sigterm_extensions/paths.rb +235 -0
  106. data/lib/sigterm_extensions/per_thread_registry.rb +58 -0
  107. data/lib/sigterm_extensions/proxy_object.rb +14 -0
  108. data/lib/sigterm_extensions/staging/boot.rb +31 -0
  109. data/lib/sigterm_extensions/staging/boot/bundler_patch.rb +24 -0
  110. data/lib/sigterm_extensions/staging/boot/command.rb +26 -0
  111. data/lib/sigterm_extensions/staging/boot/gemfile_next_auto_sync.rb +79 -0
  112. data/lib/sigterm_extensions/version.rb +4 -0
  113. data/lib/sigterm_extensions/wrappable.rb +16 -0
  114. data/sigterm_extensions.gemspec +42 -0
  115. data/templates/dotpryrc.rb.erb +124 -0
  116. metadata +315 -0
@@ -0,0 +1,5 @@
1
+ module Git
2
+ class Index < Git::Path
3
+
4
+ end
5
+ end
@@ -0,0 +1,1041 @@
1
+ require 'tempfile'
2
+
3
+ module Git
4
+
5
+ class GitExecuteError < StandardError
6
+ end
7
+
8
+ class Lib
9
+
10
+ @@semaphore = Mutex.new
11
+
12
+ def initialize(base = nil, logger = nil)
13
+ @git_dir = nil
14
+ @git_index_file = nil
15
+ @git_work_dir = nil
16
+ @path = nil
17
+
18
+ if base.is_a?(Git::Base)
19
+ @git_dir = base.repo.path
20
+ @git_index_file = base.index.path if base.index
21
+ @git_work_dir = base.dir.path if base.dir
22
+ elsif base.is_a?(Hash)
23
+ @git_dir = base[:repository]
24
+ @git_index_file = base[:index]
25
+ @git_work_dir = base[:working_directory]
26
+ end
27
+ @logger = logger
28
+ end
29
+
30
+ # creates or reinitializes the repository
31
+ #
32
+ # options:
33
+ # :bare
34
+ # :working_directory
35
+ #
36
+ def init(opts={})
37
+ arr_opts = []
38
+ arr_opts << '--bare' if opts[:bare]
39
+
40
+ command('init', arr_opts, false)
41
+ end
42
+
43
+ # tries to clone the given repo
44
+ #
45
+ # returns {:repository} (if bare)
46
+ # {:working_directory} otherwise
47
+ #
48
+ # accepts options:
49
+ # :bare:: no working directory
50
+ # :branch:: name of branch to track (rather than 'master')
51
+ # :depth:: the number of commits back to pull
52
+ # :origin:: name of remote (same as remote)
53
+ # :path:: directory where the repo will be cloned
54
+ # :remote:: name of remote (rather than 'origin')
55
+ # :recursive:: after the clone is created, initialize all submodules within, using their default settings.
56
+ #
57
+ # TODO - make this work with SSH password or auth_key
58
+ #
59
+ def clone(repository, name, opts = {})
60
+ @path = opts[:path] || '.'
61
+ clone_dir = opts[:path] ? File.join(@path, name) : name
62
+
63
+ arr_opts = []
64
+ arr_opts << '--bare' if opts[:bare]
65
+ arr_opts << '--branch' << opts[:branch] if opts[:branch]
66
+ arr_opts << '--depth' << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
67
+ arr_opts << '--config' << opts[:config] if opts[:config]
68
+ arr_opts << '--origin' << opts[:remote] || opts[:origin] if opts[:remote] || opts[:origin]
69
+ arr_opts << '--recursive' if opts[:recursive]
70
+ arr_opts << "--mirror" if opts[:mirror]
71
+
72
+ arr_opts << '--'
73
+
74
+ arr_opts << repository
75
+ arr_opts << clone_dir
76
+
77
+ command('clone', arr_opts)
78
+
79
+ (opts[:bare] or opts[:mirror]) ? {:repository => clone_dir} : {:working_directory => clone_dir}
80
+ end
81
+
82
+
83
+ ## READ COMMANDS ##
84
+
85
+ #
86
+ # Returns most recent tag that is reachable from a commit
87
+ #
88
+ # accepts options:
89
+ # :all
90
+ # :tags
91
+ # :contains
92
+ # :debug
93
+ # :exact_match
94
+ # :dirty
95
+ # :abbrev
96
+ # :candidates
97
+ # :long
98
+ # :always
99
+ # :math
100
+ #
101
+ # @param [String|NilClass] committish target commit sha or object name
102
+ # @param [{Symbol=>Object}] opts the given options
103
+ # @return [String] the tag name
104
+ #
105
+ def describe(committish=nil, opts={})
106
+ arr_opts = []
107
+
108
+ arr_opts << '--all' if opts[:all]
109
+ arr_opts << '--tags' if opts[:tags]
110
+ arr_opts << '--contains' if opts[:contains]
111
+ arr_opts << '--debug' if opts[:debug]
112
+ arr_opts << '--long' if opts[:long]
113
+ arr_opts << '--always' if opts[:always]
114
+ arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
115
+
116
+ arr_opts << '--dirty' if opts['dirty'] == true
117
+ arr_opts << "--dirty=#{opts['dirty']}" if opts['dirty'].is_a?(String)
118
+
119
+ arr_opts << "--abbrev=#{opts['abbrev']}" if opts[:abbrev]
120
+ arr_opts << "--candidates=#{opts['candidates']}" if opts[:candidates]
121
+ arr_opts << "--match=#{opts['match']}" if opts[:match]
122
+
123
+ arr_opts << committish if committish
124
+
125
+ return command('describe', arr_opts)
126
+ end
127
+
128
+ def log_commits(opts={})
129
+ arr_opts = log_common_options(opts)
130
+
131
+ arr_opts << '--pretty=oneline'
132
+
133
+ arr_opts += log_path_options(opts)
134
+
135
+ command_lines('log', arr_opts, true).map { |l| l.split.first }
136
+ end
137
+
138
+ def full_log_commits(opts={})
139
+ arr_opts = log_common_options(opts)
140
+
141
+ arr_opts << '--pretty=raw'
142
+ arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
143
+
144
+ arr_opts += log_path_options(opts)
145
+
146
+ full_log = command_lines('log', arr_opts, true)
147
+
148
+ process_commit_log_data(full_log)
149
+ end
150
+
151
+ def revparse(string)
152
+ return string if string =~ /^[A-Fa-f0-9]{40}$/ # passing in a sha - just no-op it
153
+ rev = ['head', 'remotes', 'tags'].map do |d|
154
+ File.join(@git_dir, 'refs', d, string)
155
+ end.find do |path|
156
+ File.file?(path)
157
+ end
158
+ return File.read(rev).chomp if rev
159
+ command('rev-parse', string)
160
+ end
161
+
162
+ def namerev(string)
163
+ command('name-rev', string).split[1]
164
+ end
165
+
166
+ def object_type(sha)
167
+ command('cat-file', ['-t', sha])
168
+ end
169
+
170
+ def object_size(sha)
171
+ command('cat-file', ['-s', sha]).to_i
172
+ end
173
+
174
+ # returns useful array of raw commit object data
175
+ def commit_data(sha)
176
+ sha = sha.to_s
177
+ cdata = command_lines('cat-file', ['commit', sha])
178
+ process_commit_data(cdata, sha, 0)
179
+ end
180
+
181
+ def process_commit_data(data, sha = nil, indent = 4)
182
+ hsh = {
183
+ 'sha' => sha,
184
+ 'message' => '',
185
+ 'parent' => []
186
+ }
187
+
188
+ loop do
189
+ key, *value = data.shift.split
190
+
191
+ break if key.nil?
192
+
193
+ if key == 'parent'
194
+ hsh['parent'] << value.join(' ')
195
+ else
196
+ hsh[key] = value.join(' ')
197
+ end
198
+ end
199
+
200
+ hsh['message'] = data.collect {|line| line[indent..-1]}.join("\n") + "\n"
201
+
202
+ return hsh
203
+ end
204
+
205
+ def tag_data(name)
206
+ sha = sha.to_s
207
+ tdata = command_lines('cat-file', ['tag', name])
208
+ process_tag_data(tdata, name, 0)
209
+ end
210
+
211
+ def process_tag_data(data, name, indent=4)
212
+ hsh = {
213
+ 'name' => name,
214
+ 'message' => ''
215
+ }
216
+
217
+ loop do
218
+ key, *value = data.shift.split
219
+
220
+ break if key.nil?
221
+
222
+ hsh[key] = value.join(' ')
223
+ end
224
+
225
+ hsh['message'] = data.collect {|line| line[indent..-1]}.join("\n") + "\n"
226
+
227
+ return hsh
228
+ end
229
+
230
+ def process_commit_log_data(data)
231
+ in_message = false
232
+
233
+ hsh_array = []
234
+
235
+ hsh = nil
236
+
237
+ data.each do |line|
238
+ line = line.chomp
239
+
240
+ if line[0].nil?
241
+ in_message = !in_message
242
+ next
243
+ end
244
+
245
+ if in_message
246
+ hsh['message'] << "#{line[4..-1]}\n"
247
+ next
248
+ end
249
+
250
+ key, *value = line.split
251
+ value = value.join(' ')
252
+
253
+ case key
254
+ when 'commit'
255
+ hsh_array << hsh if hsh
256
+ hsh = {'sha' => value, 'message' => '', 'parent' => []}
257
+ when 'parent'
258
+ hsh['parent'] << value
259
+ else
260
+ hsh[key] = value
261
+ end
262
+ end
263
+
264
+ hsh_array << hsh if hsh
265
+
266
+ return hsh_array
267
+ end
268
+
269
+ def object_contents(sha, &block)
270
+ command('cat-file', ['-p', sha], &block)
271
+ end
272
+
273
+ def ls_tree(sha)
274
+ data = {'blob' => {}, 'tree' => {}}
275
+
276
+ command_lines('ls-tree', sha).each do |line|
277
+ (info, filenm) = line.split("\t")
278
+ (mode, type, sha) = info.split
279
+ data[type][filenm] = {:mode => mode, :sha => sha}
280
+ end
281
+
282
+ data
283
+ end
284
+
285
+ def mv(file1, file2)
286
+ command_lines('mv', ['--', file1, file2])
287
+ end
288
+
289
+ def full_tree(sha)
290
+ command_lines('ls-tree', ['-r', sha])
291
+ end
292
+
293
+ def tree_depth(sha)
294
+ full_tree(sha).size
295
+ end
296
+
297
+ def change_head_branch(branch_name)
298
+ command('symbolic-ref', ['HEAD', "refs/heads/#{branch_name}"])
299
+ end
300
+
301
+ def branches_all
302
+ arr = []
303
+ command_lines('branch', '-a').each do |b|
304
+ current = (b[0, 2] == '* ')
305
+ arr << [b.gsub('* ', '').strip, current]
306
+ end
307
+ arr
308
+ end
309
+
310
+ def list_files(ref_dir)
311
+ dir = File.join(@git_dir, 'refs', ref_dir)
312
+ files = []
313
+ Dir.chdir(dir) { files = Dir.glob('**/*').select { |f| File.file?(f) } } rescue nil
314
+ files
315
+ end
316
+
317
+ def branch_current
318
+ branches_all.select { |b| b[1] }.first[0] rescue nil
319
+ end
320
+
321
+ def branch_contains(commit, branch_name="")
322
+ command("branch", [branch_name, "--contains", commit])
323
+ end
324
+
325
+ # returns hash
326
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
327
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
328
+ def grep(string, opts = {})
329
+ opts[:object] ||= 'HEAD'
330
+
331
+ grep_opts = ['-n']
332
+ grep_opts << '-i' if opts[:ignore_case]
333
+ grep_opts << '-v' if opts[:invert_match]
334
+ grep_opts << '-e'
335
+ grep_opts << string
336
+ grep_opts << opts[:object] if opts[:object].is_a?(String)
337
+ grep_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
338
+
339
+ hsh = {}
340
+ command_lines('grep', grep_opts).each do |line|
341
+ if m = /(.*)\:(\d+)\:(.*)/.match(line)
342
+ hsh[m[1]] ||= []
343
+ hsh[m[1]] << [m[2].to_i, m[3]]
344
+ end
345
+ end
346
+ hsh
347
+ end
348
+
349
+ def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
350
+ diff_opts = ['-p']
351
+ diff_opts << obj1
352
+ diff_opts << obj2 if obj2.is_a?(String)
353
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
354
+
355
+ command('diff', diff_opts)
356
+ end
357
+
358
+ def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
359
+ diff_opts = ['--numstat']
360
+ diff_opts << obj1
361
+ diff_opts << obj2 if obj2.is_a?(String)
362
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
363
+
364
+ hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}
365
+
366
+ command_lines('diff', diff_opts).each do |file|
367
+ (insertions, deletions, filename) = file.split("\t")
368
+ hsh[:total][:insertions] += insertions.to_i
369
+ hsh[:total][:deletions] += deletions.to_i
370
+ hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
371
+ hsh[:total][:files] += 1
372
+ hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
373
+ end
374
+
375
+ hsh
376
+ end
377
+
378
+ def diff_name_status(reference1 = nil, reference2 = nil, opts = {})
379
+ opts_arr = ['--name-status']
380
+ opts_arr << reference1 if reference1
381
+ opts_arr << reference2 if reference2
382
+
383
+ opts_arr << '--' << opts[:path] if opts[:path]
384
+
385
+ command_lines('diff', opts_arr).inject({}) do |memo, line|
386
+ status, path = line.split("\t")
387
+ memo[path] = status
388
+ memo
389
+ end
390
+ end
391
+
392
+ # compares the index and the working directory
393
+ def diff_files
394
+ diff_as_hash('diff-files')
395
+ end
396
+
397
+ # compares the index and the repository
398
+ def diff_index(treeish)
399
+ diff_as_hash('diff-index', treeish)
400
+ end
401
+
402
+ def ls_files(location=nil)
403
+ location ||= '.'
404
+ hsh = {}
405
+ command_lines('ls-files', ['--stage', location]).each do |line|
406
+ (info, file) = line.split("\t")
407
+ (mode, sha, stage) = info.split
408
+ file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
409
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
410
+ end
411
+ hsh
412
+ end
413
+
414
+ def ls_remote(location=nil)
415
+ location ||= '.'
416
+ Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
417
+ command_lines('ls-remote', [location], false).each do |line|
418
+ (sha, info) = line.split("\t")
419
+ (ref, type, name) = info.split('/', 3)
420
+ type ||= 'head'
421
+ type = 'branches' if type == 'heads'
422
+ value = {:ref => ref, :sha => sha}
423
+ hsh[type].update( name.nil? ? value : { name => value })
424
+ end
425
+ end
426
+ end
427
+
428
+ def ignored_files
429
+ command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
430
+ end
431
+
432
+
433
+ def config_remote(name)
434
+ hsh = {}
435
+ config_list.each do |key, value|
436
+ if /remote.#{name}/.match(key)
437
+ hsh[key.gsub("remote.#{name}.", '')] = value
438
+ end
439
+ end
440
+ hsh
441
+ end
442
+
443
+ def config_get(name)
444
+ do_get = lambda do |path|
445
+ command('config', ['--get', name])
446
+ end
447
+
448
+ if @git_dir
449
+ Dir.chdir(@git_dir, &do_get)
450
+ else
451
+ do_get.call
452
+ end
453
+ end
454
+
455
+ def global_config_get(name)
456
+ command('config', ['--global', '--get', name], false)
457
+ end
458
+
459
+ def config_list
460
+ build_list = lambda do |path|
461
+ parse_config_list command_lines('config', ['--list'])
462
+ end
463
+
464
+ if @git_dir
465
+ Dir.chdir(@git_dir, &build_list)
466
+ else
467
+ build_list.call
468
+ end
469
+ end
470
+
471
+ def global_config_list
472
+ parse_config_list command_lines('config', ['--global', '--list'], false)
473
+ end
474
+
475
+ def parse_config_list(lines)
476
+ hsh = {}
477
+ lines.each do |line|
478
+ (key, *values) = line.split('=')
479
+ hsh[key] = values.join('=')
480
+ end
481
+ hsh
482
+ end
483
+
484
+ def parse_config(file)
485
+ parse_config_list command_lines('config', ['--list', '--file', file], false)
486
+ end
487
+
488
+ # Shows objects
489
+ #
490
+ # @param [String|NilClass] objectish the target object reference (nil == HEAD)
491
+ # @param [String|NilClass] path the path of the file to be shown
492
+ # @return [String] the object information
493
+ def show(objectish=nil, path=nil)
494
+ arr_opts = []
495
+
496
+ arr_opts << (path ? "#{objectish}:#{path}" : objectish)
497
+
498
+ command('show', arr_opts.compact)
499
+ end
500
+
501
+ ## WRITE COMMANDS ##
502
+
503
+ def config_set(name, value)
504
+ command('config', [name, value])
505
+ end
506
+
507
+ def global_config_set(name, value)
508
+ command('config', ['--global', name, value], false)
509
+ end
510
+
511
+ # updates the repository index using the working directory content
512
+ #
513
+ # lib.add('path/to/file')
514
+ # lib.add(['path/to/file1','path/to/file2'])
515
+ # lib.add(:all => true)
516
+ #
517
+ # options:
518
+ # :all => true
519
+ # :force => true
520
+ #
521
+ # @param [String,Array] paths files paths to be added to the repository
522
+ # @param [Hash] options
523
+ def add(paths='.',options={})
524
+ arr_opts = []
525
+
526
+ arr_opts << '--all' if options[:all]
527
+ arr_opts << '--force' if options[:force]
528
+
529
+ arr_opts << '--'
530
+
531
+ arr_opts << paths
532
+
533
+ arr_opts.flatten!
534
+
535
+ command('add', arr_opts)
536
+ end
537
+
538
+ def remove(path = '.', opts = {})
539
+ arr_opts = ['-f'] # overrides the up-to-date check by default
540
+ arr_opts << ['-r'] if opts[:recursive]
541
+ arr_opts << ['--cached'] if opts[:cached]
542
+ arr_opts << '--'
543
+ if path.is_a?(Array)
544
+ arr_opts += path
545
+ else
546
+ arr_opts << path
547
+ end
548
+
549
+ command('rm', arr_opts)
550
+ end
551
+
552
+ def commit(message, opts = {})
553
+ arr_opts = []
554
+ arr_opts << "--message=#{message}" if message
555
+ arr_opts << '--amend' << '--no-edit' if opts[:amend]
556
+ arr_opts << '--all' if opts[:add_all] || opts[:all]
557
+ arr_opts << '--allow-empty' if opts[:allow_empty]
558
+ arr_opts << "--author=#{opts[:author]}" if opts[:author]
559
+ arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
560
+
561
+ command('commit', arr_opts)
562
+ end
563
+
564
+ def reset(commit, opts = {})
565
+ arr_opts = []
566
+ arr_opts << '--hard' if opts[:hard]
567
+ arr_opts << commit if commit
568
+ command('reset', arr_opts)
569
+ end
570
+
571
+ def clean(opts = {})
572
+ arr_opts = []
573
+ arr_opts << '--force' if opts[:force]
574
+ arr_opts << '-d' if opts[:d]
575
+ arr_opts << '-x' if opts[:x]
576
+
577
+ command('clean', arr_opts)
578
+ end
579
+
580
+ def revert(commitish, opts = {})
581
+ # Forcing --no-edit as default since it's not an interactive session.
582
+ opts = {:no_edit => true}.merge(opts)
583
+
584
+ arr_opts = []
585
+ arr_opts << '--no-edit' if opts[:no_edit]
586
+ arr_opts << commitish
587
+
588
+ command('revert', arr_opts)
589
+ end
590
+
591
+ def apply(patch_file)
592
+ arr_opts = []
593
+ arr_opts << '--' << patch_file if patch_file
594
+ command('apply', arr_opts)
595
+ end
596
+
597
+ def apply_mail(patch_file)
598
+ arr_opts = []
599
+ arr_opts << '--' << patch_file if patch_file
600
+ command('am', arr_opts)
601
+ end
602
+
603
+ def stashes_all
604
+ arr = []
605
+ filename = File.join(@git_dir, 'logs/refs/stash')
606
+ if File.exist?(filename)
607
+ File.open(filename) do |f|
608
+ f.each_with_index do |line, i|
609
+ m = line.match(/:(.*)$/)
610
+ arr << [i, m[1].strip]
611
+ end
612
+ end
613
+ end
614
+ arr
615
+ end
616
+
617
+ def stash_save(message)
618
+ output = command('stash save', ['--', message])
619
+ output =~ /HEAD is now at/
620
+ end
621
+
622
+ def stash_apply(id = nil)
623
+ if id
624
+ command('stash apply', [id])
625
+ else
626
+ command('stash apply')
627
+ end
628
+ end
629
+
630
+ def stash_clear
631
+ command('stash clear')
632
+ end
633
+
634
+ def stash_list
635
+ command('stash list')
636
+ end
637
+
638
+ def branch_new(branch)
639
+ command('branch', branch)
640
+ end
641
+
642
+ def branch_delete(branch)
643
+ command('branch', ['-D', branch])
644
+ end
645
+
646
+ def checkout(branch, opts = {})
647
+ arr_opts = []
648
+ arr_opts << '-b' if opts[:new_branch] || opts[:b]
649
+ arr_opts << '--force' if opts[:force] || opts[:f]
650
+ arr_opts << branch
651
+
652
+ command('checkout', arr_opts)
653
+ end
654
+
655
+ def checkout_file(version, file)
656
+ arr_opts = []
657
+ arr_opts << version
658
+ arr_opts << file
659
+ command('checkout', arr_opts)
660
+ end
661
+
662
+ def merge(branch, message = nil)
663
+ arr_opts = []
664
+ arr_opts << '-m' << message if message
665
+ arr_opts += [branch]
666
+ command('merge', arr_opts)
667
+ end
668
+
669
+ def unmerged
670
+ unmerged = []
671
+ command_lines('diff', ["--cached"]).each do |line|
672
+ unmerged << $1 if line =~ /^\* Unmerged path (.*)/
673
+ end
674
+ unmerged
675
+ end
676
+
677
+ def conflicts # :yields: file, your, their
678
+ self.unmerged.each do |f|
679
+ your = Tempfile.new("YOUR-#{File.basename(f)}").path
680
+ command('show', ":2:#{f}", true, "> #{escape your}")
681
+
682
+ their = Tempfile.new("THEIR-#{File.basename(f)}").path
683
+ command('show', ":3:#{f}", true, "> #{escape their}")
684
+ yield(f, your, their)
685
+ end
686
+ end
687
+
688
+ def remote_add(name, url, opts = {})
689
+ arr_opts = ['add']
690
+ arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
691
+ arr_opts << '-t' << opts[:track] if opts[:track]
692
+ arr_opts << '--'
693
+ arr_opts << name
694
+ arr_opts << url
695
+
696
+ command('remote', arr_opts)
697
+ end
698
+
699
+ def remote_set_url(name, url)
700
+ arr_opts = ['set-url']
701
+ arr_opts << name
702
+ arr_opts << url
703
+
704
+ command('remote', arr_opts)
705
+ end
706
+
707
+ def remote_remove(name)
708
+ command('remote', ['rm', name])
709
+ end
710
+
711
+ def remotes
712
+ command_lines('remote')
713
+ end
714
+
715
+ def tags
716
+ command_lines('tag')
717
+ end
718
+
719
+ def tag(name, *opts)
720
+ target = opts[0].instance_of?(String) ? opts[0] : nil
721
+
722
+ opts = opts.last.instance_of?(Hash) ? opts.last : {}
723
+
724
+ if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
725
+ raise "Can not create an [:a|:annotate] tag without the precense of [:m|:message]."
726
+ end
727
+
728
+ arr_opts = []
729
+
730
+ arr_opts << '-f' if opts[:force] || opts[:f]
731
+ arr_opts << '-a' if opts[:a] || opts[:annotate]
732
+ arr_opts << '-s' if opts[:s] || opts[:sign]
733
+ arr_opts << '-d' if opts[:d] || opts[:delete]
734
+ arr_opts << name
735
+ arr_opts << target if target
736
+
737
+ if opts[:m] || opts[:message]
738
+ arr_opts << '-m' << (opts[:m] || opts[:message])
739
+ end
740
+
741
+ command('tag', arr_opts)
742
+ end
743
+
744
+
745
+ def fetch(remote, opts)
746
+ arr_opts = [remote]
747
+ arr_opts << opts[:ref] if opts[:ref]
748
+ arr_opts << '--tags' if opts[:t] || opts[:tags]
749
+ arr_opts << '--prune' if opts[:p] || opts[:prune]
750
+
751
+ command('fetch', arr_opts)
752
+ end
753
+
754
+ def push(remote, branch = 'master', opts = {})
755
+ # Small hack to keep backwards compatibility with the 'push(remote, branch, tags)' method signature.
756
+ opts = {:tags => opts} if [true, false].include?(opts)
757
+
758
+ arr_opts = []
759
+ arr_opts << '--mirror' if opts[:mirror]
760
+ arr_opts << '--delete' if opts[:delete]
761
+ arr_opts << '--force' if opts[:force] || opts[:f]
762
+ arr_opts << remote
763
+
764
+ if opts[:mirror]
765
+ command('push', arr_opts)
766
+ else
767
+ command('push', arr_opts + [branch])
768
+ command('push', ['--tags'] + arr_opts) if opts[:tags]
769
+ end
770
+ end
771
+
772
+ def pull(remote='origin', branch='master')
773
+ command('pull', [remote, branch])
774
+ end
775
+
776
+ def tag_sha(tag_name)
777
+ head = File.join(@git_dir, 'refs', 'tags', tag_name)
778
+ return File.read(head).chomp if File.exist?(head)
779
+
780
+ command('show-ref', ['--tags', '-s', tag_name])
781
+ end
782
+
783
+ def repack
784
+ command('repack', ['-a', '-d'])
785
+ end
786
+
787
+ def gc
788
+ command('gc', ['--prune', '--aggressive', '--auto'])
789
+ end
790
+
791
+ # reads a tree into the current index file
792
+ def read_tree(treeish, opts = {})
793
+ arr_opts = []
794
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
795
+ arr_opts += [treeish]
796
+ command('read-tree', arr_opts)
797
+ end
798
+
799
+ def write_tree
800
+ command('write-tree')
801
+ end
802
+
803
+ def commit_tree(tree, opts = {})
804
+ opts[:message] ||= "commit tree #{tree}"
805
+ t = Tempfile.new('commit-message')
806
+ t.write(opts[:message])
807
+ t.close
808
+
809
+ arr_opts = []
810
+ arr_opts << tree
811
+ arr_opts << '-p' << opts[:parent] if opts[:parent]
812
+ arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
813
+ command('commit-tree', arr_opts, true, "< #{escape t.path}")
814
+ end
815
+
816
+ def update_ref(branch, commit)
817
+ command('update-ref', [branch, commit])
818
+ end
819
+
820
+ def checkout_index(opts = {})
821
+ arr_opts = []
822
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
823
+ arr_opts << "--force" if opts[:force]
824
+ arr_opts << "--all" if opts[:all]
825
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
826
+
827
+ command('checkout-index', arr_opts)
828
+ end
829
+
830
+ # creates an archive file
831
+ #
832
+ # options
833
+ # :format (zip, tar)
834
+ # :prefix
835
+ # :remote
836
+ # :path
837
+ def archive(sha, file = nil, opts = {})
838
+ opts[:format] ||= 'zip'
839
+
840
+ if opts[:format] == 'tgz'
841
+ opts[:format] = 'tar'
842
+ opts[:add_gzip] = true
843
+ end
844
+
845
+ if !file
846
+ tempfile = Tempfile.new('archive')
847
+ file = tempfile.path
848
+ # delete it now, before we write to it, so that Ruby doesn't delete it
849
+ # when it finalizes the Tempfile.
850
+ tempfile.close!
851
+ end
852
+
853
+ arr_opts = []
854
+ arr_opts << "--format=#{opts[:format]}" if opts[:format]
855
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
856
+ arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
857
+ arr_opts << sha
858
+ arr_opts << '--' << opts[:path] if opts[:path]
859
+ command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
860
+ return file
861
+ end
862
+
863
+ # returns the current version of git, as an Array of Fixnums.
864
+ def current_command_version
865
+ output = command('version', [], false)
866
+ version = output[/\d+\.\d+(\.\d+)+/]
867
+ version.split('.').collect {|i| i.to_i}
868
+ end
869
+
870
+ def required_command_version
871
+ [1, 6]
872
+ end
873
+
874
+ def meets_required_version?
875
+ (self.current_command_version <=> self.required_command_version) >= 0
876
+ end
877
+
878
+
879
+ private
880
+
881
+ # Systen ENV variables involved in the git commands.
882
+ #
883
+ # @return [<String>] the names of the EVN variables involved in the git commands
884
+ ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
885
+
886
+ def command_lines(cmd, opts = [], chdir = true, redirect = '')
887
+ cmd_op = command(cmd, opts, chdir)
888
+ if cmd_op.encoding.name != "UTF-8"
889
+ op = cmd_op.encode("UTF-8", "binary", {
890
+ :invalid => :replace,
891
+ :undef => :replace
892
+ })
893
+ else
894
+ op = cmd_op
895
+ end
896
+ op.split("\n")
897
+ end
898
+
899
+ # Takes the current git's system ENV variables and store them.
900
+ def store_git_system_env_variables
901
+ @git_system_env_variables = {}
902
+ ENV_VARIABLE_NAMES.each do |env_variable_name|
903
+ @git_system_env_variables[env_variable_name] = ENV[env_variable_name]
904
+ end
905
+ end
906
+
907
+ # Takes the previously stored git's ENV variables and set them again on ENV.
908
+ def restore_git_system_env_variables
909
+ ENV_VARIABLE_NAMES.each do |env_variable_name|
910
+ ENV[env_variable_name] = @git_system_env_variables[env_variable_name]
911
+ end
912
+ end
913
+
914
+ # Sets git's ENV variables to the custom values for the current instance.
915
+ def set_custom_git_env_variables
916
+ ENV['GIT_DIR'] = @git_dir
917
+ ENV['GIT_WORK_TREE'] = @git_work_dir
918
+ ENV['GIT_INDEX_FILE'] = @git_index_file
919
+ ENV['GIT_SSH'] = Git::Base.config.git_ssh
920
+ end
921
+
922
+ # Runs a block inside an environment with customized ENV variables.
923
+ # It restores the ENV after execution.
924
+ #
925
+ # @param [Proc] block block to be executed within the customized environment
926
+ def with_custom_env_variables(&block)
927
+ @@semaphore.synchronize do
928
+ store_git_system_env_variables()
929
+ set_custom_git_env_variables()
930
+ return block.call()
931
+ end
932
+ ensure
933
+ restore_git_system_env_variables()
934
+ end
935
+
936
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
937
+ global_opts = []
938
+ global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
939
+ global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
940
+
941
+ opts = [opts].flatten.map {|s| escape(s) }.join(' ')
942
+
943
+ global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
944
+
945
+ git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{redirect} 2>&1"
946
+
947
+ output = nil
948
+
949
+ command_thread = nil;
950
+
951
+ exitstatus = nil
952
+
953
+ with_custom_env_variables do
954
+ command_thread = Thread.new do
955
+ output = run_command(git_cmd, &block)
956
+ exitstatus = $?.exitstatus
957
+ end
958
+ command_thread.join
959
+ end
960
+
961
+ if @logger
962
+ @logger.info(git_cmd)
963
+ @logger.debug(output)
964
+ end
965
+
966
+ if exitstatus > 1 || (exitstatus == 1 && output != '')
967
+ raise Git::GitExecuteError.new(git_cmd + ':' + output.to_s)
968
+ end
969
+
970
+ return output
971
+ end
972
+
973
+ # Takes the diff command line output (as Array) and parse it into a Hash
974
+ #
975
+ # @param [String] diff_command the diff commadn to be used
976
+ # @param [Array] opts the diff options to be used
977
+ # @return [Hash] the diff as Hash
978
+ def diff_as_hash(diff_command, opts=[])
979
+ command_lines(diff_command, opts).inject({}) do |memo, line|
980
+ info, file = line.split("\t")
981
+ mode_src, mode_dest, sha_src, sha_dest, type = info.split
982
+
983
+ memo[file] = {
984
+ :mode_index => mode_dest,
985
+ :mode_repo => mode_src.to_s[1, 7],
986
+ :path => file,
987
+ :sha_repo => sha_src,
988
+ :sha_index => sha_dest,
989
+ :type => type
990
+ }
991
+
992
+ memo
993
+ end
994
+ end
995
+
996
+ # Returns an array holding the common options for the log commands
997
+ #
998
+ # @param [Hash] opts the given options
999
+ # @return [Array] the set of common options that the log command will use
1000
+ def log_common_options(opts)
1001
+ arr_opts = []
1002
+
1003
+ arr_opts << "-#{opts[:count]}" if opts[:count]
1004
+ arr_opts << "--no-color"
1005
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
1006
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
1007
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
1008
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
1009
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
1010
+
1011
+ arr_opts
1012
+ end
1013
+
1014
+ # Retrurns an array holding path options for the log commands
1015
+ #
1016
+ # @param [Hash] opts the given options
1017
+ # @return [Array] the set of path options that the log command will use
1018
+ def log_path_options(opts)
1019
+ arr_opts = []
1020
+
1021
+ arr_opts << opts[:object] if opts[:object].is_a? String
1022
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter]
1023
+ arr_opts
1024
+ end
1025
+
1026
+ def run_command(git_cmd, &block)
1027
+ return IO.popen(git_cmd, &block) if block_given?
1028
+
1029
+ `#{git_cmd}`.chomp
1030
+ end
1031
+
1032
+ def escape(s)
1033
+ return "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'" if RUBY_PLATFORM !~ /mingw|mswin/
1034
+
1035
+ # Keeping the old escape format for windows users
1036
+ escaped = s.to_s.gsub('\'', '\'\\\'\'')
1037
+ return %Q{"#{escaped}"}
1038
+ end
1039
+
1040
+ end
1041
+ end