Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example
Submodule example added at 5fd5ee
103 changes: 103 additions & 0 deletions src/blob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { createHash } from "crypto";
import fs from "fs";
import path from "path";
import zlib from "zlib";

interface GitBlobObject {
type: "blob";
size: number;
content: string;
}

/**
* .git/object/ 以下に保存された Git Blob オブジェクトを読み込む。
* @param hash Git Blob オブジェクトのハッシュ値
* @returns GitBlobObject
*/
const loadBlob = (hash: string): GitBlobObject => {
const cwd = process.cwd();
const dirName = hash.slice(0, 2);
const fileName = hash.slice(2);
const objectPath = path.join(cwd, ".git", "objects", dirName, fileName);

const rawData = fs.readFileSync(objectPath, "binary");
const dataBuffer = Buffer.from(rawData, "binary");
const decompressed = zlib.unzipSync(toArrayBuffer(dataBuffer));

// Parse the Git object structure: "<type> <size>\0<content>"
const decompressedString = decompressed.toString("utf-8");
const nullIndex = decompressedString.indexOf("\0");
if (nullIndex === -1) {
throw new Error("Invalid Git object format: Missing null separator.");
}

const header = decompressedString.slice(0, nullIndex);
const content = decompressedString.slice(nullIndex + 1);

const [type, size] = header.split(" ");
if (!type || !size || isNaN(Number(size))) {
throw new Error("Invalid Git object format: Invalid header.");
}

if (type !== "blob") {
throw new Error("Invalid Git object format: Invalid type. (excepted: blob)");
}

return { type, size: Number(size), content };
};

/**
* Git Blob オブジェクトを .git/object/ 以下に保存する。
* @param blob GitBlobObject
*/
const saveBlob = (blob: GitBlobObject): void => {
const blobBuffer = blob2UInt8Array(blob);

// Compress the blob data
const compressed = zlib.deflateSync(blobBuffer);

// Calculate the hash of the blob
const hash = createHash("sha1").update(blobBuffer).digest("hex");

// Write the compressed blob to the file
const cwd = process.cwd();
const dirName = hash.slice(0, 2);
const fileName = hash.slice(2);
const dirPath = path.join(cwd, ".git", "objects", dirName);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
const filePath = path.join(dirPath, fileName);
fs.writeFileSync(filePath, new Uint8Array(compressed));
};



const blob2UInt8Array = (blob: GitBlobObject): Uint8Array => {
const contentBuffer = Buffer.from(blob.content, "utf-8");
const header = `${blob.type} ${contentBuffer.length.toString()}\0`;
const headerBuffer = Buffer.from(header, "utf-8");
const blobBuffer = Buffer.concat([headerBuffer, contentBuffer].map((b) => new Uint8Array(b)));
return new Uint8Array(blobBuffer);
};

const blob2Hash = (blob: GitBlobObject): string => {
const blobBuffer = blob2UInt8Array(blob);
return createHash("sha1").update(blobBuffer).digest("hex");
};

const toArrayBuffer = (buffer: Buffer): ArrayBuffer => {
const ab = new ArrayBuffer(buffer.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buffer.length; ++i) {
// TODO: buffer[i] === undefined の場合に0を入れているが、データ整合静的にはこれでいいのか?
view[i] = buffer[i] ?? 0;
}
return ab;
}

// test
const data: GitBlobObject = { type: "blob", size: 5, content: "Hello\n" };
saveBlob(data);
console.log(blob2Hash(data));
console.log(loadBlob(blob2Hash(data)));
53 changes: 0 additions & 53 deletions src/loader.ts

This file was deleted.

42 changes: 17 additions & 25 deletions src/mygit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,23 @@ const __dirname = path.dirname(__filename);

export const mygit = async (argv: Array<string>): Promise<void> => {
const commandName = argv[2] ?? "";

const commandsDirPath = path.join(__dirname, "commands");
const commandsDir = fs.readdirSync(commandsDirPath)
.filter((file) => file.endsWith(".js"));

for (const file of commandsDir) {
if(file.replace(".js", "") !== commandName) {
continue;
}

// * ESM Module まわりが any を使わざるを得ないので、ESLint 側の制限を緩めてます
const commandsPath = path.join(commandsDirPath, file);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const commandModule = await import(commandsPath);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const command: Command = commandModule.default;

if (typeof command.exec === "function") {
command.exec(argv);
return;
}

switch (commandName) {
case "log":
break;

case "add":
break;

case "commit":
break;

default:
console.log("サポートされていない引数です ><");
}
}


console.log("サポートされていない引数です ><");

// Avoid eslint error by adding some async operation.
await new Promise((resolve) => setTimeout(resolve, 1000));
// Avoid eslint error by adding some async operation.
await new Promise((resolve) => setTimeout(resolve, 1000));
};