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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +23 -0
  3. data/lib/minvee/serverbox.rb +2038 -0
  4. data/lib/minvee.rb +885 -0
  5. 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
+