minvee 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.
- checksums.yaml +7 -0
- data/README.md +23 -0
- data/lib/minvee/serverbox.rb +2038 -0
- data/lib/minvee.rb +885 -0
- metadata +46 -0
@@ -0,0 +1,2038 @@
|
|
1
|
+
require 'minvee'
|
2
|
+
require 'timeout'
|
3
|
+
require 'json'
|
4
|
+
require 'json/file'
|
5
|
+
require 'open3'
|
6
|
+
require 'pathname'
|
7
|
+
require 'securerandom'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'date'
|
10
|
+
require 'xeme'
|
11
|
+
require 'nokogiri/plus'
|
12
|
+
# require 'exception/plus'
|
13
|
+
|
14
|
+
|
15
|
+
#===============================================================================
|
16
|
+
# Minvee::ServerBox
|
17
|
+
#
|
18
|
+
module Minvee::ServerBox
|
19
|
+
#---------------------------------------------------------------------------
|
20
|
+
# timeout
|
21
|
+
#
|
22
|
+
@@timeout = 60 * 100
|
23
|
+
|
24
|
+
def self.timeout(opts={})
|
25
|
+
return opts['timeout'] || @@timeout
|
26
|
+
end
|
27
|
+
#
|
28
|
+
# timeout
|
29
|
+
#---------------------------------------------------------------------------
|
30
|
+
|
31
|
+
|
32
|
+
#---------------------------------------------------------------------------
|
33
|
+
# default paths
|
34
|
+
#
|
35
|
+
@@paths = {}
|
36
|
+
@@paths['zip'] = '/usr/bin/zip'
|
37
|
+
@@paths['unzip'] = '/usr/bin/unzip'
|
38
|
+
@@paths['tar'] = '/bin/tar'
|
39
|
+
@@paths['diff'] = '/usr/bin/diff'
|
40
|
+
@@paths['xdelta3'] = '/usr/bin/xdelta3'
|
41
|
+
@@paths['tmp'] = '/tmp'
|
42
|
+
@@paths['file'] = '/usr/bin/file'
|
43
|
+
@@paths['md5sum'] = '/usr/bin/md5sum'
|
44
|
+
|
45
|
+
# accessor
|
46
|
+
def self.paths
|
47
|
+
return @@paths
|
48
|
+
end
|
49
|
+
|
50
|
+
# path to repo merge-requests dir
|
51
|
+
MERGE_REQUESTS_DIR = 'merge-requests'
|
52
|
+
|
53
|
+
#
|
54
|
+
# paths
|
55
|
+
#---------------------------------------------------------------------------
|
56
|
+
|
57
|
+
|
58
|
+
#---------------------------------------------------------------------------
|
59
|
+
# get_path
|
60
|
+
# TODO: Clean up the process for setting/getting paths
|
61
|
+
#
|
62
|
+
def self.get_path(path_id)
|
63
|
+
# $tm.hrm
|
64
|
+
|
65
|
+
# get possibilities
|
66
|
+
poss = *@@paths[path_id]
|
67
|
+
|
68
|
+
# TESTING
|
69
|
+
# $tm.show poss
|
70
|
+
|
71
|
+
# loop through possibilities until we find a real file
|
72
|
+
poss.each do |path|
|
73
|
+
if File.exist?(path)
|
74
|
+
return path
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# did not find a path
|
79
|
+
raise 'did not find a path for ' + path_id.to_s
|
80
|
+
end
|
81
|
+
#
|
82
|
+
# get_path
|
83
|
+
#---------------------------------------------------------------------------
|
84
|
+
|
85
|
+
|
86
|
+
#---------------------------------------------------------------------------
|
87
|
+
# path shortcuts
|
88
|
+
#
|
89
|
+
def self.zip
|
90
|
+
return self.get_path('zip')
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.unzip
|
94
|
+
return self.get_path('unzip')
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.tar
|
98
|
+
return self.get_path('tar')
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.diff
|
102
|
+
return self.get_path('diff')
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.tmp
|
106
|
+
return self.get_path('tmp')
|
107
|
+
end
|
108
|
+
#
|
109
|
+
# path shortcuts
|
110
|
+
#---------------------------------------------------------------------------
|
111
|
+
|
112
|
+
|
113
|
+
#---------------------------------------------------------------------------
|
114
|
+
# dirs_same
|
115
|
+
#
|
116
|
+
def self.dirs_same(a, b)
|
117
|
+
# $tm.hrm
|
118
|
+
|
119
|
+
# build cmd
|
120
|
+
cmd = [
|
121
|
+
Minvee::ServerBox.diff,
|
122
|
+
'--brief',
|
123
|
+
'--recursive',
|
124
|
+
a,
|
125
|
+
b,
|
126
|
+
]
|
127
|
+
|
128
|
+
# run command
|
129
|
+
results = Open3.capture3(*cmd)
|
130
|
+
|
131
|
+
# return
|
132
|
+
return results[0] == ''
|
133
|
+
end
|
134
|
+
#
|
135
|
+
# dirs_same
|
136
|
+
#---------------------------------------------------------------------------
|
137
|
+
|
138
|
+
|
139
|
+
#---------------------------------------------------------------------------
|
140
|
+
# build_delta
|
141
|
+
#
|
142
|
+
def self.build_delta(old_tgz, new_tgz, opts={})
|
143
|
+
# $tm.hrm
|
144
|
+
|
145
|
+
# get tmp path
|
146
|
+
zip_tgt = self.TempPath('ext'=>'delta')
|
147
|
+
|
148
|
+
# build command
|
149
|
+
# xdelta3 -q -s revision.2.tgz revision.2.tgz revision.1.delta
|
150
|
+
cmd = [
|
151
|
+
Minvee::ServerBox.get_path('xdelta3'),
|
152
|
+
'-q',
|
153
|
+
'-s',
|
154
|
+
new_tgz,
|
155
|
+
old_tgz,
|
156
|
+
zip_tgt,
|
157
|
+
]
|
158
|
+
|
159
|
+
# run command
|
160
|
+
Timeout::timeout(Minvee::ServerBox.timeout(opts)) do
|
161
|
+
system(*cmd)
|
162
|
+
end
|
163
|
+
|
164
|
+
# return
|
165
|
+
return zip_tgt
|
166
|
+
end
|
167
|
+
#
|
168
|
+
# build_delta
|
169
|
+
#---------------------------------------------------------------------------
|
170
|
+
|
171
|
+
|
172
|
+
#---------------------------------------------------------------------------
|
173
|
+
# TempPath
|
174
|
+
#
|
175
|
+
def self.TempPath(opts={})
|
176
|
+
return Minvee::Utils::TempPath.new(self.tmp, opts)
|
177
|
+
end
|
178
|
+
#
|
179
|
+
# TempPath
|
180
|
+
#---------------------------------------------------------------------------
|
181
|
+
|
182
|
+
|
183
|
+
#---------------------------------------------------------------------------
|
184
|
+
# TempDir
|
185
|
+
#
|
186
|
+
def self.TempDir(opts={})
|
187
|
+
return Minvee::Utils::TempDir.new(self.tmp, opts)
|
188
|
+
end
|
189
|
+
#
|
190
|
+
# TempDir
|
191
|
+
#---------------------------------------------------------------------------
|
192
|
+
|
193
|
+
|
194
|
+
#---------------------------------------------------------------------------
|
195
|
+
# extract_tgz
|
196
|
+
#
|
197
|
+
def self.extract_tgz(src_path)
|
198
|
+
# $tm.hrm
|
199
|
+
|
200
|
+
# tmp_dir
|
201
|
+
tmp_dir = Minvee::ServerBox.TempDir()
|
202
|
+
|
203
|
+
# extract into tmp dir
|
204
|
+
Dir.chdir(tmp_dir) do
|
205
|
+
#$tm.hr("extract #{src_path}") do
|
206
|
+
|
207
|
+
# run extract command
|
208
|
+
cmd = [Minvee::ServerBox.tar, '-xzf', src_path]
|
209
|
+
sysrv = Open3.capture3(*cmd)
|
210
|
+
|
211
|
+
# if error, output and exit
|
212
|
+
# TODO: Need to throw proper exception when there's an error.
|
213
|
+
if sysrv[1].match(/\S/mu)
|
214
|
+
raise sysrv[1]
|
215
|
+
exit
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# return
|
220
|
+
return tmp_dir
|
221
|
+
end
|
222
|
+
#
|
223
|
+
# extract_tgz
|
224
|
+
#---------------------------------------------------------------------------
|
225
|
+
|
226
|
+
|
227
|
+
#---------------------------------------------------------------------------
|
228
|
+
# tgz_from_delta
|
229
|
+
#
|
230
|
+
def self.tgz_from_delta(delta_path, tgz_path)
|
231
|
+
# $tm.hrm
|
232
|
+
|
233
|
+
# get temp path
|
234
|
+
temp_tgz = self.TempPath('ext'=>'tgz')
|
235
|
+
|
236
|
+
# build command
|
237
|
+
# xdelta3 -q -d -s revision.2.tgz revision.1.delta revision.1.restored.tgz
|
238
|
+
cmd = [
|
239
|
+
Minvee::ServerBox.get_path('xdelta3'),
|
240
|
+
'-q',
|
241
|
+
'-d',
|
242
|
+
'-s',
|
243
|
+
tgz_path,
|
244
|
+
delta_path,
|
245
|
+
temp_tgz
|
246
|
+
]
|
247
|
+
|
248
|
+
# run command
|
249
|
+
system(*cmd)
|
250
|
+
|
251
|
+
# return
|
252
|
+
return temp_tgz
|
253
|
+
end
|
254
|
+
#
|
255
|
+
# tgz_from_delta
|
256
|
+
#---------------------------------------------------------------------------
|
257
|
+
|
258
|
+
|
259
|
+
#---------------------------------------------------------------------------
|
260
|
+
# diff_compare
|
261
|
+
# Given two arrays of strings, uses Diff::LCS to produce a structure that
|
262
|
+
# holds both complete arrays, and indicates the addition, subtraction or
|
263
|
+
# staying the same for each element as the first array is transformed into
|
264
|
+
# the second array.
|
265
|
+
#
|
266
|
+
def self.diff_compare(myorg, mynew)
|
267
|
+
# $tm.hrm
|
268
|
+
|
269
|
+
# get diff
|
270
|
+
mydiff = Diff::LCS.sdiff(myorg, mynew)
|
271
|
+
|
272
|
+
# initialize changes array
|
273
|
+
changes_full = []
|
274
|
+
changes_compact = []
|
275
|
+
|
276
|
+
# build changes array
|
277
|
+
mydiff.each do |change|
|
278
|
+
# convenience
|
279
|
+
myold = change.old_element
|
280
|
+
mynew = change.new_element
|
281
|
+
|
282
|
+
# if they're the same
|
283
|
+
if myold == mynew
|
284
|
+
changes_full.push ( {'action' => 's', 'str' => myold} )
|
285
|
+
|
286
|
+
# else get old and new
|
287
|
+
else
|
288
|
+
# old element
|
289
|
+
if not myold.nil?
|
290
|
+
changes_full.push ( {'action' => '-', 'str' => myold} )
|
291
|
+
end
|
292
|
+
|
293
|
+
# new element
|
294
|
+
if not mynew.nil?
|
295
|
+
changes_full.push ( {'action' => '+', 'str' => mynew} )
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# initialize latest action
|
301
|
+
latest_action = ''
|
302
|
+
|
303
|
+
# compact
|
304
|
+
changes_full.each_with_index do |change, index|
|
305
|
+
# if same action as last iteration
|
306
|
+
if change['action'] == latest_action
|
307
|
+
changes_compact[-1]['strs'].push(change['str'])
|
308
|
+
|
309
|
+
# else different action
|
310
|
+
else
|
311
|
+
changes_compact.push( {'action'=>change['action'], 'strs'=>[change['str']] } )
|
312
|
+
latest_action = change['action']
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# return
|
317
|
+
return changes_compact
|
318
|
+
end
|
319
|
+
#
|
320
|
+
# diff_compare
|
321
|
+
#---------------------------------------------------------------------------
|
322
|
+
|
323
|
+
|
324
|
+
#---------------------------------------------------------------------------
|
325
|
+
# show_comparison
|
326
|
+
# A convienience method for displaying the contents of a comparison
|
327
|
+
# structure. Don't use this method in production work.
|
328
|
+
#
|
329
|
+
def self.show_comparison(mycomp)
|
330
|
+
# Testmin.hr(__method__.to_s)
|
331
|
+
|
332
|
+
mycomp.each do |change|
|
333
|
+
puts change['action'] + ' ' + change['strs'].show
|
334
|
+
end
|
335
|
+
end
|
336
|
+
#
|
337
|
+
# show_comparison
|
338
|
+
#---------------------------------------------------------------------------
|
339
|
+
|
340
|
+
|
341
|
+
#---------------------------------------------------------------------------
|
342
|
+
# tree_description
|
343
|
+
#
|
344
|
+
def self.tree_description(root)
|
345
|
+
# $tm.hrm
|
346
|
+
|
347
|
+
# initialize return hash
|
348
|
+
rv = {}
|
349
|
+
|
350
|
+
# traverse tree
|
351
|
+
content = rv['content'] = {}
|
352
|
+
content['name'] = 'content'
|
353
|
+
content['uuid'] = SecureRandom.uuid
|
354
|
+
self.dir_description root, content
|
355
|
+
|
356
|
+
# remove minvee.json
|
357
|
+
rv['content']['files'].delete('minvee.json')
|
358
|
+
|
359
|
+
# return
|
360
|
+
return rv
|
361
|
+
end
|
362
|
+
#
|
363
|
+
# tree_description
|
364
|
+
#---------------------------------------------------------------------------
|
365
|
+
|
366
|
+
|
367
|
+
#---------------------------------------------------------------------------
|
368
|
+
# dir_description
|
369
|
+
#
|
370
|
+
def self.dir_description(dir_name, dir)
|
371
|
+
# $tm.hrm
|
372
|
+
|
373
|
+
# initialize return hash
|
374
|
+
dir['type'] = 'dir'
|
375
|
+
dir['files'] = {}
|
376
|
+
|
377
|
+
# go to dir
|
378
|
+
Dir.chdir(dir_name) do
|
379
|
+
# get list of files
|
380
|
+
entries = Dir.entries('.')
|
381
|
+
entries = entries.grep_v(/\A\.+\z/)
|
382
|
+
|
383
|
+
# loop through files
|
384
|
+
entries.each do |file_name|
|
385
|
+
# KLUDGE
|
386
|
+
# I really hate unconditionally untainting any variable.
|
387
|
+
# However, there doesn't seem to be any good way to test if a
|
388
|
+
# given file name references a directory. Given that we just got
|
389
|
+
# this file name from the current directory, it should be ok to
|
390
|
+
# untaint the string.
|
391
|
+
# file_name.untaint
|
392
|
+
|
393
|
+
# file meta
|
394
|
+
file = dir['files'][file_name] = {}
|
395
|
+
|
396
|
+
# add file name
|
397
|
+
# KLUDGE: It feels awkward adding the file name to the meta hash
|
398
|
+
# given that the file name is already the in the dir['files'].
|
399
|
+
# However, it was getting awkward passing around thew file name
|
400
|
+
# and other file meta info as separate pieces of information.
|
401
|
+
file['name'] = file_name
|
402
|
+
|
403
|
+
# uuid
|
404
|
+
file['uuid'] = SecureRandom.uuid
|
405
|
+
|
406
|
+
# symlink
|
407
|
+
if File.symlink?(file_name)
|
408
|
+
self.symlink_description file_name, file
|
409
|
+
|
410
|
+
# directory
|
411
|
+
elsif File.directory?(file_name)
|
412
|
+
self.dir_description file_name, file
|
413
|
+
|
414
|
+
# file
|
415
|
+
else
|
416
|
+
self.file_description file_name, file
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
#
|
422
|
+
# dir_description
|
423
|
+
#---------------------------------------------------------------------------
|
424
|
+
|
425
|
+
|
426
|
+
#---------------------------------------------------------------------------
|
427
|
+
# symlink_description
|
428
|
+
#
|
429
|
+
def self.symlink_description(file_name, file)
|
430
|
+
file['type'] = 'symlink'
|
431
|
+
end
|
432
|
+
#
|
433
|
+
# symlink_description
|
434
|
+
#---------------------------------------------------------------------------
|
435
|
+
|
436
|
+
|
437
|
+
#---------------------------------------------------------------------------
|
438
|
+
# file_description
|
439
|
+
#
|
440
|
+
def self.file_description(file_name, file)
|
441
|
+
# $tm.hrm
|
442
|
+
|
443
|
+
# return hash
|
444
|
+
file['type'] = 'file'
|
445
|
+
|
446
|
+
# file size
|
447
|
+
file['size'] = File.size(file_name)
|
448
|
+
|
449
|
+
# mime type
|
450
|
+
file['mime'] = file_mime(file_name)
|
451
|
+
|
452
|
+
# md5sum
|
453
|
+
file['md5sum'] = file_md5sum(file_name)
|
454
|
+
end
|
455
|
+
#
|
456
|
+
# file_description
|
457
|
+
#---------------------------------------------------------------------------
|
458
|
+
|
459
|
+
|
460
|
+
#---------------------------------------------------------------------------
|
461
|
+
# file_mime
|
462
|
+
#
|
463
|
+
def self.file_mime(file)
|
464
|
+
# build file command
|
465
|
+
cmd = [
|
466
|
+
Minvee::ServerBox.get_path('file'),
|
467
|
+
'--brief',
|
468
|
+
'--mime-type',
|
469
|
+
file,
|
470
|
+
];
|
471
|
+
|
472
|
+
# run command
|
473
|
+
results = Open3.capture3(*cmd)
|
474
|
+
results[0].sub!(/\s+\z/imu, '')
|
475
|
+
|
476
|
+
# return
|
477
|
+
return results[0]
|
478
|
+
end
|
479
|
+
#
|
480
|
+
# file_mime
|
481
|
+
#---------------------------------------------------------------------------
|
482
|
+
|
483
|
+
|
484
|
+
#---------------------------------------------------------------------------
|
485
|
+
# file_md5sum
|
486
|
+
#
|
487
|
+
def self.file_md5sum(file)
|
488
|
+
# build file command
|
489
|
+
cmd = [
|
490
|
+
Minvee::ServerBox.get_path('md5sum'),
|
491
|
+
file,
|
492
|
+
];
|
493
|
+
|
494
|
+
# run command
|
495
|
+
results = Open3.capture3(*cmd)
|
496
|
+
results[0].sub!(/\s.*\z/imu, '')
|
497
|
+
|
498
|
+
# return
|
499
|
+
return results[0]
|
500
|
+
end
|
501
|
+
#
|
502
|
+
# file_mime
|
503
|
+
#---------------------------------------------------------------------------
|
504
|
+
|
505
|
+
|
506
|
+
#---------------------------------------------------------------------------
|
507
|
+
# run
|
508
|
+
#
|
509
|
+
def self.run(cmd)
|
510
|
+
# $tm.hrm
|
511
|
+
# $tm.show cmd
|
512
|
+
|
513
|
+
# run command
|
514
|
+
results = Open3.capture3(*cmd)
|
515
|
+
|
516
|
+
# if error, output and exit
|
517
|
+
# TODO: Need to throw proper exception when there's an error.
|
518
|
+
if results[1].match(/\S/mu)
|
519
|
+
raise results[1]
|
520
|
+
end
|
521
|
+
|
522
|
+
# return
|
523
|
+
return true
|
524
|
+
end
|
525
|
+
#
|
526
|
+
# run
|
527
|
+
#---------------------------------------------------------------------------
|
528
|
+
end
|
529
|
+
#
|
530
|
+
# Minvee::ServerBox
|
531
|
+
#===============================================================================
|
532
|
+
|
533
|
+
|
534
|
+
#===============================================================================
|
535
|
+
# Minvee::Project
|
536
|
+
#
|
537
|
+
class Minvee::Project
|
538
|
+
attr_reader :root
|
539
|
+
|
540
|
+
#---------------------------------------------------------------------------
|
541
|
+
# initialize
|
542
|
+
#
|
543
|
+
def initialize(p_root)
|
544
|
+
# set @root
|
545
|
+
@root = p_root.dup
|
546
|
+
|
547
|
+
# normalize @root
|
548
|
+
@root.sub!(/\/+\z/imu, '')
|
549
|
+
|
550
|
+
# check that project exists
|
551
|
+
if not Dir.exist?(@root)
|
552
|
+
raise 'project-dir-found-not-exist'
|
553
|
+
end
|
554
|
+
|
555
|
+
# TESTING
|
556
|
+
# if @root.match(/static\-3/mu)
|
557
|
+
# $tm.hr do
|
558
|
+
# $tm.puts 'calling ensure_revisions_dir()'
|
559
|
+
# $tm.puts '@root: ' + @root
|
560
|
+
# end
|
561
|
+
# end
|
562
|
+
|
563
|
+
# ensure revisions directory
|
564
|
+
ensure_revisions_dir()
|
565
|
+
end
|
566
|
+
#
|
567
|
+
# initialize
|
568
|
+
#---------------------------------------------------------------------------
|
569
|
+
|
570
|
+
|
571
|
+
#---------------------------------------------------------------------------
|
572
|
+
# name
|
573
|
+
#
|
574
|
+
def name
|
575
|
+
# $tm.hrm
|
576
|
+
rv = root
|
577
|
+
rv = rv.sub(/\A.*\//mu, '')
|
578
|
+
return rv
|
579
|
+
end
|
580
|
+
#
|
581
|
+
# name
|
582
|
+
#---------------------------------------------------------------------------
|
583
|
+
|
584
|
+
|
585
|
+
#---------------------------------------------------------------------------
|
586
|
+
# overrides
|
587
|
+
#
|
588
|
+
def revision_class
|
589
|
+
return Minvee::Project::Revision
|
590
|
+
end
|
591
|
+
#
|
592
|
+
# overrides
|
593
|
+
#---------------------------------------------------------------------------
|
594
|
+
|
595
|
+
|
596
|
+
#---------------------------------------------------------------------------
|
597
|
+
# merge_request_names
|
598
|
+
#
|
599
|
+
def merge_request_names
|
600
|
+
# $tm.hrm
|
601
|
+
rv = []
|
602
|
+
|
603
|
+
# if no merge requests dir, return empty
|
604
|
+
if not File.exist?(self.merge_requests_dir)
|
605
|
+
return rv
|
606
|
+
end
|
607
|
+
|
608
|
+
# loop through files in merge requests dir
|
609
|
+
# Yes, I like to nest if's instead of having one long conditional.
|
610
|
+
Dir.chdir(self.merge_requests_dir) do
|
611
|
+
Dir.entries('./').each do |entry|
|
612
|
+
if entry.match(/\A[0-9\-\~\:]+\z/mu)
|
613
|
+
# untaint entry directory name
|
614
|
+
entry_ut = entry.dup
|
615
|
+
|
616
|
+
# Having to remove untainting.
|
617
|
+
# entry_ut.untaint
|
618
|
+
|
619
|
+
# check if this really appears to be a merge request
|
620
|
+
if File.directory?(entry_ut)
|
621
|
+
if File.exist?("#{entry_ut}/merge-request.json")
|
622
|
+
if File.exist?("#{entry_ut}/uploaded.tgz") or File.exist?("#{entry_ut}/uploaded.zip")
|
623
|
+
rv.push entry_ut
|
624
|
+
end
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
# return
|
632
|
+
return rv.sort
|
633
|
+
end
|
634
|
+
#
|
635
|
+
# merge_request_names
|
636
|
+
#---------------------------------------------------------------------------
|
637
|
+
|
638
|
+
|
639
|
+
#---------------------------------------------------------------------------
|
640
|
+
# merge_requests
|
641
|
+
#
|
642
|
+
def merge_requests
|
643
|
+
# $tm.hrm
|
644
|
+
rv = []
|
645
|
+
|
646
|
+
# loop through names
|
647
|
+
merge_request_names.each do |name|
|
648
|
+
rv.push Minvee::Project::MergeRequest.new(self, name)
|
649
|
+
end
|
650
|
+
|
651
|
+
# return
|
652
|
+
return rv
|
653
|
+
end
|
654
|
+
#
|
655
|
+
# merge_requests
|
656
|
+
#---------------------------------------------------------------------------
|
657
|
+
|
658
|
+
|
659
|
+
#---------------------------------------------------------------------------
|
660
|
+
# process_merge_requests
|
661
|
+
#
|
662
|
+
def process_merge_requests(opts={})
|
663
|
+
# $tm.hrm
|
664
|
+
verbose = opts['verbose']
|
665
|
+
id_output = false
|
666
|
+
|
667
|
+
# run inside lock block
|
668
|
+
self.revision_dir_lock('x') do
|
669
|
+
# loop through merge requests
|
670
|
+
self.merge_requests.each do |mr|
|
671
|
+
if mr.approved
|
672
|
+
send_opts = opts.clone
|
673
|
+
xeme = Xeme.new
|
674
|
+
send_opts['xeme'] = xeme
|
675
|
+
mr.merge send_opts
|
676
|
+
|
677
|
+
# verbosify
|
678
|
+
if verbose and (not xeme.misc['already_up_to_date'])
|
679
|
+
if not id_output
|
680
|
+
puts self.name
|
681
|
+
id_output = true
|
682
|
+
end
|
683
|
+
|
684
|
+
puts "\t" + xeme.misc['revision'].to_s
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
#
|
691
|
+
# process_merge_requests
|
692
|
+
#---------------------------------------------------------------------------
|
693
|
+
|
694
|
+
|
695
|
+
#---------------------------------------------------------------------------
|
696
|
+
# working_copy_same_as_latest
|
697
|
+
#
|
698
|
+
def working_copy_same_as_latest(wc_path)
|
699
|
+
# $tm.hrm
|
700
|
+
|
701
|
+
# if no revisions, return false
|
702
|
+
if not self.any_revisions?
|
703
|
+
return false
|
704
|
+
end
|
705
|
+
|
706
|
+
# put inside block to close rev_path
|
707
|
+
begin
|
708
|
+
# get latest revision
|
709
|
+
rev_path = self.latest_revision_extracted()
|
710
|
+
|
711
|
+
# TESTING
|
712
|
+
# puts 'latest: ' + rev_path + '/content'
|
713
|
+
# puts 'new: ' + wc_path
|
714
|
+
|
715
|
+
# compare the two directories
|
716
|
+
return Minvee::ServerBox.dirs_same(rev_path + '/content', wc_path)
|
717
|
+
ensure
|
718
|
+
if rev_path.respond_to?('close')
|
719
|
+
rev_path.close
|
720
|
+
end
|
721
|
+
end
|
722
|
+
end
|
723
|
+
#
|
724
|
+
# working_copy_same_as_latest
|
725
|
+
#---------------------------------------------------------------------------
|
726
|
+
|
727
|
+
|
728
|
+
#---------------------------------------------------------------------------
|
729
|
+
# latest_revision_extracted
|
730
|
+
#
|
731
|
+
def latest_revision_extracted
|
732
|
+
# $tm.hrm
|
733
|
+
|
734
|
+
# if no revisions
|
735
|
+
if not self.any_revisions?
|
736
|
+
return nil
|
737
|
+
end
|
738
|
+
|
739
|
+
# get latest revision number
|
740
|
+
tgz_path = self.revision_path(self.latest_revision_number)
|
741
|
+
|
742
|
+
# return
|
743
|
+
return Minvee::ServerBox.extract_tgz(tgz_path)
|
744
|
+
end
|
745
|
+
#
|
746
|
+
# latest_revision_extracted
|
747
|
+
#---------------------------------------------------------------------------
|
748
|
+
|
749
|
+
|
750
|
+
#---------------------------------------------------------------------------
|
751
|
+
# paths
|
752
|
+
#
|
753
|
+
def revisions_dir
|
754
|
+
return @root + '/revisions'
|
755
|
+
end
|
756
|
+
|
757
|
+
def merge_requests_dir
|
758
|
+
return @root + '/' + Minvee::ServerBox::MERGE_REQUESTS_DIR
|
759
|
+
end
|
760
|
+
|
761
|
+
def revision_dir(number)
|
762
|
+
return self.revisions_dir + '/' + number.to_s
|
763
|
+
end
|
764
|
+
|
765
|
+
def revision_path(number, opts={})
|
766
|
+
# $tm.hrm
|
767
|
+
|
768
|
+
# number must be defined
|
769
|
+
if number.nil?
|
770
|
+
raise 'revision-path-nil'
|
771
|
+
end
|
772
|
+
|
773
|
+
# default opts
|
774
|
+
opts = {'ext'=>'tgz'}.merge(opts)
|
775
|
+
|
776
|
+
# return dir
|
777
|
+
return revision_dir(number) + '/revision.' + opts['ext']
|
778
|
+
end
|
779
|
+
|
780
|
+
def delta_path(number)
|
781
|
+
return revision_dir(number) + '/revision.delta'
|
782
|
+
end
|
783
|
+
#
|
784
|
+
# paths
|
785
|
+
#---------------------------------------------------------------------------
|
786
|
+
|
787
|
+
|
788
|
+
#---------------------------------------------------------------------------
|
789
|
+
# ensure directories
|
790
|
+
#
|
791
|
+
def ensure_revisions_dir
|
792
|
+
FileUtils::mkdir_p self.revisions_dir
|
793
|
+
end
|
794
|
+
|
795
|
+
def ensure_merge_requests_dir
|
796
|
+
FileUtils::mkdir_p self.merge_requests_dir
|
797
|
+
end
|
798
|
+
#
|
799
|
+
# ensure directories
|
800
|
+
#---------------------------------------------------------------------------
|
801
|
+
|
802
|
+
|
803
|
+
#---------------------------------------------------------------------------
|
804
|
+
# revision_dir_lock
|
805
|
+
#
|
806
|
+
def revision_dir_lock(p_lock_type, opts={})
|
807
|
+
# $tm.hrm
|
808
|
+
|
809
|
+
# ensure existence of revisions dir
|
810
|
+
self.ensure_revisions_dir()
|
811
|
+
|
812
|
+
# get lock type
|
813
|
+
if p_lock_type == 'x'
|
814
|
+
lock_type = File::LOCK_EX
|
815
|
+
else
|
816
|
+
lock_type = File::LOCK_SH
|
817
|
+
end
|
818
|
+
|
819
|
+
# get timeout
|
820
|
+
timeout = Minvee::ServerBox.timeout(opts)
|
821
|
+
|
822
|
+
# set timeout, get lock on revisions directory
|
823
|
+
Timeout::timeout(timeout) do
|
824
|
+
File.open(self.revisions_dir + '/lock.txt', 'a') do |lock|
|
825
|
+
lock.flock(lock_type)
|
826
|
+
|
827
|
+
# yield, but ensure unlock
|
828
|
+
begin
|
829
|
+
yield()
|
830
|
+
ensure
|
831
|
+
lock.flock(File::LOCK_UN)
|
832
|
+
end
|
833
|
+
end
|
834
|
+
end
|
835
|
+
end
|
836
|
+
#
|
837
|
+
# revision_dir_lock
|
838
|
+
#---------------------------------------------------------------------------
|
839
|
+
|
840
|
+
|
841
|
+
#---------------------------------------------------------------------------
|
842
|
+
# revision_tmp_to_zip
|
843
|
+
#
|
844
|
+
def revision_tmp_to_zip(revision_tmp, opts={})
|
845
|
+
# $tm.hrm
|
846
|
+
|
847
|
+
# default opts
|
848
|
+
opts = {'save'=>true}.merge(opts)
|
849
|
+
|
850
|
+
# new revision number
|
851
|
+
new_number = self.next_revision_number
|
852
|
+
|
853
|
+
# zip to revisions dir
|
854
|
+
zip_tgt = self.zip_revision(revision_tmp)
|
855
|
+
|
856
|
+
# we don't need the revision_tmp dir anymore
|
857
|
+
revision_tmp.close
|
858
|
+
|
859
|
+
# move file, except if indicated not to
|
860
|
+
if opts['save']
|
861
|
+
FileUtils.mkdir_p self.revision_dir(new_number)
|
862
|
+
FileUtils.mv zip_tgt, self.revision_path(new_number)
|
863
|
+
end
|
864
|
+
|
865
|
+
# return next_revision
|
866
|
+
return new_number
|
867
|
+
end
|
868
|
+
#
|
869
|
+
# revision_tmp_to_zip
|
870
|
+
#---------------------------------------------------------------------------
|
871
|
+
|
872
|
+
|
873
|
+
#---------------------------------------------------------------------------
|
874
|
+
# revision_files
|
875
|
+
#
|
876
|
+
def revision_files()
|
877
|
+
Dir[self.revisions_dir + '/revision.*'].each do |file|
|
878
|
+
file.match(/revision\.(\d+)\.(?:delta|tgz)/imu) do |m|
|
879
|
+
if c = m.captures[0]
|
880
|
+
# untaint c
|
881
|
+
# Yes, we're doing an extra pattern check here.
|
882
|
+
if c.match(/\A\d+\z/imu)
|
883
|
+
# Having to remove untainting.
|
884
|
+
# c.untaint
|
885
|
+
else
|
886
|
+
raise 'invalid-revision-number-pattern'
|
887
|
+
end
|
888
|
+
|
889
|
+
# yield
|
890
|
+
yield c.to_i
|
891
|
+
end
|
892
|
+
end
|
893
|
+
end
|
894
|
+
end
|
895
|
+
#
|
896
|
+
# revision_files
|
897
|
+
#---------------------------------------------------------------------------
|
898
|
+
|
899
|
+
|
900
|
+
#---------------------------------------------------------------------------
|
901
|
+
# latest_revision_number
|
902
|
+
#
|
903
|
+
def latest_revision_number()
|
904
|
+
# $tm.hrm
|
905
|
+
return self.revision_numbers[0]
|
906
|
+
end
|
907
|
+
#
|
908
|
+
# latest_revision_number
|
909
|
+
#---------------------------------------------------------------------------
|
910
|
+
|
911
|
+
|
912
|
+
#---------------------------------------------------------------------------
|
913
|
+
# next_revision_number
|
914
|
+
#
|
915
|
+
def next_revision_number
|
916
|
+
if lrn = self.latest_revision_number
|
917
|
+
return lrn + 1
|
918
|
+
else
|
919
|
+
return 1
|
920
|
+
end
|
921
|
+
end
|
922
|
+
#
|
923
|
+
# next_revision_number
|
924
|
+
#---------------------------------------------------------------------------
|
925
|
+
|
926
|
+
|
927
|
+
#---------------------------------------------------------------------------
|
928
|
+
# revision_numbers
|
929
|
+
#
|
930
|
+
def revision_numbers(opts={})
|
931
|
+
# $tm.hrm
|
932
|
+
rv = []
|
933
|
+
|
934
|
+
# early exit: no revisions dir
|
935
|
+
if not File.exist?(self.revisions_dir)
|
936
|
+
return rv
|
937
|
+
end
|
938
|
+
|
939
|
+
# loop through files
|
940
|
+
Dir.entries(self.revisions_dir).each do |file|
|
941
|
+
if file.match(/\A\d+\z/)
|
942
|
+
if opts['any']
|
943
|
+
return true
|
944
|
+
end
|
945
|
+
|
946
|
+
num = file.to_i
|
947
|
+
rv.push num
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
# if we get this far with the any option, there are no revisions
|
952
|
+
if opts['any']
|
953
|
+
return false
|
954
|
+
end
|
955
|
+
|
956
|
+
# return sorted and reversed
|
957
|
+
return rv.sort.reverse
|
958
|
+
end
|
959
|
+
#
|
960
|
+
# revision_numbers
|
961
|
+
#---------------------------------------------------------------------------
|
962
|
+
|
963
|
+
|
964
|
+
#---------------------------------------------------------------------------
|
965
|
+
# revision
|
966
|
+
#
|
967
|
+
def revision(num)
|
968
|
+
# return Minvee::Project::Revision.new(self, num)
|
969
|
+
return revision_class.new(self, num)
|
970
|
+
end
|
971
|
+
#
|
972
|
+
# revision
|
973
|
+
#---------------------------------------------------------------------------
|
974
|
+
|
975
|
+
|
976
|
+
#---------------------------------------------------------------------------
|
977
|
+
# revisions
|
978
|
+
#
|
979
|
+
def revisions
|
980
|
+
rv = nil
|
981
|
+
|
982
|
+
self.revision_numbers.each do |num|
|
983
|
+
rev = self.revision(num)
|
984
|
+
|
985
|
+
if block_given?
|
986
|
+
yield rev
|
987
|
+
else
|
988
|
+
rv ||= []
|
989
|
+
rv.push rev
|
990
|
+
end
|
991
|
+
end
|
992
|
+
|
993
|
+
if block_given?
|
994
|
+
return nil
|
995
|
+
else
|
996
|
+
return rv || []
|
997
|
+
end
|
998
|
+
end
|
999
|
+
#
|
1000
|
+
# revisions
|
1001
|
+
#---------------------------------------------------------------------------
|
1002
|
+
|
1003
|
+
|
1004
|
+
#---------------------------------------------------------------------------
|
1005
|
+
# any_revisions?
|
1006
|
+
#
|
1007
|
+
def any_revisions?()
|
1008
|
+
# $tm.hrm
|
1009
|
+
return self.revision_numbers('any'=>true)
|
1010
|
+
end
|
1011
|
+
#
|
1012
|
+
# any_revisions?
|
1013
|
+
#---------------------------------------------------------------------------
|
1014
|
+
|
1015
|
+
|
1016
|
+
#---------------------------------------------------------------------------
|
1017
|
+
# latest_revision
|
1018
|
+
#
|
1019
|
+
def latest_revision
|
1020
|
+
# $tm.hrm
|
1021
|
+
if lrn = self.latest_revision_number()
|
1022
|
+
return self.revision(lrn)
|
1023
|
+
else
|
1024
|
+
return nil
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
#
|
1028
|
+
# latest_revision
|
1029
|
+
#---------------------------------------------------------------------------
|
1030
|
+
|
1031
|
+
|
1032
|
+
#---------------------------------------------------------------------------
|
1033
|
+
# next_needed_delta
|
1034
|
+
#
|
1035
|
+
def next_needed_delta
|
1036
|
+
# $tm.hrm
|
1037
|
+
|
1038
|
+
# get numbers
|
1039
|
+
numbers = self.revision_numbers
|
1040
|
+
|
1041
|
+
# remove latest revision number, which does not ever need a delta
|
1042
|
+
numbers.shift
|
1043
|
+
|
1044
|
+
# if less than one, nothing to do
|
1045
|
+
if numbers.length < 1
|
1046
|
+
return nil
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
# loop through numbers until we find a revision that doesn't have a delta
|
1050
|
+
numbers.each do |num|
|
1051
|
+
# build path
|
1052
|
+
path = self.revisions_dir + '/' + num.to_s + '/revision.delta'
|
1053
|
+
|
1054
|
+
# if delta file doesn't exist, return the number
|
1055
|
+
if not File.exist?(path)
|
1056
|
+
return num
|
1057
|
+
end
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
# return nil
|
1061
|
+
return nil
|
1062
|
+
end
|
1063
|
+
#
|
1064
|
+
# next_needed_delta
|
1065
|
+
#---------------------------------------------------------------------------
|
1066
|
+
|
1067
|
+
|
1068
|
+
#---------------------------------------------------------------------------
|
1069
|
+
# extract_revision
|
1070
|
+
#
|
1071
|
+
# def extract_revision(revision_number)
|
1072
|
+
# # $tm.hrm
|
1073
|
+
#
|
1074
|
+
# # get path to revision file
|
1075
|
+
# tgz_path = rev = self.revision(revision_number).tgz_path
|
1076
|
+
#
|
1077
|
+
# # path to extracted dir
|
1078
|
+
# tmp_dir = Minvee::ServerBox.extract_tgz(tgz_path)
|
1079
|
+
#
|
1080
|
+
# # yield or return
|
1081
|
+
# if block_given?
|
1082
|
+
# begin
|
1083
|
+
# yield tmp_dir
|
1084
|
+
# ensure
|
1085
|
+
# tmp_dir.close
|
1086
|
+
# end
|
1087
|
+
# else
|
1088
|
+
# return tmp_dir
|
1089
|
+
# end
|
1090
|
+
# end
|
1091
|
+
#
|
1092
|
+
# extract_revision
|
1093
|
+
#---------------------------------------------------------------------------
|
1094
|
+
|
1095
|
+
|
1096
|
+
#---------------------------------------------------------------------------
|
1097
|
+
# tgz_to_delta
|
1098
|
+
#
|
1099
|
+
def tgz_to_delta(old_num, new_num, opts={})
|
1100
|
+
# $tm.hrm
|
1101
|
+
|
1102
|
+
# verbosify
|
1103
|
+
if opts['verbose']
|
1104
|
+
puts " #{old_num} -> #{new_num}"
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
# paths
|
1108
|
+
old_tgz_path = self.revision_path(old_num)
|
1109
|
+
|
1110
|
+
# check that old revision exists
|
1111
|
+
if not File.exist?(old_tgz_path)
|
1112
|
+
raise 'do-not-have-old-revision: ' + old_tgz_path
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
# tgz for newer revision
|
1116
|
+
new_tgz_path = self.revision(new_num).tgz
|
1117
|
+
|
1118
|
+
# build delta
|
1119
|
+
tmp_delta = Minvee::ServerBox.build_delta(old_tgz_path, new_tgz_path)
|
1120
|
+
|
1121
|
+
# move into revisions dir
|
1122
|
+
FileUtils.mv tmp_delta, self.delta_path(old_num)
|
1123
|
+
|
1124
|
+
# delete old_tgz_path, which is now no longer needed
|
1125
|
+
File.delete old_tgz_path
|
1126
|
+
|
1127
|
+
# always close temp path
|
1128
|
+
ensure
|
1129
|
+
if new_tgz_path.respond_to?('close')
|
1130
|
+
new_tgz_path.close
|
1131
|
+
end
|
1132
|
+
end
|
1133
|
+
#
|
1134
|
+
# tgz_to_delta
|
1135
|
+
#---------------------------------------------------------------------------
|
1136
|
+
|
1137
|
+
|
1138
|
+
#---------------------------------------------------------------------------
|
1139
|
+
# build_needed_deltas
|
1140
|
+
#
|
1141
|
+
def build_needed_deltas(opts={})
|
1142
|
+
# $tm.hrm
|
1143
|
+
verbose = opts['verbose']
|
1144
|
+
verbosified = false
|
1145
|
+
|
1146
|
+
# while there are more deltas
|
1147
|
+
while needed = self.next_needed_delta
|
1148
|
+
if verbose and (not verbosified) and (not opts['title_verbosified'])
|
1149
|
+
puts @root
|
1150
|
+
verbosified = true
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
# create delta
|
1154
|
+
self.tgz_to_delta needed, needed + 1, opts
|
1155
|
+
end
|
1156
|
+
end
|
1157
|
+
#
|
1158
|
+
# build_needed_deltas
|
1159
|
+
#---------------------------------------------------------------------------
|
1160
|
+
|
1161
|
+
|
1162
|
+
#---------------------------------------------------------------------------
|
1163
|
+
# latest_tgz
|
1164
|
+
#
|
1165
|
+
def latest_tgz
|
1166
|
+
# $tm.hrm
|
1167
|
+
|
1168
|
+
# loop through revisions
|
1169
|
+
self.revisions do |rev|
|
1170
|
+
tgz_path = rev.root + '/revision.tgz'
|
1171
|
+
|
1172
|
+
if File.exist?(tgz_path)
|
1173
|
+
return tgz_path
|
1174
|
+
end
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
# don'thave latest
|
1178
|
+
return nil
|
1179
|
+
end
|
1180
|
+
#
|
1181
|
+
# latest_tgz
|
1182
|
+
#---------------------------------------------------------------------------
|
1183
|
+
|
1184
|
+
|
1185
|
+
#---------------------------------------------------------------------------
|
1186
|
+
# delete_tmp_files
|
1187
|
+
#
|
1188
|
+
def delete_tmp_files
|
1189
|
+
# $tm.hrm
|
1190
|
+
|
1191
|
+
# loop through tmp files
|
1192
|
+
Dir[self.revisions_dir + '/*.tmp'].each do |file|
|
1193
|
+
File.delete(file)
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
#
|
1197
|
+
# delete_tmp_files
|
1198
|
+
#---------------------------------------------------------------------------
|
1199
|
+
|
1200
|
+
|
1201
|
+
#---------------------------------------------------------------------------
|
1202
|
+
# clean_revisions_dir
|
1203
|
+
#
|
1204
|
+
def clean_revisions_dir(opts={})
|
1205
|
+
# $tm.hrm
|
1206
|
+
|
1207
|
+
# run inside lock block
|
1208
|
+
self.revision_dir_lock('x', opts) do
|
1209
|
+
# build deltas as needed
|
1210
|
+
self.build_needed_deltas()
|
1211
|
+
|
1212
|
+
# clean out tmp files
|
1213
|
+
self.delete_tmp_files()
|
1214
|
+
|
1215
|
+
# revision descriptions
|
1216
|
+
self.build_revision_descriptions()
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
#
|
1220
|
+
# clean_revisions_dir
|
1221
|
+
#---------------------------------------------------------------------------
|
1222
|
+
|
1223
|
+
|
1224
|
+
#---------------------------------------------------------------------------
|
1225
|
+
# build_revision_metas
|
1226
|
+
#
|
1227
|
+
def build_revision_metas(opts={})
|
1228
|
+
# $tm.hrm
|
1229
|
+
opts['verbose'] and puts @root
|
1230
|
+
|
1231
|
+
# loop through revisions
|
1232
|
+
self.revisions.reverse.each do |rev|
|
1233
|
+
rev.build_meta opts
|
1234
|
+
end
|
1235
|
+
end
|
1236
|
+
#
|
1237
|
+
# build_revision_metas
|
1238
|
+
#---------------------------------------------------------------------------
|
1239
|
+
|
1240
|
+
|
1241
|
+
#---------------------------------------------------------------------------
|
1242
|
+
# meta_file_path
|
1243
|
+
#
|
1244
|
+
def meta_file_path(rev_num)
|
1245
|
+
return "#{self.revisions_dir}/#{rev_num}/revision.json"
|
1246
|
+
end
|
1247
|
+
#
|
1248
|
+
# meta_file_path
|
1249
|
+
#---------------------------------------------------------------------------
|
1250
|
+
|
1251
|
+
|
1252
|
+
#---------------------------------------------------------------------------
|
1253
|
+
# build_revision_meta
|
1254
|
+
#
|
1255
|
+
def build_revision_meta(rev_dir)
|
1256
|
+
# $tm.hrm
|
1257
|
+
# puts 'rev_dir: ' + rev_dir
|
1258
|
+
|
1259
|
+
# go into revision dir
|
1260
|
+
Dir.chdir(rev_dir) do
|
1261
|
+
rev = {}
|
1262
|
+
mrj_path = './revision/merge-request.json'
|
1263
|
+
|
1264
|
+
# slurp in save.json
|
1265
|
+
# KLUDGE: For now, silently rescuing if there's an error
|
1266
|
+
# reading or parsing the save.json file.
|
1267
|
+
if File.exist?(mrj_path)
|
1268
|
+
begin
|
1269
|
+
mrj = JSON.parse(File.read mrj_path)
|
1270
|
+
rev['merge-request'] = mrj
|
1271
|
+
rescue
|
1272
|
+
end
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
# get tree description
|
1276
|
+
rev['tree'] = Minvee::ServerBox.tree_description('./content')
|
1277
|
+
|
1278
|
+
# return
|
1279
|
+
return rev
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
#
|
1283
|
+
# build_revision_meta
|
1284
|
+
#---------------------------------------------------------------------------
|
1285
|
+
|
1286
|
+
|
1287
|
+
#---------------------------------------------------------------------------
|
1288
|
+
# summary
|
1289
|
+
#
|
1290
|
+
def summary()
|
1291
|
+
# $tm.hrm
|
1292
|
+
rv = {}
|
1293
|
+
|
1294
|
+
# basic info
|
1295
|
+
rv['root'] = self.root
|
1296
|
+
|
1297
|
+
# revisions hash
|
1298
|
+
revs_arr = rv['revisions'] = []
|
1299
|
+
|
1300
|
+
# loop through revisions
|
1301
|
+
self.revisions() do |rev|
|
1302
|
+
rev_hsh = {}
|
1303
|
+
revs_arr.push rev_hsh
|
1304
|
+
meta = rev.meta
|
1305
|
+
|
1306
|
+
# basic info
|
1307
|
+
rev_hsh['num'] = rev.num
|
1308
|
+
rev_hsh['saved'] = meta['saved']
|
1309
|
+
rev_hsh['merge-request'] = meta['merge-request']
|
1310
|
+
|
1311
|
+
# additional info from subclasses
|
1312
|
+
summary_rev_additional rev, rev_hsh
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
# return
|
1316
|
+
return rv
|
1317
|
+
end
|
1318
|
+
#
|
1319
|
+
# summary
|
1320
|
+
#---------------------------------------------------------------------------
|
1321
|
+
|
1322
|
+
|
1323
|
+
#---------------------------------------------------------------------------
|
1324
|
+
# summary_rev_additional
|
1325
|
+
#
|
1326
|
+
def summary_rev_additional(rev, rev_hsh)
|
1327
|
+
end
|
1328
|
+
#
|
1329
|
+
# summary_additional
|
1330
|
+
#---------------------------------------------------------------------------
|
1331
|
+
end
|
1332
|
+
#
|
1333
|
+
# Minvee::Project
|
1334
|
+
#===============================================================================
|
1335
|
+
|
1336
|
+
|
1337
|
+
#===============================================================================
|
1338
|
+
# Minvee::Project::MergeRequest
|
1339
|
+
#
|
1340
|
+
class Minvee::Project::MergeRequest
|
1341
|
+
attr_reader :project
|
1342
|
+
attr_reader :name
|
1343
|
+
|
1344
|
+
#---------------------------------------------------------------------------
|
1345
|
+
# initialize
|
1346
|
+
#
|
1347
|
+
def initialize(p_project, p_name)
|
1348
|
+
# $tm.hrm
|
1349
|
+
@project = p_project
|
1350
|
+
@name = p_name
|
1351
|
+
end
|
1352
|
+
#
|
1353
|
+
# initialize
|
1354
|
+
#---------------------------------------------------------------------------
|
1355
|
+
|
1356
|
+
|
1357
|
+
#---------------------------------------------------------------------------
|
1358
|
+
# root
|
1359
|
+
#
|
1360
|
+
def root
|
1361
|
+
return @project.root + '/merge-requests/' + @name
|
1362
|
+
end
|
1363
|
+
#
|
1364
|
+
# root
|
1365
|
+
#---------------------------------------------------------------------------
|
1366
|
+
|
1367
|
+
|
1368
|
+
#---------------------------------------------------------------------------
|
1369
|
+
# approved
|
1370
|
+
#
|
1371
|
+
def approved
|
1372
|
+
if status = self.meta['status']
|
1373
|
+
return status['approved']
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
return false
|
1377
|
+
end
|
1378
|
+
#
|
1379
|
+
# approved
|
1380
|
+
#---------------------------------------------------------------------------
|
1381
|
+
|
1382
|
+
|
1383
|
+
#---------------------------------------------------------------------------
|
1384
|
+
# meta
|
1385
|
+
#
|
1386
|
+
def meta
|
1387
|
+
# $tm.hrm
|
1388
|
+
return JSON.parse(File.read self.root + '/merge-request.json')
|
1389
|
+
end
|
1390
|
+
#
|
1391
|
+
# meta
|
1392
|
+
#---------------------------------------------------------------------------
|
1393
|
+
|
1394
|
+
|
1395
|
+
#---------------------------------------------------------------------------
|
1396
|
+
# uploaded_path
|
1397
|
+
#
|
1398
|
+
def uploaded_path
|
1399
|
+
# $tm.hrm
|
1400
|
+
|
1401
|
+
# loop through extension possibilities
|
1402
|
+
['tgz', 'zip'].each do |ext|
|
1403
|
+
rv = self.root + '/uploaded.' + ext
|
1404
|
+
|
1405
|
+
if File.exist?(rv)
|
1406
|
+
return rv
|
1407
|
+
end
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
# error: could not find uploaded file
|
1411
|
+
raise 'merge-request-no-uploaded-file'
|
1412
|
+
end
|
1413
|
+
#
|
1414
|
+
# uploaded_path
|
1415
|
+
#---------------------------------------------------------------------------
|
1416
|
+
|
1417
|
+
|
1418
|
+
#---------------------------------------------------------------------------
|
1419
|
+
# build_save_revision
|
1420
|
+
#
|
1421
|
+
def build_save_revision()
|
1422
|
+
# $tm.hrm
|
1423
|
+
|
1424
|
+
# go into content directory
|
1425
|
+
Dir.chdir('./content') do |content_dir|
|
1426
|
+
# run command
|
1427
|
+
cmd = [Minvee::ServerBox.tar, '-xzf', self.uploaded_path]
|
1428
|
+
sysrv = Open3.capture3(*cmd)
|
1429
|
+
|
1430
|
+
# if error, output and exit
|
1431
|
+
if sysrv[1].match(/\S/mu)
|
1432
|
+
raise 'build-save-revision-unzip-error: ' + sysrv[1]
|
1433
|
+
end
|
1434
|
+
|
1435
|
+
# delete minvee.json, which is only relevent to working copies
|
1436
|
+
if File.exist?('./minvee.json')
|
1437
|
+
File.delete('./minvee.json')
|
1438
|
+
end
|
1439
|
+
end
|
1440
|
+
end
|
1441
|
+
#
|
1442
|
+
# build_save_revision
|
1443
|
+
#---------------------------------------------------------------------------
|
1444
|
+
|
1445
|
+
|
1446
|
+
#---------------------------------------------------------------------------
|
1447
|
+
# merge
|
1448
|
+
#
|
1449
|
+
def merge(opts={})
|
1450
|
+
# $tm.hrm
|
1451
|
+
|
1452
|
+
# create a temp directory for processing this revision
|
1453
|
+
revision_tmp = Minvee::ServerBox.TempDir()
|
1454
|
+
|
1455
|
+
# change into revision_tmp
|
1456
|
+
Dir.chdir(revision_tmp) do
|
1457
|
+
# create revision and content dir
|
1458
|
+
FileUtils::mkdir_p './revision'
|
1459
|
+
FileUtils::mkdir_p './content'
|
1460
|
+
|
1461
|
+
# build revision dir
|
1462
|
+
self.build_save_revision()
|
1463
|
+
|
1464
|
+
# check if working copy is identical to latest revision
|
1465
|
+
if @project.working_copy_same_as_latest(revision_tmp + '/content')
|
1466
|
+
# note results
|
1467
|
+
if xeme = opts['xeme']
|
1468
|
+
xeme.misc['already_up_to_date'] = true
|
1469
|
+
xeme.misc['revision'] = @project.latest_revision_number
|
1470
|
+
end
|
1471
|
+
else
|
1472
|
+
# path to merge-request.json
|
1473
|
+
mrj_path = self.root + '/merge-request.json'
|
1474
|
+
|
1475
|
+
# copy merge-request.json
|
1476
|
+
if File.exist?(mrj_path)
|
1477
|
+
FileUtils.cp mrj_path, './revision/merge-request.json'
|
1478
|
+
end
|
1479
|
+
|
1480
|
+
# build meta file
|
1481
|
+
File.write 'revision/revision.json', JSON.pretty_generate(@project.build_revision_meta(revision_tmp))
|
1482
|
+
|
1483
|
+
# zip content into revision dir
|
1484
|
+
self.zip_content()
|
1485
|
+
|
1486
|
+
# move into final destination
|
1487
|
+
self.commit revision_tmp
|
1488
|
+
|
1489
|
+
# note results
|
1490
|
+
if xeme = opts['xeme']
|
1491
|
+
xeme.misc['already_up_to_date'] = false
|
1492
|
+
xeme.misc['revision'] = @project.latest_revision_number
|
1493
|
+
end
|
1494
|
+
end
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
# delete own directory
|
1498
|
+
self.delete_self()
|
1499
|
+
|
1500
|
+
# note success
|
1501
|
+
# if xeme = opts['xeme']
|
1502
|
+
# xeme.succeed
|
1503
|
+
# end
|
1504
|
+
|
1505
|
+
# return success
|
1506
|
+
return true
|
1507
|
+
end
|
1508
|
+
#
|
1509
|
+
# merge
|
1510
|
+
#---------------------------------------------------------------------------
|
1511
|
+
|
1512
|
+
|
1513
|
+
#---------------------------------------------------------------------------
|
1514
|
+
# delete_self
|
1515
|
+
#
|
1516
|
+
def delete_self
|
1517
|
+
if File.exist?(self.root)
|
1518
|
+
FileUtils.rm_rf self.root
|
1519
|
+
end
|
1520
|
+
end
|
1521
|
+
#
|
1522
|
+
# delete_self
|
1523
|
+
#---------------------------------------------------------------------------
|
1524
|
+
|
1525
|
+
|
1526
|
+
#---------------------------------------------------------------------------
|
1527
|
+
# commit
|
1528
|
+
#
|
1529
|
+
def commit(revision_tmp)
|
1530
|
+
# $tm.hrm
|
1531
|
+
@project.ensure_revisions_dir
|
1532
|
+
src = revision_tmp + '/revision'
|
1533
|
+
tgt = @project.root + '/revisions/' + @project.next_revision_number.to_s
|
1534
|
+
FileUtils.mv src, tgt
|
1535
|
+
return true
|
1536
|
+
end
|
1537
|
+
#
|
1538
|
+
# commit
|
1539
|
+
#---------------------------------------------------------------------------
|
1540
|
+
|
1541
|
+
|
1542
|
+
#---------------------------------------------------------------------------
|
1543
|
+
# zip_content
|
1544
|
+
#
|
1545
|
+
def zip_content()
|
1546
|
+
# $tm.hr(__method__.to_s)
|
1547
|
+
|
1548
|
+
# build command
|
1549
|
+
cmd = [
|
1550
|
+
Minvee::ServerBox.tar,
|
1551
|
+
'-czf',
|
1552
|
+
'./revision/revision.tgz',
|
1553
|
+
'./content'
|
1554
|
+
]
|
1555
|
+
|
1556
|
+
# TODO: Run with open3
|
1557
|
+
# run system command
|
1558
|
+
system(*cmd)
|
1559
|
+
end
|
1560
|
+
#
|
1561
|
+
# zip_content
|
1562
|
+
#---------------------------------------------------------------------------
|
1563
|
+
end
|
1564
|
+
#
|
1565
|
+
# Minvee::Project::MergeRequest
|
1566
|
+
#===============================================================================
|
1567
|
+
|
1568
|
+
|
1569
|
+
#===============================================================================
|
1570
|
+
# Minvee::Project::Revision
|
1571
|
+
#
|
1572
|
+
class Minvee::Project::Revision
|
1573
|
+
attr_reader :project
|
1574
|
+
attr_reader :num
|
1575
|
+
|
1576
|
+
#---------------------------------------------------------------------------
|
1577
|
+
# initialize
|
1578
|
+
#
|
1579
|
+
def initialize(p_project, p_num)
|
1580
|
+
@meta_file_cache = nil
|
1581
|
+
@project = p_project
|
1582
|
+
@num = p_num
|
1583
|
+
end
|
1584
|
+
#
|
1585
|
+
# initialize
|
1586
|
+
#---------------------------------------------------------------------------
|
1587
|
+
|
1588
|
+
|
1589
|
+
#---------------------------------------------------------------------------
|
1590
|
+
# paths
|
1591
|
+
#
|
1592
|
+
def root
|
1593
|
+
return @project.root + '/revisions/' + @num.to_s
|
1594
|
+
end
|
1595
|
+
|
1596
|
+
def tgz_path
|
1597
|
+
return root + '/revision.tgz'
|
1598
|
+
end
|
1599
|
+
|
1600
|
+
def meta_path
|
1601
|
+
return root + '/revision.json'
|
1602
|
+
end
|
1603
|
+
#
|
1604
|
+
# paths
|
1605
|
+
#---------------------------------------------------------------------------
|
1606
|
+
|
1607
|
+
|
1608
|
+
#---------------------------------------------------------------------------
|
1609
|
+
# meta
|
1610
|
+
#
|
1611
|
+
def meta()
|
1612
|
+
return JSON.parse( ::File.read(meta_path()) )
|
1613
|
+
end
|
1614
|
+
#
|
1615
|
+
# meta
|
1616
|
+
#---------------------------------------------------------------------------
|
1617
|
+
|
1618
|
+
|
1619
|
+
#---------------------------------------------------------------------------
|
1620
|
+
# meta_file
|
1621
|
+
#
|
1622
|
+
def meta_file()
|
1623
|
+
# create cache if necessary
|
1624
|
+
if @meta_file_cache.nil?
|
1625
|
+
@meta_file_cache = JSON::File.new(meta_path())
|
1626
|
+
@meta_file_cache.locked = true
|
1627
|
+
end
|
1628
|
+
|
1629
|
+
# return cache
|
1630
|
+
return @meta_file_cache
|
1631
|
+
end
|
1632
|
+
#
|
1633
|
+
# meta_file
|
1634
|
+
#---------------------------------------------------------------------------
|
1635
|
+
|
1636
|
+
|
1637
|
+
#---------------------------------------------------------------------------
|
1638
|
+
# timestamp
|
1639
|
+
#
|
1640
|
+
def timestamp
|
1641
|
+
begin
|
1642
|
+
ts = meta['merge-request']['status']['timestamp']
|
1643
|
+
return DateTime.parse(ts)
|
1644
|
+
rescue
|
1645
|
+
return nil
|
1646
|
+
end
|
1647
|
+
end
|
1648
|
+
#
|
1649
|
+
# timestamp
|
1650
|
+
#---------------------------------------------------------------------------
|
1651
|
+
|
1652
|
+
|
1653
|
+
#---------------------------------------------------------------------------
|
1654
|
+
# org_merge_request
|
1655
|
+
# NOTE: This routine looks for a file called "save.json" was used in an
|
1656
|
+
# early version of Minvee. That file is only sought out if
|
1657
|
+
# merge-request.json isn't present and if a tmp_dir was sent as an option.
|
1658
|
+
#
|
1659
|
+
def org_merge_request(opts={})
|
1660
|
+
# $tm.hrm
|
1661
|
+
mr_path = self.root + '/merge-request.json'
|
1662
|
+
|
1663
|
+
# if revision.json exists
|
1664
|
+
if File.exist?(mr_path)
|
1665
|
+
return JSON.parse(File.read(mr_path))
|
1666
|
+
elsif opts['tmp_dir']
|
1667
|
+
save_path = opts['tmp_dir'] + '/save.json'
|
1668
|
+
|
1669
|
+
if File.exist?(save_path)
|
1670
|
+
return JSON.parse(File.read(save_path))
|
1671
|
+
end
|
1672
|
+
end
|
1673
|
+
|
1674
|
+
# nothing found
|
1675
|
+
return nil
|
1676
|
+
end
|
1677
|
+
#
|
1678
|
+
# org_merge_request
|
1679
|
+
#---------------------------------------------------------------------------
|
1680
|
+
|
1681
|
+
|
1682
|
+
#---------------------------------------------------------------------------
|
1683
|
+
# build_meta
|
1684
|
+
# KLUDGE: This routine is redundant to the routine that builds the meta
|
1685
|
+
# information when the merge-request is committed.
|
1686
|
+
#
|
1687
|
+
def build_meta(opts={})
|
1688
|
+
# if meta file doesn't exist, or force
|
1689
|
+
if (not File.exist?(self.meta_path())) or opts['force']
|
1690
|
+
opts['verbose'] and puts self.num
|
1691
|
+
|
1692
|
+
# process in temp dir
|
1693
|
+
@project.extract_revision(self.num) do |tmp_dir|
|
1694
|
+
Dir.chdir(tmp_dir) do
|
1695
|
+
rev = JSON::File.new(self.meta_path, 'create_ok'=>true)
|
1696
|
+
rev.clear
|
1697
|
+
|
1698
|
+
# merge request settings
|
1699
|
+
if mr_settings = self.org_merge_request('tmp_dir'=>tmp_dir)
|
1700
|
+
rev['merge-request'] = mr_settings
|
1701
|
+
end
|
1702
|
+
|
1703
|
+
# tree
|
1704
|
+
rev['tree'] = Minvee::ServerBox.tree_description("#{tmp_dir}/content")
|
1705
|
+
|
1706
|
+
# save
|
1707
|
+
rev.save
|
1708
|
+
end
|
1709
|
+
end
|
1710
|
+
end
|
1711
|
+
end
|
1712
|
+
#
|
1713
|
+
# build_meta
|
1714
|
+
#---------------------------------------------------------------------------
|
1715
|
+
|
1716
|
+
|
1717
|
+
#---------------------------------------------------------------------------
|
1718
|
+
# next_revision
|
1719
|
+
#
|
1720
|
+
def next_revision()
|
1721
|
+
# $tm.hrm
|
1722
|
+
return @project.revision(@num + 1)
|
1723
|
+
end
|
1724
|
+
#
|
1725
|
+
# next_revision
|
1726
|
+
#---------------------------------------------------------------------------
|
1727
|
+
|
1728
|
+
|
1729
|
+
#---------------------------------------------------------------------------
|
1730
|
+
# tgz
|
1731
|
+
# KLUDGE: This routine gets a bit spaghettish.
|
1732
|
+
#
|
1733
|
+
def tgz()
|
1734
|
+
# $tm.hrm
|
1735
|
+
existing_path = self.root + '/revision.tgz'
|
1736
|
+
|
1737
|
+
# TESTING
|
1738
|
+
# puts 'existing_path: ' + existing_path
|
1739
|
+
|
1740
|
+
# path to existing tgz if it exists, just use it
|
1741
|
+
if ::File.exist?(existing_path)
|
1742
|
+
# yield or return
|
1743
|
+
if block_given?
|
1744
|
+
yield existing_path
|
1745
|
+
return nil
|
1746
|
+
else
|
1747
|
+
return existing_path
|
1748
|
+
end
|
1749
|
+
|
1750
|
+
# else build a path from deltas
|
1751
|
+
else
|
1752
|
+
# old_path
|
1753
|
+
old_path = self.next_revision.tgz()
|
1754
|
+
|
1755
|
+
# build tgz from delta
|
1756
|
+
rv = Minvee::ServerBox.tgz_from_delta(self.root + '/revision.delta', old_path)
|
1757
|
+
|
1758
|
+
# close old_path
|
1759
|
+
if old_path.respond_to?('close')
|
1760
|
+
old_path.close
|
1761
|
+
end
|
1762
|
+
|
1763
|
+
# yield or return
|
1764
|
+
if block_given?
|
1765
|
+
begin
|
1766
|
+
yield rv
|
1767
|
+
ensure
|
1768
|
+
rv.close
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
return nil
|
1772
|
+
else
|
1773
|
+
return rv
|
1774
|
+
end
|
1775
|
+
end
|
1776
|
+
end
|
1777
|
+
#
|
1778
|
+
# tgz
|
1779
|
+
#---------------------------------------------------------------------------
|
1780
|
+
|
1781
|
+
|
1782
|
+
#---------------------------------------------------------------------------
|
1783
|
+
# extract
|
1784
|
+
#
|
1785
|
+
def extract
|
1786
|
+
# $tm.hrm
|
1787
|
+
|
1788
|
+
# source and extracted dir
|
1789
|
+
src = self.tgz()
|
1790
|
+
dir = Minvee::ServerBox.extract_tgz(src)
|
1791
|
+
|
1792
|
+
# close source if necessary
|
1793
|
+
if src.respond_to?('close')
|
1794
|
+
src.close
|
1795
|
+
end
|
1796
|
+
|
1797
|
+
# yield or return
|
1798
|
+
if block_given?
|
1799
|
+
begin
|
1800
|
+
yield dir
|
1801
|
+
ensure
|
1802
|
+
dir.close
|
1803
|
+
end
|
1804
|
+
else
|
1805
|
+
return dir
|
1806
|
+
end
|
1807
|
+
end
|
1808
|
+
#
|
1809
|
+
# extract
|
1810
|
+
#---------------------------------------------------------------------------
|
1811
|
+
|
1812
|
+
|
1813
|
+
#---------------------------------------------------------------------------
|
1814
|
+
# extract_file
|
1815
|
+
#
|
1816
|
+
def extract_file(path)
|
1817
|
+
# $tm.hrm
|
1818
|
+
|
1819
|
+
# get tgz
|
1820
|
+
tgz_path = self.tgz()
|
1821
|
+
|
1822
|
+
# temp dir to extract file into
|
1823
|
+
tmp_dir = Minvee::ServerBox.TempDir()
|
1824
|
+
|
1825
|
+
# normalize path
|
1826
|
+
path = path.sub(/\A\//mu, '')
|
1827
|
+
path = 'content/' + path
|
1828
|
+
|
1829
|
+
# build command
|
1830
|
+
# tar --extract --one-top-level=out --file=revision.tgz
|
1831
|
+
cmd = []
|
1832
|
+
cmd.push Minvee::ServerBox.paths['tar']
|
1833
|
+
cmd.push '--extract'
|
1834
|
+
cmd.push '--file'
|
1835
|
+
cmd.push tgz_path
|
1836
|
+
cmd.push "./#{path}"
|
1837
|
+
|
1838
|
+
# go to tmp dir and run command
|
1839
|
+
Dir.chdir(tmp_dir) do
|
1840
|
+
Minvee::ServerBox.run cmd
|
1841
|
+
end
|
1842
|
+
|
1843
|
+
# path to extracted file
|
1844
|
+
extracted_path = tmp_dir + '/' + path
|
1845
|
+
|
1846
|
+
# tmp path
|
1847
|
+
tmp_path = Minvee::ServerBox.TempPath()
|
1848
|
+
|
1849
|
+
# move file to tmp path
|
1850
|
+
FileUtils.mv extracted_path, tmp_path
|
1851
|
+
|
1852
|
+
# close tmp dir
|
1853
|
+
tmp_dir.close()
|
1854
|
+
|
1855
|
+
# return
|
1856
|
+
return tmp_path
|
1857
|
+
|
1858
|
+
# always close tgz_path
|
1859
|
+
ensure
|
1860
|
+
if tgz_path.respond_to?('close')
|
1861
|
+
tgz_path.close
|
1862
|
+
end
|
1863
|
+
end
|
1864
|
+
#
|
1865
|
+
# extract_file
|
1866
|
+
#---------------------------------------------------------------------------
|
1867
|
+
|
1868
|
+
|
1869
|
+
#---------------------------------------------------------------------------
|
1870
|
+
# file
|
1871
|
+
#
|
1872
|
+
def file(uuid)
|
1873
|
+
return Minvee::Project::Revision::File.new(self, uuid)
|
1874
|
+
end
|
1875
|
+
#
|
1876
|
+
# file
|
1877
|
+
#---------------------------------------------------------------------------
|
1878
|
+
|
1879
|
+
|
1880
|
+
#---------------------------------------------------------------------------
|
1881
|
+
# summary
|
1882
|
+
#
|
1883
|
+
def summary
|
1884
|
+
end
|
1885
|
+
#
|
1886
|
+
# summary
|
1887
|
+
#---------------------------------------------------------------------------
|
1888
|
+
end
|
1889
|
+
#
|
1890
|
+
# Minvee::Project::Revision
|
1891
|
+
#===============================================================================
|
1892
|
+
|
1893
|
+
|
1894
|
+
#===============================================================================
|
1895
|
+
# Minvee::Project::Revision::File
|
1896
|
+
# TODO: This ancestor routines in this class could be a lot more efficient.
|
1897
|
+
#
|
1898
|
+
class Minvee::Project::Revision::File
|
1899
|
+
attr_reader :uuid
|
1900
|
+
attr_reader :rev
|
1901
|
+
attr_reader :meta
|
1902
|
+
attr_reader :ancestor_metas
|
1903
|
+
|
1904
|
+
#---------------------------------------------------------------------------
|
1905
|
+
# initialize
|
1906
|
+
#
|
1907
|
+
def initialize(p_rev, p_uuid)
|
1908
|
+
@rev = p_rev
|
1909
|
+
@uuid = p_uuid
|
1910
|
+
@meta = nil
|
1911
|
+
@ancestor_metas = nil
|
1912
|
+
|
1913
|
+
# find file record
|
1914
|
+
# search_file_for_uuid @rev.meta['tree']['content']
|
1915
|
+
search_dir_for_uuid @rev.meta['tree']['content']
|
1916
|
+
|
1917
|
+
# error if no meta
|
1918
|
+
# if not @meta
|
1919
|
+
# # TESTING
|
1920
|
+
# puts 'do-not-find-uuid-for-file: ' + @uuid
|
1921
|
+
# $tm.devexit
|
1922
|
+
#
|
1923
|
+
# raise 'do-not-find-uuid-for-file: ' + @uuid
|
1924
|
+
# end
|
1925
|
+
|
1926
|
+
# freeze ancestor uuids
|
1927
|
+
# @ancestor_uuids.freeze
|
1928
|
+
end
|
1929
|
+
#
|
1930
|
+
# initialize
|
1931
|
+
#---------------------------------------------------------------------------
|
1932
|
+
|
1933
|
+
|
1934
|
+
#---------------------------------------------------------------------------
|
1935
|
+
# simple accessors
|
1936
|
+
#
|
1937
|
+
def name
|
1938
|
+
return @meta['name']
|
1939
|
+
end
|
1940
|
+
#
|
1941
|
+
# simple accessors
|
1942
|
+
#---------------------------------------------------------------------------
|
1943
|
+
|
1944
|
+
|
1945
|
+
#---------------------------------------------------------------------------
|
1946
|
+
# path
|
1947
|
+
#
|
1948
|
+
def path
|
1949
|
+
# $tm.hrm
|
1950
|
+
names = []
|
1951
|
+
|
1952
|
+
@ancestor_metas.each do |anc|
|
1953
|
+
names.push anc['name']
|
1954
|
+
end
|
1955
|
+
|
1956
|
+
return '/' + names.join('/')
|
1957
|
+
end
|
1958
|
+
#
|
1959
|
+
# path
|
1960
|
+
#---------------------------------------------------------------------------
|
1961
|
+
|
1962
|
+
|
1963
|
+
#---------------------------------------------------------------------------
|
1964
|
+
# ancestors
|
1965
|
+
#
|
1966
|
+
def ancestors
|
1967
|
+
if v_parent = self.parent
|
1968
|
+
rv = v_parent.ancestors
|
1969
|
+
rv.push v_parent
|
1970
|
+
return rv
|
1971
|
+
else
|
1972
|
+
return []
|
1973
|
+
end
|
1974
|
+
end
|
1975
|
+
#
|
1976
|
+
# ancestors
|
1977
|
+
#---------------------------------------------------------------------------
|
1978
|
+
|
1979
|
+
|
1980
|
+
#---------------------------------------------------------------------------
|
1981
|
+
# parent
|
1982
|
+
#
|
1983
|
+
def parent
|
1984
|
+
# $tm.hrm
|
1985
|
+
|
1986
|
+
# if there are any ancestors, return the first
|
1987
|
+
if @ancestor_metas.any?
|
1988
|
+
return @rev.file(@ancestor_metas[-1]['uuid'])
|
1989
|
+
|
1990
|
+
# else return nil
|
1991
|
+
else
|
1992
|
+
return nil
|
1993
|
+
end
|
1994
|
+
end
|
1995
|
+
#
|
1996
|
+
# parent
|
1997
|
+
#---------------------------------------------------------------------------
|
1998
|
+
|
1999
|
+
|
2000
|
+
# private
|
2001
|
+
private
|
2002
|
+
|
2003
|
+
#---------------------------------------------------------------------------
|
2004
|
+
# search_dir_for_uuid
|
2005
|
+
#
|
2006
|
+
def search_dir_for_uuid(dir, p_ancestors=[])
|
2007
|
+
# indent = ' ' * p_ancestors.length
|
2008
|
+
# puts indent + dir['name']
|
2009
|
+
|
2010
|
+
# loop through files
|
2011
|
+
dir['files'].values.each do |file|
|
2012
|
+
# puts indent + '-' + file['name'] + ': ' + file['uuid']
|
2013
|
+
|
2014
|
+
if file['uuid'] == @uuid
|
2015
|
+
@meta = file
|
2016
|
+
@ancestor_metas = p_ancestors
|
2017
|
+
return true
|
2018
|
+
end
|
2019
|
+
|
2020
|
+
if file['type'] == 'dir'
|
2021
|
+
if search_dir_for_uuid(file, p_ancestors + [file])
|
2022
|
+
return true
|
2023
|
+
end
|
2024
|
+
end
|
2025
|
+
end
|
2026
|
+
|
2027
|
+
# didn't find the file
|
2028
|
+
return false
|
2029
|
+
end
|
2030
|
+
#
|
2031
|
+
# search_dir_for_uuid
|
2032
|
+
#---------------------------------------------------------------------------
|
2033
|
+
end
|
2034
|
+
#
|
2035
|
+
# Minvee::Project::Revision::File
|
2036
|
+
#===============================================================================
|
2037
|
+
|
2038
|
+
|