Skip to content
Open
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
47 changes: 34 additions & 13 deletions src/commands/add.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { readFile } from "node:fs/promises";
import { readFile, readdir, stat } from "node:fs/promises";
import { join } from "node:path";

import { GIT_INDEX } from "../constants.js";
import { coloredLog } from "../functions/colored-log.js";
Expand All @@ -7,6 +8,36 @@ import { isValidPath } from "../functions/is-valid-path.js";
import { BlobObject } from "../models/blob-object.js";
import { GitIndex } from "../models/git-index.js";

const processPath = async (
filePath: string,
gitIndex: GitIndex,
): Promise<void> => {
const stats = await stat(filePath);

if (stats.isDirectory()) {
if (filePath === ".git") {
return;
}
Comment on lines +18 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good-badge
ナイスハンドリングです

const entries = await readdir(filePath);
for (const entry of entries) {
await processPath(join(filePath, entry), gitIndex);
}
} else if (stats.isFile()) {
const content = await readFile(filePath);
const blobObject = new BlobObject(content);
const hash = await blobObject.dumpBlobObject();

if (!gitIndex.checkDuplicate(filePath, hash)) {
await gitIndex.pushEntry(filePath, hash);

coloredLog({
text: `added '${filePath}'`,
color: "green",
});
}
}
};

