Error in cloning a git repository

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');
}

This is a tricky stage to debug. We’re planning on splitting this down into multiple stages at some point, hopefully that’ll make this more tractable.

@SREERAJ089

here const requestPayload = '0032want ${branchSha}00000009done\n'; You need a space \x0A after the sha1 so it should be somethign like this 0032want ${branchSha} \n 00000009done\n

[What does the Git Smart HTTP(S) protocol fully look like in all its glory? - Stack Overflow](See this on stackoverflow.)

1 Like

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!

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