git-trifle 0.0.1

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