Custom Coverage


/Users/mattweppler/developer/projects/custom-source-control/lib/custom_source_control.rb

Coverage: 74.24%

Total Lines Relevant Lines Irrelevant Lines Covered Lines Missed Lines
224 132 92 98 34
1
#!/usr/bin/env ruby
2

        
3
require 'fileutils'
4
require 'openssl'
5

        
6
class CustomSourceControl
7

        
8
  ['manifest', 'metadata'].each do |method|
9
    define_method("hash_and_copy_#{method}") do
10
      file = File.join('.esc', "__#{method}__")
11
      hash = hash_for_file(file)
12
      FileUtils.cp(file, File.join('.esc', hash))
13
      hash
14
    end
15
  end
16

        
17
  ['__manifest__', '__metadata__', 'HEAD'].each do |ctl_file|
18
    define_method("#{ctl_file.downcase.gsub('_', '')}_exists?") do
19
      File.exists? File.join('.esc', ctl_file)
20
    end
21
  end
22

        
23
  def checkout(snapshot = nil)
24
    manifest_hash = ''
25
    File.open(File.join('.esc', snapshot), 'r') do |file|
26
      file.readlines.each do |entry|
27
        manifest_hash = $1 if entry =~ /Snapshot Manifest: (\w{40})/
28
      end
29
    end
30

        
31
    entries = []
32
    File.open(File.join('.esc', manifest_hash), 'r') do |file|
33
      file.readlines.each do |entry|
34
        entry =~ /(.*?) => (.*?) \(new\)\n?/
35
        entries << { :hash => $1, :pathname => $2 } if $1
36
      end
37
    end
38
    entries.each do |entry|
39
      copy_entry_to_working_directory entry
40
    end
41
  end
42

        
43
  def copy_entry_to_repository(manifest_entry)
44
    FileUtils.cp(manifest_entry[:pathname], File.join('.esc', manifest_entry[:hash]), { :preserve => true })
45
  end
46

        
47
  def copy_entry_to_working_directory(manifest_entry)
48
    FileUtils.cp(File.join('.esc', manifest_entry[:hash]), manifest_entry[:pathname], { :preserve => true })
49
  end
50

        
51
  def copy_manifest_files_to_repository
52
    entries = []
53
    File.open(File.join('.esc', '__manifest__'), 'r') do |file|
54
      file.readlines.each do |entry|
55
        entry =~ /(.*?) => (.*?) \(new\)\n?/
56
        entries << { :hash => $1, :pathname => $2 } if $1
57
      end
58
    end
59
    entries.each do |entry|
60
      copy_entry_to_repository entry
61
    end
62
  end
63

        
64
  def cwd_files
65
    all_files_wildcard = File.join '**', '*'
66
    Dir.glob(all_files_wildcard)
67
  end
68

        
69
  def cwd_hashes
70
    cwd_files.inject({}) { |hash, file| hash[file] = hash_for_file(file); hash }
71
  end
72
  alias_method :refactor_cwd_hashes, :cwd_hashes
73

        
74
  def initial_cwd_hashes
75
    sha1 = OpenSSL::Digest::SHA1.new
76
    hashes = {}
77
    cwd_files.each do |file|
78
      hashes[file] = sha1.hexdigest(File.read(file))
79
      sha1.reset
80
    end
81
    hashes
82
  end
83

        
84
  def deltas
85
    new, existing = [], []
86
    cwd_hashes.each do |key, value|
87
      if repository_file_list.include? key
88
        existing << key
89
      else
90
        new << key
91
      end
92
    end
93
    { :new => new, :existing => existing }
94
  end
95

        
96
  def hash_for_file(file = nil)
97
    sha1 = OpenSSL::Digest::SHA1.new
98
    sha1.hexdigest(File.read(file))
99
  end
100

        
101
  def head_contents
102
    File.open('.esc/HEAD', 'r') { |f| f.read }
103
  end
104

        
105
  def initialize_repository
106
    Dir.mkdir '.esc'
107
    File.new(File.join('.esc', 'HEAD'), 'w')
108
  end
109

        
110
  def manifest_contents(manifest = nil)
