sigterm_extensions 0.0.4

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