Skip to content

Instantly share code, notes, and snippets.

@jcoglan
Created October 8, 2017 16:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcoglan/cf4fc61049337b1b84e3a996059bae73 to your computer and use it in GitHub Desktop.
Save jcoglan/cf4fc61049337b1b84e3a996059bae73 to your computer and use it in GitHub Desktop.
require "set"
class Index
SIGNATURE = "DIRC"
VERSIONS = [2, 3, 4]
HEADER_SIZE = 12
MIN_ENTRY_SIZE = 64
ENTRY_BLOCK = 8
HEADER_FORMAT = "a4N2"
ENTRY_FORMAT = "N10H40nZ*"
ENTRY_FIELDS = %i[
ctime ctime_nano
mtime mtime_nano
dev ino mode uid gid size
oid flags path
]
STAGES = 0..3
def initialize(pathname)
@pathname = pathname
@keys = SortedSet.new
@entries = {}
read_from_disk
end
def add(path, oid, stat, stage = 0)
case stage
when 0 then remove(path, 1..3)
when 1..3 then remove(path, [0])
end
entry = Entry.new(
stat.ctime.to_i, 0,
stat.mtime.to_i, 0,
stat.dev, stat.ino, stat.mode, stat.uid, stat.gid, stat.size,
oid, 0, path)
entry.stage = stage
entry.path_size = path.bytesize
add_entry(entry)
end
def remove(path, stages = STAGES)
stages.each { |stage| @keys.delete([path, stage]) }
end
def commit
write_to_disk
end
private
def add_entry(entry)
key = entry.key
@keys.add(key)
@entries[key] = entry
end
def read_from_disk
begin
content = File.read(@pathname)
rescue Errno::EACCES
raise "file exists but is unreadable"
rescue Errno::ENOENT
return
end
offset = HEADER_SIZE
signature, version, count = content.byteslice(0, offset).unpack(HEADER_FORMAT)
raise "invalid signature" unless signature == SIGNATURE
raise "invalid version" unless VERSIONS.include?(version)
count.times do |i|
size = MIN_ENTRY_SIZE
size += ENTRY_BLOCK until content.getbyte(offset + size - 1) == 0
entry = Entry.parse(content.byteslice(offset, size))
add_entry(entry)
offset += size
end
end
def write_to_disk
File.open(@pathname, "w") do |file|
header = [SIGNATURE, 2, @keys.size].pack(HEADER_FORMAT)
file.write(header)
@keys.each do |key|
entry = @entries[key]
file.write(entry)
end
end
rescue Errno::EACCES
raise "file exists but is not writable"
end
Entry = Struct.new(*ENTRY_FIELDS) do
def self.parse(string)
fields = string.unpack(ENTRY_FORMAT)
new(*fields)
end
def to_s
string = to_a.pack(ENTRY_FORMAT)
string << "\0" until string.bytesize % ENTRY_BLOCK == 0
string
end
def key
[path, stage]
end
def stage
flags >> 12 & 0x3
end
def stage=(stage)
self.flags |= stage << 12
end
def path_size
flags & 0xFFF
end
def path_size=(size)
self.flags |= [size, 0xFFF].min
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment