I’m stuck on Stage #MG6
I’ve tried logging all the responses. From my code i found out that console.log(“data event triggered”) is not logged, indicating a possible fault at this section.
Here are my logs:
[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
Debug = true
[tester::#MG6] Running tests for Stage #MG6 (Clone a repository)
[tester::#MG6] $ ./your_program.sh clone https://github.com/codecrafters-io/git-sample-1 <testDir>
[your_program] Logs from your program will appear here!
[your_program] Initialized repository in test_dir
[your_program] Fetched refs: 001e# service=git-upload-pack
[your_program] 0000015547b37f1a82bfe85f6d8df52b6258b75e4343b7fd HEADmulti_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed allow-tip-sha1-in-want allow-reachable-sha1-in-want no-done symref=HEAD:refs/heads/master filter object-format=sha1 agent=git/github-395dce4f6ecf
[your_program] 003f47b37f1a82bfe85f6d8df52b6258b75e4343b7fd refs/heads/master
[your_program] 0000
[your_program] Fetching packfile...
[your_program] Cloning branch: master with SHA: 47b37f1a82bfe85f6d8df52b6258b75e4343b7fd
[your_program] Resolved URL: https://github.com/codecrafters-io/git-sample-1/git-upload-pack
[your_program] Request Payload: 0032want 47b37f1a82bfe85f6d8df52b6258b75e4343b7fd00000009done
[your_program]
[your_program] HTTP Response Status: 200
[your_program] Response Headers: {
[your_program] server: 'GitHub-Babel/3.0',
[your_program] 'content-type': 'application/x-git-upload-pack-result',
[your_program] 'content-security-policy': "default-src 'none'; sandbox",
[your_program] expires: 'Fri, 01 Jan 1980 00:00:00 GMT',
[your_program] pragma: 'no-cache',
[your_program] 'cache-control': 'no-cache, max-age=0, must-revalidate',
[your_program] vary: 'Accept-Encoding',
[your_program] 'transfer-encoding': 'chunked',
[your_program] date: 'Sat, 28 Dec 2024 05:42:43 GMT',
[your_program] 'x-github-request-id': 'BE38:3876BA:11971D0E:1BD078DA:676F8FD3',
[your_program] 'x-frame-options': 'DENY'
[your_program] }
[your_program] No data received in response.
[your_program] Packfile is empty.
[your_program] End event triggered.
[your_program] Complete Response Body:
[tester::#MG6] Expected 0 as exit code, got: 1
[tester::#MG6] Test failed
And here’s a snippet of my code:
function cloneRepo(repoUrl, targetDir) {
initializeRepoDirectory(targetDir);
fetchRefs(repoUrl, (err, refs) => {
if (err) {
console.error("Failed to fetch refs:", err.message);
process.exit(1);
}
console.log("Fetched refs:", refs);
// Parse refs and send packfile request
fetchPackfile(repoUrl, refs, targetDir);
});
}
function initializeRepoDirectory(targetDir) {
const gitDir = path.join(targetDir, ".git");
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 repository in ${targetDir}`);
}
function fetchRefs(repoUrl, callback) {
const refUrl = `${repoUrl}/info/refs?service=git-upload-pack`;
https.get(refUrl, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode === 200) {
callback(null, data);
} else {
callback(new Error(`Failed to fetch refs: HTTP ${res.statusCode}`));
}
});
}).on('error', callback);
}
function fetchPackfile(repoUrl, refs, targetDir) {
console.log("Fetching packfile...");
// Extract the branch reference we are cloning (e.g., refs/heads/main)
const branchRef = refs.match(/([a-f0-9]{40})\s+refs\/heads\/([a-zA-Z0-9_-]+)/);
if (!branchRef) {
console.error("Could not find any branch in refs.");
console.error("Received refs data:", refs);
process.exit(1);
}
const branchName = branchRef[2]; // Branch name (e.g., master, main)
const branchSha = branchRef[1]; // SHA of the latest commit on the branch
console.log(`Cloning branch: ${branchName} with SHA: ${branchSha}`);
// Prepare the POST request to git-upload-pack
const options = {
method: "POST",
headers: {
"Content-Type": "application/x-git-upload-pack-request",
},
};
// Convert URL to appropriate path
const url = new URL(repoUrl);
url.pathname += "/git-upload-pack";
console.log("Resolved URL:", url.href);
// Create the packfile request payload
const requestPayload = `0032want ${branchSha}00000009done\n`;
console.log(`Request Payload: ${requestPayload}`);
// Make the HTTP request
const req = https.request(url, options, (res) => {
console.log(`HTTP Response Status: ${res.statusCode}`);
console.log(`Response Headers:`, res.headers);
if (res.headers['content-length'] === '0') {
console.error("Server responded with no content.");
}
if (!res.headers['transfer-encoding'] && !res.headers['content-length']) {
console.error("No transfer-encoding or content-length. No data expected.");
}
let responseBody = '';
res.on('data', chunk => {
console.log("data event triggered")
responseBody += chunk;
console.log("Received chunk:", chunk.length);
console.log('Received Chunk:', chunk.toString('hex')); // Log in hex to inspect raw data
});
res.on('end', () => {
console.log("End event triggered.");
if (!responseBody) {
console.error("No data received in response.");
}
console.log('Complete Response Body:', responseBody);
const outputPath = path.join(targetDir, "packfile");
if (responseBody.length > 0) {
fs.writeFileSync(outputPath, responseBody);
const stats = fs.statSync(outputPath);
console.log(`Packfile saved to ${outputPath}, size: ${stats.size} bytes`);
} else {
console.error("Packfile is empty.");
process.exit(1);
}
});
res.on('close', () => {
console.log("Close event triggered.");
});
res.on('error', err => {
console.error("Error event triggered:", err);
});
if (res.statusCode !== 200) {
console.error(`Failed to fetch packfile: HTTP ${res.statusCode}`);
process.exit(1);
}
const outputPath = path.join(targetDir, "packfile");
const fileStream = fs.createWriteStream(outputPath);
res.pipe(fileStream);
fileStream.on("finish", () => {
console.log(`Packfile saved to ${outputPath}`);
const stats = fs.statSync(outputPath);
console.log(`Packfile size: ${stats.size} bytes`);
unpackPackfile(outputPath, targetDir); // Unpack the downloaded packfile
});
fileStream.on("error", (err) => {
console.error("Error saving packfile:", err);
process.exit(1);
});
});
req.on("error", (err) => {
console.error("Error fetching packfile:", err);
process.exit(1);
});
req.write(requestPayload);
req.end();
}
function unpackPackfile(packfilePath, targetDir) {
const gitObjectsDir = path.join(targetDir, ".git/objects");
const packfileStream = fs.createReadStream(packfilePath);
let data = Buffer.alloc(0);
packfileStream.on('data', chunk => {
data = Buffer.concat([data, chunk]);
});
packfileStream.on('end', () => {
console.log("Unpacking packfile...");
const objects = parsePackfile(data);
objects.forEach(obj => {
// Compute SHA-1 hash for the object
const hash = computeObjectHash(obj.type, obj.content);
// Save the object to .git/objects
const objectPath = path.join(gitObjectsDir, hash.substring(0, 2), hash.substring(2));
fs.mkdirSync(path.dirname(objectPath), { recursive: true });
fs.writeFileSync(objectPath, obj.compressedContent);
});
console.log("Packfile unpacking complete.");
});
packfileStream.on('error', (err) => {
console.error("Error reading packfile:", err);
});
}
function parsePackfile(data) {
const objects = [];
let offset = 0;
console.log("Packfile initial bytes:", data.slice(0, 8));
// Validate the packfile
if (data.slice(offset, offset + 4).toString() !== "PACK") {
throw new Error("Invalid packfile format");
}
offset += 4;
// Read version and object count
const version = data.readUInt32BE(offset);
offset += 4;
if (version !== 2) {
throw new Error(`Unsupported packfile version: ${version}`);
}
const objectCount = data.readUInt32BE(offset);
offset += 4;
console.log(`Packfile contains ${objectCount} objects.`);
for (let i = 0; i < objectCount; i++) {
const { type, size, headerSize } = parseObjectHeader(data, offset);
offset += headerSize;
// Extract and decompress data
const compressedData = extractCompressedData(data, offset);
offset += compressedData.length;
const decompressedData = zlib.inflateSync(compressedData);
objects.push({
type,
size,
content: decompressedData,
compressedContent: compressedData,
});
}
return objects;
}
function parseObjectHeader(data, offset) {
let byte = data[offset];
let size = byte & 0x0f; // Lower 4 bits are part of the size
let type = (byte >> 4) & 0x07; // Next 3 bits are the type
let shift = 4;
let headerSize = 1;
while (byte & 0x80) { // Continuation bit
byte = data[offset + headerSize];
size |= (byte & 0x7f) << shift;
shift += 7;
headerSize++;
}
return { type, size, headerSize };
}
function extractCompressedData(data, offset) {
const compressedData = [];
let i = offset;
while (i < data.length) {
compressedData.push(data[i]);
i++;
// Break if end of compressed data is found
// This is typically handled by zlib inflate
}
return Buffer.from(compressedData);
}
function computeObjectHash(type, content) {
const header = `${type} ${content.length}\0`;
const hash = crypto.createHash('sha1');
hash.update(header);
hash.update(content);
return hash.digest('hex');
}