sourcecontrol
Version:
A modern TypeScript CLI application for source control
157 lines • 8.73 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexEntry = void 0;
const exceptions_1 = require("../exceptions");
const index_entry_utils_1 = require("./index-entry-utils");
class IndexEntry {
constructor(data = {}) {
this.creationTime = data.creationTime || new index_entry_utils_1.GitTimestamp(0, 0);
this.modificationTime = data.modificationTime || new index_entry_utils_1.GitTimestamp(0, 0);
this.deviceId = data.deviceId || 0;
this.inodeNumber = data.inodeNumber || 0;
this.fileMode = data.fileMode ?? 0o100644;
this.userId = data.userId || 0;
this.groupId = data.groupId || 0;
this.fileSize = data.fileSize || 0;
this.contentHash = data.contentHash || '';
this.assumeValid = data.assumeValid || false;
this.stageNumber = data.stageNumber || 0;
this.filePath = data.filePath || '';
}
get modeType() {
return (this.fileMode >> 12) & 0b1111;
}
get modePerms() {
return this.fileMode & 0o777;
}
get isRegularFile() {
return this.modeType === 0b1000;
}
get isSymlink() {
return this.modeType === 0b1010;
}
get isDirectory() {
return this.modeType === 0b0000;
}
get isGitlink() {
return this.modeType === 0b1110;
}
compareTo(other) {
const thisKey = this.isDirectory ? this.filePath + '/' : this.filePath;
const otherKey = other.isDirectory ? other.filePath + '/' : other.filePath;
if (thisKey < otherKey)
return -1;
if (thisKey > otherKey)
return 1;
return 0;
}
serialize() {
const filenameBytes = new TextEncoder().encode(this.filePath);
const filenameLength = filenameBytes.length;
const entrySize = index_entry_utils_1.IndexEntryLayout.FIXED_HEADER_SIZE + filenameLength + 1;
const paddedSize = Math.ceil(entrySize / index_entry_utils_1.IndexEntryLayout.ALIGNMENT_BOUNDARY) *
index_entry_utils_1.IndexEntryLayout.ALIGNMENT_BOUNDARY;
const buffer = new Uint8Array(paddedSize);
const dataView = new DataView(buffer.buffer);
this.writeFixedHeaderFields(dataView);
this.writeVariableFields(buffer, filenameBytes, filenameLength);
return buffer;
}
writeFixedHeaderFields(dataView) {
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.CTIME_SECONDS_OFFSET, this.creationTime.seconds, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.CTIME_NANOSECONDS_OFFSET, this.creationTime.nanoseconds, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.MTIME_SECONDS_OFFSET, this.modificationTime.seconds, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.MTIME_NANOSECONDS_OFFSET, this.modificationTime.nanoseconds, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.DEVICE_ID_OFFSET, this.deviceId, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.INODE_OFFSET, this.inodeNumber, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.MODE_OFFSET, this.fileMode, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.USER_ID_OFFSET, this.userId, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.GROUP_ID_OFFSET, this.groupId, false);
dataView.setUint32(index_entry_utils_1.IndexEntryLayout.FILE_SIZE_OFFSET, this.fileSize, false);
this.writeShaHash(dataView.buffer, index_entry_utils_1.IndexEntryLayout.SHA_OFFSET);
const flags = index_entry_utils_1.IndexEntryFlags.encode(this.assumeValid, this.stageNumber, this.filePath.length);
dataView.setUint16(index_entry_utils_1.IndexEntryLayout.FLAGS_OFFSET, flags, false);
}
writeShaHash(buffer, offset) {
const view = new Uint8Array(buffer, offset, index_entry_utils_1.IndexEntryLayout.SHA_BYTE_LENGTH);
for (let i = 0; i < index_entry_utils_1.IndexEntryLayout.SHA_BYTE_LENGTH; i++) {
const hexIndex = i * 2;
const hexPair = this.contentHash.substring(hexIndex, hexIndex + 2);
view[i] = parseInt(hexPair, 16);
}
}
writeVariableFields(buffer, filenameBytes, filenameLength) {
const filenameOffset = index_entry_utils_1.IndexEntryLayout.FIXED_HEADER_SIZE;
buffer.set(filenameBytes, filenameOffset);
buffer[filenameOffset + filenameLength] = IndexEntry.NULL_TERMINATOR;
}
static deserialize(data, offset) {
const dataView = new DataView(data.buffer, data.byteOffset + offset);
const entry = new IndexEntry();
entry.readFixedHeaderFields(dataView);
const { filename, bytesRead } = this.readVariableFields(data, offset);
entry.filePath = filename;
const entrySize = index_entry_utils_1.IndexEntryLayout.FIXED_HEADER_SIZE + bytesRead;
const nextOffset = offset +
Math.ceil(entrySize / index_entry_utils_1.IndexEntryLayout.ALIGNMENT_BOUNDARY) *
index_entry_utils_1.IndexEntryLayout.ALIGNMENT_BOUNDARY;
return { entry, nextOffset };
}
static readVariableFields(data, baseOffset) {
const filenameStartOffset = baseOffset + index_entry_utils_1.IndexEntryLayout.FIXED_HEADER_SIZE;
let nullTerminatorOffset = filenameStartOffset;
while (nullTerminatorOffset < data.length && data[nullTerminatorOffset] !== 0) {
nullTerminatorOffset++;
}
if (nullTerminatorOffset >= data.length) {
throw new exceptions_1.ObjectException('Invalid index entry: filename not null-terminated');
}
const filenameLength = nullTerminatorOffset - filenameStartOffset;
const filenameBytes = data.slice(filenameStartOffset, nullTerminatorOffset);
const filename = new TextDecoder().decode(filenameBytes);
const bytesRead = filenameLength + 1;
return { filename, bytesRead };
}
readFixedHeaderFields(dataView) {
const ctimeSeconds = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.CTIME_SECONDS_OFFSET, false);
const ctimeNanoseconds = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.CTIME_NANOSECONDS_OFFSET, false);
this.creationTime = new index_entry_utils_1.GitTimestamp(ctimeSeconds, ctimeNanoseconds);
const mtimeSeconds = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.MTIME_SECONDS_OFFSET, false);
const mtimeNanoseconds = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.MTIME_NANOSECONDS_OFFSET, false);
this.modificationTime = new index_entry_utils_1.GitTimestamp(mtimeSeconds, mtimeNanoseconds);
this.deviceId = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.DEVICE_ID_OFFSET, false);
this.inodeNumber = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.INODE_OFFSET, false);
this.fileMode = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.MODE_OFFSET, false);
this.userId = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.USER_ID_OFFSET, false);
this.groupId = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.GROUP_ID_OFFSET, false);
this.fileSize = dataView.getUint32(index_entry_utils_1.IndexEntryLayout.FILE_SIZE_OFFSET, false);
this.contentHash = this.readShaHash(dataView.buffer, dataView.byteOffset + index_entry_utils_1.IndexEntryLayout.SHA_OFFSET);
const flagsValue = dataView.getUint16(index_entry_utils_1.IndexEntryLayout.FLAGS_OFFSET, false);
const { assumeValid, stage } = index_entry_utils_1.IndexEntryFlags.decode(flagsValue);
this.assumeValid = assumeValid;
this.stageNumber = stage;
}
readShaHash(buffer, offset) {
const view = new Uint8Array(buffer, offset, index_entry_utils_1.IndexEntryLayout.SHA_BYTE_LENGTH);
return Array.from(view)
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('');
}
static fromFileStats(path, stats, sha) {
return new IndexEntry({
filePath: path,
creationTime: new index_entry_utils_1.GitTimestamp(Math.floor(stats.ctimeMs / 1000), stats.ctimeMs % 1000),
modificationTime: new index_entry_utils_1.GitTimestamp(Math.floor(stats.mtimeMs / 1000), stats.mtimeMs % 1000),
deviceId: stats.dev,
inodeNumber: stats.ino,
fileMode: stats.mode,
userId: stats.uid,
groupId: stats.gid,
fileSize: stats.size,
contentHash: sha,
});
}
}
exports.IndexEntry = IndexEntry;
IndexEntry.NULL_TERMINATOR = 0;
//# sourceMappingURL=index-entry.js.map