111
    manifest ||= '__manifest__'
112
    File.open(".esc/#{manifest}", 'r') { |f| f.read }
113
  end
114

        
115
  def repository_exists?
116
    Dir.exists? '.esc'
117
  end
118

        
119
  def repository_file_exists?(filename = nil)
120
    File.exists? File.join('.esc', filename)
121
  end
122

        
123
  def repository_file_list
124
    all_files_wildcard = File.join '.esc', '*'
125
    Dir.glob(all_files_wildcard).map { |pathname| File.basename pathname }
126
  end
127

        
128
  def snapshot
129
    write_manifest
130
    copy_manifest_files_to_repository
131
    manifest_hash = hash_and_copy_manifest
132
    write_metadata manifest_hash
133
    metadata_hash = hash_and_copy_metadata
134
    update_head metadata_hash
135
  end
136

        
137
  def update_head(metadata_hash = nil)
138
    File.open(File.join('.esc', 'HEAD'), 'w') do |file|
139
      file.write metadata_hash
140
    end
141
  end
142

        
143
  def verify_manifest(manifest = nil)
144
    manifest ||= '__manifest__'
145
    File.open(File.join('.esc', manifest)) do |file|
146
      repo_files = repository_file_list()
147
      file.readlines.each do |entry|
148
        return false unless repo_files.include? entry[0...40]
149
      end
150
    end
151
    true
152
  end
153

        
154
  def write_manifest
155
    file_deltas = deltas
156
    file_list   = []
157
    file_deltas[:new].each      { |filename| file_list << "#{hash_for_file filename} => #{filename} (new)"}
158
    file_deltas[:existing].each { |filename| file_list << "#{hash_for_file filename} => #{filename} (existing)"}
159
    File.open(File.join('.esc', '__manifest__'), 'w') do |file|
160
      file_list.sort!.each { |entry| file.puts entry }
161
    end
162
  end
163

        
164
  def write_metadata(manifest_hash = nil)
165
    File.open(File.join('.esc', '__metadata__'), 'w') do |file|
166
      file.puts "Snapshot Manifest: #{manifest_hash}"
167
      file.puts "Snapshot Parent:   #{(head_contents.empty?) ? 'root' : head_contents}"
168
      file.puts "Snapshot Taken:    #{Time.now}"
169
    end
170
  end
171
end
172

        
173

        
174
# Benchmark the cwd_hashes methods
175
def benchmark_cwd_hashes
176
  repo_dir = File.join(File.dirname(__FILE__), '../', 'specs', 'test_dir')
177
  Dir.chdir repo_dir
178

        
179
  10000.times do |idx|
180
    File.open("file_#{idx}", 'w') do |f|
181
      f.write "I am file #{idx}"
182
    end
183
  end
184

        
185
  csc = CustomSourceControl.new
186
  csc.initialize_repository
187
  csc.snapshot
188

        
189
  require 'benchmark'
190
  Benchmark.bmbm do |x|
191
    x.report("initial:")  { csc.initial_cwd_hashes }
192
    x.report("refactor:") { csc.refactor_cwd_hashes }
193
  end
194

        
195
  if Dir.exists? '.esc'
196
    FileUtils.rm_rf '.esc'
197
    FileUtils.rm Dir.glob('file_*')
198
  end
199
end
200

        
201

        
202
if __FILE__ == $0
203
  unless ['--benchmark', '--checkout', '--initialize', '--snapshot'].include? ARGV[0]
204
    puts "#{ARGV[0]} is not a subcommand."
205
    exit 1
206
  end
207

        
208
  csc = CustomSourceControl.new
209
  case ARGV[0]
210
  when '--benchmark'
211
    benchmark_cwd_hashes
212
  when '--checkout'
213
    if ARGV[1]
214
      csc.checkout ARGV[1]
215
    else
216
      puts "'checkout' subcommand takes a second argument, SHA1 of the metadata file to checkout."
217
      exit 1
218
    end
219
  when '--initialize'
220
    csc.initialize_repository
221
  when '--snapshot'
222
    csc.snapshot
223
  end
224
end