UNPKG

sourcecontrol

Version:

A modern TypeScript CLI application for source control

157 lines 8.73 kB
"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