export const add = async (options: Array<string>): Promise<void> => {
const filePath = options[0];

Expand All @@ -21,7 +52,7 @@ export const add = async (options: Array<string>): Promise<void> => {
}

//ファイル名が条件を満たしていない場合の処理
if (!isValidPath(filePath)) {
if (filePath !== "." && !isValidPath(filePath)) {
coloredLog({
text: `fatal: invalid path '${filePath}'`,
color: "red",
Expand All @@ -38,20 +69,10 @@ export const add = async (options: Array<string>): Promise<void> => {
return;
}

// TODO: ディレクトリを指定した際などに複数回pushEntryする
const content = await readFile(filePath);

const blobObject = new BlobObject(content);
const hash = await blobObject.dumpBlobObject();

const gitIndex = new GitIndex(GIT_INDEX);
await gitIndex.initialize();

if (gitIndex.checkDuplicate(filePath, hash)) {
console.log("Nothing has changed.");
return;
}
await processPath(filePath, gitIndex);

await gitIndex.pushEntry(filePath, hash);
await gitIndex.dumpIndex();
};
90 changes: 90 additions & 0 deletions src/commands/commit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { readFile, writeFile } from "fs/promises";
import { join } from "path";

import { COMMIT_OPTIONS, GIT_DIR, GIT_HEAD, GIT_INDEX } from "../constants.js";
import { coloredLog } from "../functions/colored-log.js";
import { extractHeadHash } from "../functions/extract-head-hash.js";
import { Commit } from "../models/commit.js";
import { GitIndex } from "../models/git-index.js";
import { TreeObject } from "../models/tree-object.js";
import { add } from "./add.js";

export const commit = async (options: Array<string>): Promise<void> => {
//ファイル名指定でコミットはできない仕様とする
const option = options[0];
const message = options[1];

//optionもしくはmessageが存在しない場合
if (!(option && message)) {
coloredLog({
text: "invalid command",
color: "red",
});
return;
}

//optionがあらかじめ用意したものと一致しない場合
if (!COMMIT_OPTIONS.some((OPTION) => OPTION.name === option)) {
coloredLog({
text: `error: unknown switch '${option}'\n`,
color: "red",
});
console.log("Commit options:");
COMMIT_OPTIONS.forEach((option) => {
console.log(` ${option.name} ${option.description}\n`);
});
}

//optionが -amだった場合は全てのファイルをaddする
if (option === "-am") {
await add(["."]);
}

//indexからファイルパスとblobオブジェクトのhashを取得
const gitIndex = new GitIndex(GIT_INDEX);
await gitIndex.initialize();
const fileData = gitIndex.getFileData();

const treeObject = new TreeObject(fileData);
const rootTreeHash = await treeObject.dumpAllTrees();

//ファイルがステージングされていない場合
if (!rootTreeHash) {
console.log(
'nothing added to commit but untracked files present (use "git add" to track)',
);
return;
}

const parentHash = await extractHeadHash();

//コミットに含める情報をセットしてdump
const commit = new Commit();
commit.setCommit({
tree: rootTreeHash,
parent: parentHash,
author: "yamada taro",
email: "yamada@gmail.com",
message: message,
});
const commitHash = await commit.dumpCommit();

//headを最新のコミットhashに更新しておく
const headContent = await readFile(GIT_HEAD, "utf8");

if (headContent.startsWith("ref: ")) {
//今回の実装ではmainブランチのみの実装とする
const branchPath = "main";

const branchFilePath = join(GIT_DIR, "refs/heads", branchPath);
await writeFile(branchFilePath, commitHash, "utf8");

console.log(`Updated ${branchPath} branch with commit hash: ${commitHash}`);
} else {
await writeFile(GIT_HEAD, commitHash, "utf8");

console.log(
`Updated ${parentHash ?? ""} branch with commit hash: ${commitHash}`,
);
}
};
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { add } from "./add.js";
import { commit } from "./commit.js";
import { log } from "./log.js";

export const validCommand = {
add: add,
commit: commit,
log: log,
help: () => {
console.log("Available commands:");
Expand Down
34 changes: 3 additions & 31 deletions src/commands/log.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,13 @@
import { readFile } from "node:fs/promises";
import { join } from "path";

import { GIT_DIR } from "../constants.js";
import { coloredLog } from "../functions/colored-log.js";
import { exists } from "../functions/exists.js";
import { extractHeadHash } from "../functions/extract-head-hash.js";
import { Commit, CommitFieldType } from "../models/commit.js";

const extractHeadHash = async (): Promise<string | undefined> => {
const headPath = join(GIT_DIR, "HEAD");

if (!(await exists(headPath))) {
return;
}

const headText = await readFile(headPath).then((head) =>
head.toString("utf-8"),
);

const refPrefix = "ref: ";
//ブランチ名かコミットハッシュのどちらをHEADに持つかを識別して出し分ける
if (headText.startsWith(refPrefix)) {
return await readFile(
join(GIT_DIR, headText.slice(refPrefix.length)).trim(),
"utf-8",
).then((path) => path.trim());
} else {
return headText.trim();
}
};

const getCommitHistory = async (
hash: string,
history: Array<CommitFieldType> = [],
): Promise<Array<CommitFieldType>> => {
const commit = new Commit();
await commit.setCommit(hash);
await commit.setCommitHash(hash);
const commitData = commit.getCommit();

const currentHistory = [...history, commitData];
Expand All @@ -54,8 +27,7 @@ export const displayCommitHistory = (
text: `commit: ${commit.hash}`,
color: "yellow",
});
console.log(`Author: ${commit.author}`);
console.log(`Committer: ${commit.committer}\n`);
console.log(`author: ${commit.author}\n`);
console.log(` ${commit.message}\n`);
});
};
Expand Down
12 changes: 12 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ export const CWD = process.cwd();
export const GIT_DIR = join(process.cwd(), ".git");
export const GIT_OBJECTS = join(GIT_DIR, "objects");
export const GIT_INDEX = join(GIT_DIR, "index");
export const GIT_HEAD = join(GIT_DIR, "HEAD");

export const COMMIT_OPTIONS = [
{
name: "-m",
description: "commit message",
},
{
name: "-am",
description: "add all files and commit message",
},
];
1 change: 1 addition & 0 deletions src/functions/colored-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const colors = {
//https://qiita.com/shuhei/items/a61b4324fd5dbc1af79b
yellow: "\u001b[33m",
red: "\u001b[31m",
green: "\u001b[32m",
};

export const coloredLog = ({
Expand Down
27 changes: 27 additions & 0 deletions src/functions/extract-head-hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { readFile } from "fs/promises";
import { join } from "path";

import { GIT_DIR } from "../constants.js";
import { exists } from "./exists.js";

export const extractHeadHash = async (): Promise<string | undefined> => {
const headPath = join(GIT_DIR, "HEAD");

if (!(await exists(headPath))) {
return;
}

const headText = await readFile(headPath).then((head) =>
head.toString("utf-8"),
);

const refPrefix = "ref: ";
//ブランチ名かコミットハッシュのどちらをHEADに持つかを識別して出し分ける
if (headText.startsWith(refPrefix)) {
const branchPath = join(GIT_DIR, headText.slice(refPrefix.length)).trim();
if (!(await exists(branchPath))) return;
return await readFile(branchPath, "utf-8").then((path) => path.trim());
} else {
return headText.trim();
}
};
Loading