git-trifle 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ module Git
2
+ class Trifle
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
data/lib/git/trifle.rb ADDED
@@ -0,0 +1,383 @@
1
+ # encoding: utf-8
2
+
3
+ # This is merely an abstract layer to Git
4
+ # So far, i intend to have nothing more than a hand capable
5
+ # of seizing handlers on the row, on the fly
6
+
7
+ # My main goal here is to stick as little as possible to the
8
+ # underlying git lib. I also want to be able to change if i
9
+ # must, as painlessly as possible
10
+ #
11
+ # That's why the code below is a bit of Enumerable fest
12
+ # (to deplete the underlying lib of all class instances, and
13
+ # rather work on arrays of strings (paths names, branches
14
+ # names, remotes names, commit sha, etc...)
15
+
16
+ module Git
17
+
18
+ class Trifle
19
+
20
+ extend Forwardable
21
+
22
+ STATUS_LIST = [:changed, :added, :deleted, :untracked].freeze
23
+
24
+ # Needless to do more than this for the following methods
25
+ # Very neat BTW.
26
+ DELEGATORS = %W|
27
+ add add_remote apply
28
+ branch branches
29
+ current_branch commit
30
+ fetch
31
+ log ls_files
32
+ merge
33
+ pull push
34
+ reset remotes remove
35
+ |.
36
+ map(&:to_sym).
37
+ freeze
38
+
39
+ def_delegators :@layer, *DELEGATORS
40
+
41
+ def initialize(options={})
42
+ @dressing = []
43
+
44
+ if options.is_a? String
45
+ cover options
46
+ elsif options[:clone]
47
+ clone options.merge!(remote: options[:clone])
48
+ elsif options[:init]
49
+ init options.merge!(path: options[:init])
50
+ end
51
+ end
52
+
53
+ # hands on the handler
54
+ def cover(path, options={})
55
+ reset = options.delete :reset
56
+
57
+ cook_layer do
58
+ @dressing << Proc.new { self.reset if commits.any? } if reset
59
+ Git::Base.open path if can_cover? path
60
+ end
61
+ end
62
+
63
+ def clone(options)
64
+ path, name = File.split options[:path]
65
+ remote = options.delete :remote
66
+ reset = options.delete :reset
67
+
68
+ cook_layer do
69
+ # Dunno why i have to do that, but that's a fact. Ain't workin' without...
70
+ FileUtils.mkdir_p path
71
+
72
+ @dressing << Proc.new { self.reset if commits.any? } if reset
73
+ Git.clone remote, name, options.merge!(path: path)
74
+ end
75
+ end
76
+
77
+ def init(options)
78
+ path = options.delete :path
79
+ remote = options.delete :remote
80
+ remote_name = options.delete(:remote_name) { 'origin' }
81
+
82
+ cook_layer do
83
+ @dressing << Proc.new { self.add_remote remote_name, remote } if remote
84
+ Git.init path, options
85
+ end
86
+ end
87
+
88
+ def create_branch(name, options={})
89
+ # go to bed Captain, we don't need you here
90
+ options[:track] = remote_branch_for(name) if options.delete :track_remote
91
+
92
+ # mere delegation with options
93
+ branch(name, options).create
94
+ end
95
+
96
+ def delete_branch(branch)
97
+ # woodsman advice : dude, don't saw the branch you're on
98
+ return if branch == current_branch
99
+ # actual mere delegation
100
+ branch(branch).delete
101
+ end
102
+
103
+ def push_branch(branch=nil)
104
+ # we don't need Captain Obvious here
105
+ # Rest Captain, greater tasks await you !
106
+ branch ||= current_branch
107
+ push remote_for(branch), branch
108
+ end
109
+
110
+ def checkout(name, options={})
111
+ # avoid crash when repo' was just init'ed
112
+ return false unless can_checkout? name
113
+
114
+ # 0 but true
115
+ return true if name == current_branch
116
+
117
+ # -b option on command line
118
+ options.merge! new_branch: true unless name.nil? || has_branch?(name)
119
+
120
+ # wipe all options to let git to the job and checkout
121
+ # tracking branch. YKWIM ? then DWIM
122
+ options = {} if has_remote_branch? name
123
+ @layer.checkout name, options
124
+ end
125
+
126
+ # i know, it exists in Git gem. But i prefer having here
127
+ # with my own checkout method as a pivotal point for all
128
+ # checkouts (as long as it is accurate)
129
+ def checkout_files(files)
130
+ files = Array(files).select { |path| files_paths.include? path }
131
+ checkout nil, files: files if files
132
+ end
133
+
134
+ def checkout_deleted_files
135
+ checkout_files files_with_status(:deleted)
136
+ end
137
+
138
+ def checkout_changed_files
139
+ checkout_files files_with_status(:changed)
140
+ end
141
+
142
+ def push_file(file, options={})
143
+ # yeah yeah i allow to push on another branch in one line
144
+ # not sure it's a good idea. We'll see...
145
+ options[:branch] ||= current_branch
146
+ options[:remote] ||= remote_for(options[:branch])
147
+
148
+ # not sure of the option name. Well...
149
+ options[:verb] ||= 'add'
150
+
151
+ # not always a good idea to actually perform
152
+ # a checkout here but i let checkout method know that
153
+ checkout options[:branch]
154
+
155
+ # add || rm && commit
156
+ send options[:verb], file
157
+ commit "#{options[:verb]} #{file} (brought to you by #{self.class.name})"
158
+
159
+ # Run Forrest, run !
160
+ push options[:remote], options[:branch]
161
+ end
162
+
163
+ def commits(options={})
164
+ options[:branch] ||= current_branch
165
+
166
+ # yeah, reverse !
167
+ # Because it's only in the Bible that i understand
168
+ # why the first ones will be the last ones.
169
+ log.object(options[:branch]).map(&:sha).reverse rescue []
170
+ end
171
+
172
+ # So far, only two commits diff is implemented
173
+ def diff(first, second)
174
+ @layer.diff(first, second).to_s
175
+ end
176
+
177
+ def file_was_ever_known?(path)
178
+ log.path(path).any?
179
+ end
180
+
181
+ def local_branches
182
+ # sorry, what ?
183
+ branches.local.map &:name
184
+ end
185
+
186
+ def remote_branches
187
+ # sorry, what now ?
188
+ branches.remote.map &:name
189
+ end
190
+
191
+ def remote_for(branch)
192
+ # get out of here if i don't know you at all
193
+ return unless has_branch? branch
194
+
195
+ # ok you got me... I Perl'ed when i was younger
196
+ remote_branches.
197
+ map { |b| b.split '/' }. # returns a list of remote branch name elements
198
+ map { |elements| elements[-2] if elements.last == branch }. # list of nil or remote names
199
+ compact.first || remote_name
200
+ end
201
+
202
+ def remote_branch_for(branch)
203
+ # that one's even funnier
204
+ remote_branches.
205
+ map { |b| b.split '/' }. # returns a list of remote branch name elements
206
+ map { |elements| "#{elements[-2]}/#{elements[-1]}" if elements.last == branch }. # list of nil or remote branch names
207
+ compact.first
208
+ end
209
+
210
+ def remote_url(options={})
211
+ # yucky ? Maybe... But funny as well...
212
+ remotes.select { |r| options[:name] ? r.name == options[:name] : true }.map(&:url).first
213
+ end
214
+
215
+ def remote_name(options={})
216
+ # yucky ? Maybe... But funny as well...
217
+ remotes.select { |r| options[:url] ? r.url == options[:url] : true }.map(&:name).first
218
+ end
219
+
220
+ def reset_to_remote_head!
221
+ fetch
222
+ reset remote_branch_for(current_branch)
223
+ end
224
+
225
+ def get_status(type=nil)
226
+ types = (type && STATUS_LIST.include?(type)) ? [type] : STATUS_LIST
227
+
228
+ # Status as Hash of arrays, keys are statuses
229
+ types.inject({}) do |s, type|
230
+ # i.g layer.status.changed and keys only to get the filenames
231
+ s.merge! type => @layer.status.send(type).keys
232
+ end
233
+ end
234
+
235
+ def status(*args)
236
+ @status = get_status(*args)
237
+ end
238
+
239
+ def alterations(options={})
240
+ # which files have a status
241
+ # represented as in status above
242
+ if block_given?
243
+ status(options[:status]).select { |t, files| files.any? }.each do |type, files|
244
+ files.each do |file|
245
+ yield type, file
246
+ end
247
+ end
248
+ else
249
+ status(options[:status]).select { |t, files| files.any? }
250
+ end
251
+ end
252
+
253
+ def files_with_status(s)
254
+ # i like being specific
255
+ if block_given?
256
+ alterations(status: s) do |type, file|
257
+ yield file
258
+ end
259
+ else
260
+ alterations(status: s).values.flatten
261
+ end
262
+ end
263
+
264
+ def file_with_status(file, s)
265
+ files_with_status(s).include? file
266
+ end
267
+
268
+ def directory
269
+ # explanation here ?
270
+ # Really ?
271
+ @layer.dir.to_s
272
+ end
273
+
274
+ def full_path(path)
275
+ File.join directory, path
276
+ end
277
+
278
+ def files_paths
279
+ # for now, need only paths names
280
+ ls_files.keys
281
+ end
282
+
283
+ def initial_commit
284
+ # get the repo' initial commit
285
+ # the trinary with master is probably useless
286
+ branch = has_branch?('master') ? 'master' : local_branches.first
287
+ commits(branch: branch).first
288
+ end
289
+
290
+ def uncover!
291
+ @layer = nil
292
+ end
293
+
294
+ def wipe_file(path)
295
+ FileUtils.rm_f full_path(path)
296
+ end
297
+
298
+ def wipe_directory!
299
+ # not too clever way to do that
300
+ # i remove directories but try to avoid
301
+ # removing the repo'
302
+ files_paths.each do |f|
303
+ f = File.join directory, f
304
+ d = File.dirname f
305
+ FileUtils.rm_rf d == directory ? f : d
306
+ end
307
+ end
308
+
309
+ def local_remotes_only?
310
+ # well... yeah, i like to make people laugh
311
+ remotes.all? { |r| File.exists? r.url }
312
+ end
313
+
314
+ def can_cover?(path)
315
+ # is there any other way ?
316
+ # dunno for now
317
+ File.exists? File.join(path, '.git')
318
+ end
319
+
320
+ def has_updates?(branch=nil)
321
+ branch ||= current_branch
322
+ fetch
323
+
324
+ # do and return nothing unless... Well, you can read that
325
+ # all by yourself
326
+ return unless has_local_branch?(branch) && has_remote_branch?(branch)
327
+
328
+ # potential difference between local and remote
329
+ commits(branch: branch) != commits(branch: remote_branch_for(branch))
330
+ end
331
+
332
+ def has_branch?(name)
333
+ # whether local or remote
334
+ has_local_branch?(name) || has_remote_branch?(name)
335
+ end
336
+
337
+ def has_local_branch?(name)
338
+ local_branches.include? name
339
+ end
340
+
341
+ def has_remote_branch?(name)
342
+ remote_branches.map { |b| b.split('/').last }.include? name
343
+ end
344
+
345
+ # if i can, i'll think of a more teenily tinily specific
346
+ # used-only-once method later on
347
+ def remote_branch_only?(name)
348
+ has_remote_branch?(name) && !has_local_branch?(name)
349
+ end
350
+
351
+ # do we have a remote branch or do we have at least one commit
352
+ # on this repo'
353
+ def can_checkout?(name)
354
+ has_remote_branch?(name) || local_branches.any?
355
+ end
356
+
357
+ # Potato Potato method, i love it
358
+ def any_remote?
359
+ remotes.any?
360
+ end
361
+
362
+ def covers_anything?
363
+ !!@layer
364
+ end
365
+
366
+ def altered?
367
+ get_status != @status
368
+ end
369
+
370
+ private
371
+
372
+ def cook_layer
373
+ # i like tap. Did i say that already ?
374
+ tap do |trifle|
375
+ @layer = yield
376
+ @status = nil
377
+ @dressing.shift.call while @dressing.any?
378
+ end
379
+ end
380
+
381
+ end
382
+
383
+ end
data/lib/git-trifle.rb ADDED
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ # $LOAD_PATH upgrade
4
+ $:.unshift File.expand_path('../lib', __FILE__)
5
+
6
+ require 'git'
7
+
8
+ # git-trifle libs
9
+ require 'git/trifle'
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-trifle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - lacravate
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ruby-git-lacravate
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Trifle is a yet another layer around ruby git libs that intends to stick
47
+ as little as possible to the said underlying lib
48
+ email:
49
+ - lacravate@lacravate.fr
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/git-trifle.rb
55
+ - lib/git/trifle.rb
56
+ - lib/git/trifle/version.rb
57
+ homepage: https://github.com/lacravate/git-trifle
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project: ! '[none]'
77
+ rubygems_version: 1.8.24
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Trifle is a yet another layer around ruby git libs that intends to stick
81
+ as little as possible to the said underlying lib
82
+ test_files: []