vi-vcs
Version:
Custom version control system in JavaScript with Git-style commands (init, add, commit, ls-tree, cat-file, push).
189 lines (157 loc) • 4.58 kB
JavaScript
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const GitClient = require("./git/Client");
const {
CatFileCommand,
HashObjectCommand,
AddCommand,
LSTreeCommand,
CommitCommand,
PushCommand,
} = require("./git/commands");
const gitClient = new GitClient();
const command = process.argv[2];
switch (command) {
case "init":
createGitDirectory();
break;
case "cat-file":
handleCatFileCommand();
break;
case "hash-object":
handleHashObjectCommand();
break;
case "add":
handleAddCommand();
break;
case "ls-tree":
handleLsTreeCommand();
break;
case "commit":
handleCommitCommand();
break;
case "push":
handlePushCommand();
break;
default:
throw new Error(`Unknown command ${command}`);
}
function createGitDirectory() {
fs.mkdirSync(path.join(process.cwd(), ".git"), { recursive: true });
fs.mkdirSync(path.join(process.cwd(), ".git", "objects"), {
recursive: true,
});
fs.mkdirSync(path.join(process.cwd(), ".git", "refs"), { recursive: true });
fs.writeFileSync(
path.join(process.cwd(), ".git", "HEAD"),
"ref: refs/heads/main\n"
);
console.log("Initialized git directory");
}
function handleCatFileCommand() {
const flag = process.argv[3];
const commitSHA = process.argv[4];
const command = new CatFileCommand(flag, commitSHA);
gitClient.run(command);
}
function handleHashObjectCommand() {
let flag = process.argv[3];
let filePath = process.argv[4];
if (!filePath) {
filePath = flag;
flag = null;
}
const command = new HashObjectCommand(flag, filePath);
gitClient.run(command);
}
function handleAddCommand() {
const filePaths = process.argv.slice(3);
if (filePaths.length === 0) {
throw new Error("Nothing specified, nothing added.");
}
const command = new AddCommand(filePaths);
gitClient.run(command);
}
function handleLsTreeCommand() {
let flag = process.argv[3];
let sha = process.argv[4];
if(!sha && flag=='--name-only'){
return;
}
if(!sha) {
sha = flag;
flag = null;
}
const command = new LSTreeCommand(flag, sha);
gitClient.run(command);
}
function handleCommitCommand() {
let message = null;
let amend = false;
let allowEmpty = false;
// Parse arguments
for (let i = 3; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg === "-m" || arg === "--message") {
if (i + 1 < process.argv.length) {
message = process.argv[i + 1];
i++;
} else {
throw new Error("No message provided with -m/--message flag");
}
} else if (arg === "--amend") {
amend = true;
} else if (arg === "--allow-empty") {
allowEmpty = true;
} else if (arg.startsWith("-")) {
throw new Error(`Unknown option: ${arg}`);
}
}
if (!message && !amend) {
throw new Error("No commit message provided. Use -m flag.");
}
// If amending, get the previous commit message if none provided
if (amend && !message) {
try {
const headPath = path.join(process.cwd(), ".git", "HEAD");
const headContent = fs.readFileSync(headPath, "utf8").trim();
let commitHash;
if (headContent.startsWith("ref: ")) {
const ref = headContent.substring(5);
const refPath = path.join(process.cwd(), ".git", ref);
if (fs.existsSync(refPath)) {
commitHash = fs.readFileSync(refPath, "utf8").trim();
}
} else {
commitHash = headContent;
}
if (commitHash) {
const commitObj = gitClient.run(new CatFileCommand("commit", commitHash));
const lines = commitObj.toString().split("\n");
// Find the message (after the blank line)
let messageFound = false;
let previousMessage = [];
for (const line of lines) {
if (!messageFound && line.trim() === "") {
messageFound = true;
} else if (messageFound) {
previousMessage.push(line);
}
}
message = previousMessage.join("\n");
}
} catch (err) {
throw new Error("Could not read previous commit message for amend");
}
}
const options = { amend, allowEmpty };
const command = new CommitCommand(message, options);
gitClient.run(command);
}
function handlePushCommand() {
const remoteName = process.argv[3] || "origin";
const refspec = process.argv[4] || "";
const command = new PushCommand(remoteName, refspec);
gitClient.run(command);
}