Last stage (#MG6) of git

I am stuck in just last stage of git clone command in java script
if someone could tell me what to fix it would be helpful
here is the code
const fs = require(“fs”);
const path = require(“path”);
const zlib = require(“zlib”);
const crypto = require(“crypto”);
const https = require(“https”);

const command = process.argv[2];

switch (command) {
case “init”:
createGitDirectory();
break;

case "cat-file":
    if (process.argv.length < 5) {
        console.error('Missing arguments. Use: ./your_git.sh cat-file <option> <object>');
        break;
    }
    const option = process.argv[3];
    const object = process.argv[4];
    const content = getCatFile(option, object);
    if (content !== null) {
        console.log(content);
    }
    break;
case "hash-object":
    hashObject();
    break;

case "ls-tree":
    if (process.argv.length < 5) {
        console.log('Missing argument: <hash>');
        break;
    }

    lsTree();
    break;

case "write-tree":
    process.stdout.write(writeTree());
    break;

case "commit-tree":
    if (process.argv.length < 8) {
        console.log('Missing argument: <tree> <parent> <message>')
        break;
    }
    const tree = process.argv[3]
    const parent = process.argv[5]
    const message = process.argv[7]
    commitTree(tree, message, parent)
    break;


case "clone":
    if (process.argv.length < 5) {
        console.log('Usage: node script.js clone <repository_url> <target_directory>');
        break;
    }
    const repoUrl = process.argv[3];
    const targetDir = process.argv[4];
    cloneRepository(repoUrl, targetDir);
    break;

default:
    throw new Error(`Unknown command ${command}`);

}

function createGitDirectory() {
const gitDir = path.join(__dirname, “.git”);
fs.mkdirSync(gitDir, { recursive: true });
fs.mkdirSync(path.join(gitDir, “objects”), { recursive: true });
fs.mkdirSync(path.join(gitDir, “refs”), { recursive: true });

fs.writeFileSync(path.join(gitDir, "HEAD"), "ref: refs/heads/main\n");
console.log("Initialized git directory");

}

function getCatFile(option, object) {
switch (option) {
case “-p”:
const folder = object.substring(0, 2);
const file = object.substring(2);

        const contents = fs.readFileSync(path.join(__dirname, ".git", "objects", folder, file));

        const decompress = zlib.inflateSync(contents);
        // Remove the prefix "blob 38\x00" from the content
        const content = decompress.toString('utf-8').split('\0')[1];
        process.stdout.write(content);
    default:
        console.error("Invalid usage. Use: ./your_git.sh cat-file -p <object>");
        return null;
}

}

function hashObject() {
if (process.argv[3] !== “-w”) return;

const file = process.argv[4];
const content = fs.readFileSync(file);
const header = `blob ${content.length}\x00`;//the reason for 00 is hexadec
const data = header + content;
const hash = crypto.createHash('sha1').update(data).digest('hex');
const objectsDirPath = path.join(__dirname, ".git", "objects");
const hashDirPath = path.join(objectsDirPath, hash.slice(0, 2));
const filePath = path.join(hashDirPath, hash.slice(2));
fs.mkdirSync(hashDirPath, { recursive: true });
fs.writeFileSync(filePath, zlib.deflateSync(data));
process.stdout.write(hash);

}

// function lsTree() {
// if (process.argv[3] !== “–name-only”) {
// throw new Error(“Only --name-only flag is supported for now”);
// }
// const hash = process.argv[4];

// // Reading dir from hash
// const [folder, file] = [hash.slice(0, 2), hash.slice(2)];
// const objPath = path.join(__dirname, “.git”, “objects”, folder, file);
// const compressedContents = fs.readFileSync(objPath);
// const deflate = zlib.inflateSync(compressedContents).toString();
// const contents = deflate.split(‘\0’).slice(1).map((line) => line.split(" “).slice(1).join(” “));
// process.stdout.write(contents.join(”\n"));
// }
function lsTree() {
if (process.argv[3] !== “–name-only”) {
throw new Error(“Only --name-only flag is supported for now”);
}
const hash = process.argv[4];

// Reading dir from hash
const [folder, file] = [hash.slice(0, 2), hash.slice(2)];
const objPath = path.join(__dirname, ".git", "objects", folder, file);
const compressedContents = fs.readFileSync(objPath);
const deflate = zlib.inflateSync(compressedContents).toString();
const contents = deflate.split('\0').slice(1).map((line) => line.split(" ").slice(1).join(" "));
const fileNames = contents.filter(Boolean); // Filter out empty strings
const output = fileNames.join("\n") + "\n"; // Ensure output ends with newline
process.stdout.write(output);

}

function writeTree(dir = __dirname) {
const filesAndDirs = fs.readdirSync(dir).filter((f) => f !== “.git” && f !== “main.js”);
const entries = ;

for (const file of filesAndDirs) {
    const fullPath = path.join(dir, file);
    
    if (fs.statSync(fullPath).isFile()) {
        entries.push({
            mode: 100644,
            name: file,
            hash: writeBlob(fullPath),
        });
    } else {
        entries.push({
            mode: 40000,
            name: file,
            hash: writeTree(path.join(dir, file)),
        });
    }
}

const contents = entries.reduce((acc, { mode, name, hash }) => {
    return Buffer.concat([
        acc,
        Buffer.from(`${mode} ${name}\0`),
        Buffer.from(hash, "hex"),
    ]);
}, Buffer.alloc(0));

const treeContents = Buffer.concat([
    Buffer.from(`tree ${contents.length}\x00`),
    contents,
]);

// Calculate the hash of the tree contents
const treeHash = hashContent(treeContents);

// Write the tree object to the git directory
fs.mkdirSync(path.join(__dirname, ".git", "objects", treeHash.slice(0, 2)), { recursive: true });
fs.writeFileSync(
    path.join(__dirname, ".git", "objects", treeHash.slice(0, 2), treeHash.slice(2)),
    zlib.deflateSync(treeContents)
);

return treeHash;

}

function hashContent(data) {
return crypto.createHash(“sha1”).update(data).digest(“hex”);
}
function writeBlob(file) {
const fileContent = fs.readFileSync(file, “utf-8”);
const blob = blob ${fileContent.length}\x00${fileContent};
const hash = crypto.createHash(“sha1”).update(blob).digest(“hex”);
const directory = hash.slice(0, 2);
const filename = hash.slice(2);
fs.mkdirSync(path.join(__dirname, “.git”, “objects”, directory), {
recursive: true,
});
fs.writeFileSync(
path.join(__dirname, “.git”, “objects”, directory, filename),
zlib.deflateSync(blob)
);
return hash;
}
function commitTree(tree, message, parent = null) {
const author = ‘Dev’;
const authorEmail = ‘dev.sra2180@gmail.com’;
let contents = Buffer.from(‘tree ’ + tree + ‘\n’);
if (parent) {
contents = Buffer.concat([contents, Buffer.from(‘parent ’ + parent + ‘\n’)]);
}
const now = new Date();
const timestamp = now.valueOf();
const offset = now.getTimezoneOffset();
contents = Buffer.concat([
contents,
Buffer.from(author ${author} <${authorEmail}> ${timestamp} ${offset}\n),
]);
contents = Buffer.concat([
contents,
Buffer.from(committer ${author} <${authorEmail}> ${timestamp} ${offset}\n),
]);
contents = Buffer.concat([contents, Buffer.from(’\n’)]);
contents = Buffer.concat([contents, Buffer.from(message + ‘\n’)]);
const header = 'commit ’ + contents.length + ‘\0’;
const data = Buffer.concat([Buffer.from(header), contents]);
const hash = hashContent(data);

const dirPath = path.join(__dirname, '.git', 'objects', hash.slice(0, 2));
fs.mkdirSync(path.join(dirPath), {
    recursive: true,
});
const compressed = zlib.deflateSync(data);
fs.writeFileSync(path.join(dirPath, hash.slice(2)), compressed);
process.stdout.write(hash);
return hash;

}

function cloneRepository(repoUrl, targetDir) {
// Fetch the repository data from the provided URL
const repoName = path.basename(repoUrl, “.git”);
const options = {
hostname: “api.github.com”,
path: /repos/${repoUrl}, // Include the full repository URL
method: “GET”,
headers: {
“User-Agent”: “Node.js”,
},
};

console.log("Fetching repository info from:", `https://${options.hostname}${options.path}`);

const req = https.get(options, (res) => {
    console.log("Response status code:", res.statusCode);
    if (res.statusCode === 200) {
        let data = "";
        res.on("data", (chunk) => {
            data += chunk;
        });

        res.on("end", () => {
            const repoInfo = JSON.parse(data);
            const cloneUrl = repoInfo.clone_url;
            const cloneDir = path.join(targetDir, repoName);

            console.log("Cloning repository from:", cloneUrl);
            console.log("Into directory:", cloneDir);

            // Clone the repository by fetching its contents
            https.get(cloneUrl, (res) => {
                if (res.statusCode === 200) {
                    const tarFile = fs.createWriteStream(`${cloneDir}.tar.gz`);
                    res.pipe(tarFile);

                    tarFile.on("finish", () => {
                        tarFile.close(() => {
                            // Extract the cloned repository
                            extractRepository(`${cloneDir}.tar.gz`, cloneDir);
                        });
                    });
                } else {
                    console.error(`Failed to clone repository: ${repoUrl}. Status code: ${res.statusCode}`);
                }
            }).on("error", (err) => {
                console.error(`Error while fetching repository: ${err}`);
            });
        });
    } else if (res.statusCode === 404) {
        console.error(`Repository does not exist: ${repoUrl}`);
    } else {
        console.error(`Failed to fetch repository info: ${res.statusCode}`);
    }
});

req.on("error", (err) => {
    console.error(`Error while fetching repository info: ${err}`);
});

}

function extractRepository(tarFilePath, extractDir) {
// Check if the tar.gz file exists
if (!fs.existsSync(tarFilePath)) {
console.error(Error: Tar file does not exist: ${tarFilePath});
return;
}

// Check if the extraction directory exists, create it if not
if (!fs.existsSync(extractDir)) {
    fs.mkdirSync(extractDir, { recursive: true });
}

// Create a read stream for the tar.gz file
const readStream = fs.createReadStream(tarFilePath);
readStream.on("error", (err) => {
    console.error("Error reading tar file:", err);
});

// Create a gunzip transform stream
const gunzip = zlib.createGunzip();
gunzip.on("error", (err) => {
    console.error("Error decompressing tar file:", err);
});

// Create a tar extraction stream
const extractStream = tar.extract({ cwd: extractDir });
extractStream.on("error", (err) => {
    console.error("Error extracting tar contents:", err);
});

// Pipe the read stream through the gunzip and tar extraction streams
readStream.pipe(gunzip)
    .on("error", (err) => {
        console.error("Error piping read stream to gunzip:", err);
    })
    .pipe(extractStream)
    .on("error", (err) => {
        console.error("Error piping gunzip stream to tar extraction:", err);
    })
    .on("end", () => {
        // Once extraction is complete, remove the tar.gz file
        fs.unlinkSync(tarFilePath);
    });

}

@dev2180 could you share the logs you’re seeing? Where do you think the error is? Is there any specific part you need help with?

Also, the code’d be easier to read if you could publish to GitHub, it seems weirdly formatted in your post above.

Marking this as closed since we haven’t seen in a response in a while, please re-open if you’ve got more info to share!

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.

Note: I’ve updated the title of this post to include the stage ID (#MG6). You can learn about the stages rename here: Upcoming change: Stages overhaul.