lib/ttnt/storage.rb
require 'ttnt/internals'
module TTNT
# A utility class to store TTNT data such as test-to-code mapping and metadata.
class Storage
# Initialize the storage from given repo and sha. This reads contents from
# a `.ttnt` file. When sha is not nil, contents of the file on that commit
# is read. Data can be written only when sha is nil (written to current
# working tree).
#
# @param repo [Rugged::Repository]
# @param sha [String] sha of the commit which data should be read from.
# nil means reading from/writing to current working tree.
def initialize(repo, sha = nil)
@repo = repo
@sha = sha
end
# Read data from the storage in the given section.
#
# @param section [String]
# @return [Hash]
def read(section)
str = read_storage_content
if str.length > 0
JSON.parse(str)[section] || {}
else
{}
end
end
# Write value to the given section in the storage.
# Locks the file so that concurrent write does not occur.
#
# @param section [String]
# @param value [Hash]
def write!(section, value)
raise 'Data cannot be written to the storage back in git history' unless @sha.nil?
File.open(filename, File::RDWR|File::CREAT, 0644) do |f|
f.flock(File::LOCK_EX)
str = f.read
data = str.length > 0 ? JSON.parse(str) : {}
data[section] = value
f.rewind
f.write(data.to_json)
f.flush
f.truncate(f.pos)
end
end
private
def filename
"#{TTNT.root_dir}/.ttnt"
end
def filename_from_repository_root
filename.gsub(@repo.workdir, '')
end
def storage_file_oid
tree = @repo.lookup(@sha).tree
paths = filename_from_repository_root.split(File::SEPARATOR)
dirs, filename = paths[0...-1], paths[-1]
dirs.each do |dir|
obj = tree[dir]
return nil unless obj
tree = @repo.lookup(obj[:oid])
end
obj = tree[filename]
return nil unless obj
obj[:oid]
end
def read_storage_content
if @sha
if oid = storage_file_oid
@repo.lookup(oid).content
else
'' # Storage file is not committed for the commit of given sha
end
else
File.exist?(filename) ? File.read(filename) : ''
end
end
end
end