minvee 0.0.1

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