Challenge:git Stage #FE4 - Git object miss match

I’m stuck on Stage #FE4.

I’ve tried to refactor how I create the object in the createTree() method. however, no matter what I try it keeps telling me that the git objects don’t match. I do see that the Tree may be the issue. I just can’t figure out why they could be different.

On a sidenote when I run this locally and the run the ls-tree on the object it created it the output is the correct tree structure.

Here are my logs:

remote: [tester::#FE4] Running tests for Stage #FE4 (Write a tree object)
remote: [tester::#FE4] $ ./your_program.sh init
remote: [your_program] Initialized git directory
remote: [tester::#FE4] Creating some files & directories
remote: [tester::#FE4] $ ./your_program.sh write-tree
remote: [your_program] dcb4a85d839bbac0681f9d78de7806a5ba0f3b58
remote: [tester::#FE4] Reading file at .git/objects/dc/b4a85d839bbac0681f9d78de7806a5ba0f3b58
remote: [tester::#FE4] Found git object file written at .git/objects/dc/b4a85d839bbac0681f9d78de7806a5ba0f3b58.
remote: [tester::#FE4] Git object file doesn't match official Git implementation. Diff after zlib decompression:
remote: [tester::#FE4]
remote: [tester::#FE4] Expected (bytes 0-100), hexadecimal:                        | ASCII:
remote: [tester::#FE4] 74 72 65 65 20 31 30 31 00 31 30 30 36 34 34 20 61 70 70 6c | tree 101.100644 appl
remote: [tester::#FE4] 65 00 f6 5b 24 36 3c f5 76 ef ae 87 58 a5 df f7 aa 9a cb df | e..[$6<.v...X.......
remote: [tester::#FE4] 51 f8 34 30 30 30 30 20 6d 61 6e 67 6f 00 c7 8a 95 55 9e 53 | Q.40000 mango....U.S
remote: [tester::#FE4] 2c 11 b9 71 f4 1d 7f d7 27 6a e5 3c 8f 54 34 30 30 30 30 20 | ,..q....'j.<.T40000
remote: [tester::#FE4] 72 61 73 70 62 65 72 72 79 00 94 6f bc ff ab 03 58 a8 11 57 | raspberry..o....X..W
remote: [tester::#FE4]
remote: [tester::#FE4] Actual (bytes 0-100), hexadecimal:                          | ASCII:
remote: [tester::#FE4] 74 72 65 65 20 33 00 30 34 30 30 30 30 20 6d 61 6e 67 6f 00 | tree 3.040000 mango.
remote: [tester::#FE4] 4e 58 07 ef bf bd 64 ef bf bd ef bf bd 13 2a 42 ef bf bd ef | NX....d.......*B....
remote: [tester::#FE4] bf bd 21 ef bf bd c8 92 ef bf bd ef bf bd 58 2c 30 34 30 30 | ..!...........X,0400
remote: [tester::#FE4] 30 30 20 72 61 73 70 62 65 72 72 79 00 49 ef bf bd 4b 70 5f | 00 raspberry.I...Kp_
remote: [tester::#FE4] 03 ef bf bd 2e ef bf bd ef bf bd 56 ef bf bd ef bf bd 76 ef | ...........V......v.
remote: [tester::#FE4]
remote: [tester::#FE4] Git object file doesn't match official Git implementation
remote: [tester::#FE4] Test failed

And here’s a snippet of my code:

const fs = require('fs');
const zlib = require('zlib');
const crypto = require('crypto');
const path = require('path');

// Helper to compute SHA-1 hash
function computeSha1(data) {
    return crypto.createHash('sha1').update(data).digest('hex');
}

// Helper to write object to the .git/objects directory
function writeObject(sha, content) {
    const dir = path.join('.git', 'objects', sha.slice(0, 2));
    
    const file = sha.slice(2);
    
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir);
    }
    fs.writeFileSync(path.join(dir, file), content);
}

// Create a blob object for a file and return its SHA-1 hash
function createBlob(filePath) {
    const content = fs.readFileSync(filePath, 'utf-8');
    const blobData = Buffer.from(`blob ${content.length}\0${content}`);
    // const blobData = Buffer.concat([blobHeader, content]);

    const sha = computeSha1(blobData);
    const compressedBlob = zlib.deflateSync(blobData);

    writeObject(sha, compressedBlob);
    return sha;
}

// Recursively create tree objects for directories
function createTree(dirPath) {
    const entries = fs.readdirSync(dirPath).filter(e => e !== '.git');
    // console.log(entries)
    let treeEntries = [];

    entries.forEach(entry => {
        const fullPath = path.join(dirPath, entry);
        const stat = fs.statSync(fullPath);
        // console.log(`${fullPath} - ${stat.isFile()}`)

        let mode, sha;
        if (stat.isFile()) {
            mode = '100644'; // Regular file mode
            sha = createBlob(fullPath);
        } else if (stat.isDirectory()) {
            mode = '040000'; // Directory mode
            sha = createTree(fullPath); // Recursive call for subdirectories
        }

        const buff = Buffer.from(sha, 'hex')
        const entryData = `${mode} ${entry}\0${buff}`;
        treeEntries.push(Buffer.from(entryData));
    });

    // Sort tree entries alphabetically by name
    treeEntries.sort((a, b) => a.toString().localeCompare(b.toString()));

    // Create tree object
    const treeHeader = `tree ${treeEntries.length}\0`;
    const treeData = Buffer.from(`${treeHeader}${[...treeEntries]}`);
    // console.log(treeData)
    
    const treeSha = computeSha1(treeData);
    const compressedTree = zlib.deflateSync(treeData);
    
    writeObject(treeSha, compressedTree);
    return treeSha;
}

// Main function for the write-tree command
function writeTree() {
    const treeSha = createTree(process.cwd());
    console.log(treeSha); // Print the tree SHA-1 hash
}

module.exports = writeTree;

Hey @Gensune, there might be a few issues at play. Here are the first two I noticed:

  • Directory mode: Tree objects store the mode for directories as 40000, even though Git pretty-prints it as 040000:

You can confirm it like this:

  • Entry ordering: Entries in a tree object should be sorted alphabetically.

Let me know if you’d like any further clarification!

Thank you for the response, I didn’t realize that I had a leading 0 for the dir mode.

Now biggest issue that I can see is that the size of the tree is not matching. While doing some debugging as to the possible cause of this. I found that inside my createTree() function, I add this line console.log(`This is the tree header ${treeHeader}`) to see what is actually being written to treeHeader and found that I’m getting an output for each element inside the treeEntries rather then giving me the size of the entire array.

The same thing happens when I insert this console.log(treeData). It thinks it’s in for loop when it’s not. I’m at a loss to what is going on.

Also, when I pass the sha-1 that it outputs to ls-tree it outputs the correct information.

1 Like

@Gensune You’re making great progress!

I took another look and noticed a few more things to tweak:

  1. sha is converted to Buffer twice, while once is enough.

  2. treeEntires is essentially sorted by mode, while it should be sorted by name.

  3. Note that treeEntires is an array, so using its array length is incorrect. You’ll need the total byte length of the concatenated entries instead.

Let me know if you’d like any further clarification!