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.
- data/lib/git/trifle/version.rb +5 -0
- data/lib/git/trifle.rb +383 -0
- data/lib/git-trifle.rb +9 -0
- metadata +82 -0
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
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: []
|