Skip to content

Latest commit

 

History

History
110 lines (94 loc) · 6.29 KB

File metadata and controls

110 lines (94 loc) · 6.29 KB

🛠️ GIT-LITE SPRINT TODO (TODO.md)

Noona's orders: Golu, this is where we build a Git-lite version to learn how Git actually works under the hood. No dependencies, no fancy libraries. Pure Node.js built-ins (fs, path, crypto, zlib).

Follow this zero-decision-fatigue daily sub-task checklist to implement Git-Lite.


📅 Day 1: Initialize Repo & Hash Object (2026-06-05)

📂 Sub-Task 1: git-lite init

  • Create Directory Structures:
    • Verify if .git-lite already exists. If yes, print that it's already initialized.
    • If not, create .git-lite/ directory.
    • Create .git-lite/objects/ (for storing compressed blobs, trees, commits).
    • Create .git-lite/refs/heads/ (for holding branch pointer files).
  • Write Default HEAD File:
    • Write a file .git-lite/HEAD containing: ref: refs/heads/main\n.
  • Output Logging:
    • Print confirmation: Initialized empty Git-Lite repository in <absolute_path_to_.git-lite>.

🧮 Sub-Task 2: git-lite hash-object [-w] <file>

  • File Reading:
    • Read input file as raw binary/Buffer using fs.readFileSync(filePath).
  • Blob Header Serialization:
    • Format: blob <size-in-bytes>\0<file-contents>.
    • Remember, the separator is a strict null byte (\0 / 0x00). Concatenate buffers properly so binary files don't break.
  • SHA-1 Calculation:
    • Hash the serialized buffer using Node's crypto.createHash('sha1').update(buffer).digest('hex').
    • Print the full 40-character hexadecimal SHA-1 string to stdout.
  • Object Writing (-w flag):
    • If -w is active:
      • Get the first 2 characters of the hash as the folder name (e.g., 2a).
      • Get the remaining 38 characters as the filename (e.g., 3b4c5d...).
      • Construct the path: .git-lite/objects/2a/3b4c5d....
      • Create the sub-folder if it doesn't exist.
      • Compress the entire serialized buffer using zlib.deflateSync(serializedBuffer).
      • Write the compressed bytes to the file path.

📅 Day 2: CAS Read Path & Merkle Tree DFS Serialization (2026-06-06)

📖 Sub-Task 1: git-lite cat-file -p <hash>

  • Hash Parsing & File Path construction:
    • Retrieve the 40-character hex hash from arguments.
    • Split it into directory (first 2 chars) and filename (remaining 38 chars).
    • Construct the path: .git-lite/objects/<dir>/<filename>.
    • Handle file-not-found error gracefully (print error message and exit with 1).
  • Decompression:
    • Read the compressed object file using fs.readFileSync.
    • Decompress using zlib.inflateSync(compressedBuffer).
  • Header Parsing & Output Payload:
    • Find the index of the null byte (\0 / 0x00) in the decompressed buffer.
    • Parse the header (e.g., blob <size>) and print contents after the null byte to stdout.
    • If -p is specified, write the raw content buffer to process.stdout.write(contentBuffer).

🌳 Sub-Task 2: git-lite write-tree

  • Directory Traversal (DFS):
    • Implement a recursive depth-first search (DFS) traversal function of the workspace.
    • Ignore .git-lite, node_modules, .git, dist, and any build directories to keep the tree clean.
  • Entry Sorting & Serialization:
    • For each directory, list all entries sorted alphabetically (standard Git sorts entries by name).
    • For each entry:
      • If it's a file: read its contents, construct the blob object (header + contents), hash it, compress it, and write it to .git-lite/objects/ (similar to hash-object -w). Store its mode as 100644.
      • If it's a directory: recursively run write-tree on it to write the sub-tree and get its 40-character hex hash. Store its mode as 40000.
  • Tree Object Formatting:
    • A tree object contains a list of directory entries. Format each entry as:
      • <mode> <name>\0<binary-sha1> (Note: <binary-sha1> is the 20-byte binary representation of the 40-character hex hash, i.e., Buffer.from(hash, 'hex')).
    • Concatenate all serialized entries into a single Buffer.
    • Prepend the tree header: tree <size-in-bytes>\0.
  • Write Tree & Return Hash:
    • Calculate the SHA-1 hash of the full tree buffer.
    • Compress using zlib.deflateSync and write to .git-lite/objects/ under the appropriate sub-folder.
    • Print the 40-character hex hash of the tree to stdout.

📅 Day 3: Commit Object & Ref Updates (2026-06-13)

💾 Sub-Task 1: git-lite commit -m "<message>"

  • Write Tree State:
    • Run writeTree() to generate and retrieve the current root workspace tree-hash.
  • Resolve Parent Hash:
    • Read .git-lite/HEAD to find the current active branch ref path (e.g. refs/heads/main).
    • Read the resolved ref file (e.g. .git-lite/refs/heads/main). If it exists, read the 40-character commit hash within it as the parent-hash. If the file doesn't exist, skip the parent property.
  • Serialize Commit Object:
    • Format the commit content as:
      tree <tree-hash>
      parent <parent-hash> (if exists)
      author Golu <golu@example.com> <timestamp-in-seconds> +0530
      committer Golu <golu@example.com> <timestamp-in-seconds> +0530
      
      <commit-message>
      
    • Prepend the header: commit <size-in-bytes>\0.
  • Write Commit to CAS:
    • Compute SHA-1, compress it using zlib.deflateSync, and write to .git-lite/objects/ under the calculated SHA-1 path.
  • Update Branch Pointer:
    • Write the newly generated commit hash to the resolved branch ref file (e.g. .git-lite/refs/heads/main) ending with a newline.
    • Print the new commit hash to stdout.

👩‍✈️ Sia's Warning & Tips:

  1. Null Byte Trap: Don't just concatenate strings. If Golu passes a binary file (like a png or zip), string concatenation will corrupt it. Use Buffer.concat([Buffer.from("blob <size>\0"), fileBuffer]).
  2. ESM Import Path: Since we configured "type": "module", remember to use relative file imports with .js extensions (e.g., import { init } from './commands/init.js').
  3. Binary SHA-1 in Trees: In standard Git tree files, the SHA-1 is serialized as a raw 20-byte binary buffer, not its 40-character hex string. Use Buffer.from(hash, 'hex') to convert the hex string before writing it to the tree entry.
  4. Execution Mode: Let's keep the codebase modular. Define functions clearly and write robust DFS traversals. Keep testing as you code!