Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mini Git

A Git-inspired version control system built from scratch in C++20.

Mini Git is an educational systems-programming project designed to explore how modern version control systems work internally.

Rather than simply using Git's commands or libraries, the project progressively implements the fundamental mechanisms behind a version control system, including hashing, content-addressable storage, objects, trees, commits, staging, repository state, references, HEAD, and history.

Note: Mini Git is an educational project inspired by Git. It is not intended to replace Git and does not aim for full Git compatibility.


Goals

The primary goals of Mini Git are to understand and implement the core concepts behind modern version control systems:

  • Content-addressable storage
  • Cryptographic hashing
  • Object databases
  • Blob objects
  • Tree objects
  • Commit objects
  • Staging areas
  • Working Tree state
  • Repository state
  • References
  • HEAD
  • Branches
  • Commit history
  • Diffs
  • Merging
  • Repository integrity
  • Basic version-control workflows

The project also emphasizes professional C++ development practices:

  • C++20
  • CMake
  • Modular architecture
  • Separation of concerns
  • RAII
  • Const correctness
  • Error handling
  • Automated testing
  • Documentation
  • Git-based development workflow
  • Integration testing

Current Status

Mini Git is being developed incrementally through multiple implementation phases.

Implemented

Project Foundation

  • C++20 project structure
  • CMake build system
  • Command-line executable
  • Git-based development workflow
  • Documentation structure
  • Automated testing through CTest

Repository

  • mini-git --version
  • mini-git init
  • Repository initialization
  • .mini-git/ metadata directory
  • objects/ directory
  • refs/heads/ directory
  • Initial HEAD reference
  • HEAD pointing to main
  • Basic Repository abstraction
  • Repository path access through Repository

Hashing

  • SHA-256 hashing
  • OpenSSL integration
  • OpenSSL EVP-based hashing
  • Deterministic hash generation
  • Hexadecimal hash representation
  • Binary-data hashing support
  • Known SHA-256 test vectors

Object Model

  • Common Object abstraction
  • Blob objects
  • Tree objects
  • Commit objects
  • Object serialization
  • Tree entries
  • Commit parent relationships
  • Initial commits without parents

File Reading and Blob Pipeline

  • Binary-safe file reading
  • FileReader abstraction
  • Reading arbitrary file contents
  • Blob::from_file()
  • File contents represented as Blob objects
  • Blob serialization with object type and content size
  • SHA-256 object identifiers for serialized Blobs
  • File → Blob → Object ID workflow

The current Blob representation is:

blob <size>\0<content>

The serialized Blob is hashed with SHA-256 to produce its object identifier.

The complete file-to-storage pipeline is:

File
 │
 ▼
FileReader
 │
 ▼
Blob
 │
 ▼
serialize()
 │
 ▼
SHA-256
 │
 ▼
Object ID
 │
 ▼
Object Database
 │
 ▼
.mini-git/objects/<object-id>

Object Database

  • ObjectDatabase abstraction
  • Persistent object storage
  • Object identifiers used as storage keys
  • Binary-safe object writing
  • Binary-safe object reading
  • Object existence checking
  • Duplicate-object detection
  • Object retrieval by object ID
  • Persistent storage under .mini-git/objects/
  • mini-git hash-object <file>

The object database stores serialized objects using their SHA-256 object identifiers.

The current storage model is:

.mini-git/

└── objects/

    └── <object-id>

Identical serialized objects produce the same object identifier.

If an object with that identifier already exists, Mini Git reuses the existing object instead of writing another copy.

Trees

  • Tree object representation
  • Tree entries for files and directories
  • Deterministic Tree serialization
  • Recursive directory traversal
  • File-to-Blob conversion during Tree construction
  • Directory-to-Tree conversion
  • Nested Tree support
  • Empty directory support
  • .mini-git exclusion
  • Persistent Tree storage through the Object Database
  • Automated TreeBuilder tests

A directory such as:

project/

├── README.md
├── main.cpp
├── src/
│   ├── App.cpp
│   └── Utils.cpp
└── empty/

can be represented as:

Root Tree

├── README.md → Blob
├── main.cpp  → Blob
├── src       → Tree
│   ├── App.cpp   → Blob
│   └── Utils.cpp → Blob
└── empty     → Empty Tree

TreeBuilder recursively constructs this object hierarchy and stores the resulting Trees and Blobs in the Object Database.

Mini Git's own .mini-git/ directory is excluded from generated working-tree representations.

Index / Staging Area

  • Index abstraction
  • IndexEntry representation
  • Repository-relative staged file paths
  • Staged file → Blob object ID mapping
  • Adding new staged entries
  • Updating existing staged entries
  • Duplicate path prevention
  • Checking whether a path is staged
  • Accessing staged entries
  • Persistent Index storage
  • Index serialization
  • Index loading
  • .mini-git/index
  • mini-git add <file>

The Index represents the staged state between the Working Tree and the future commit system.

The current simplified Index representation is:

path<TAB>object-id

For example:

main.cpp    abc123...
README.md   def456...
src/App.cpp ghi789...

The staging workflow is:

Working Tree

     │

     │ mini-git add <file>

     ▼

 FileReader

     │

     ▼

   Blob

     │

     ▼

Object Database

     │

     ▼

 Object ID

     │

     ▼

   Index

     │

     ▼

.mini-git/index

The Index does not store file contents themselves.

It stores references to immutable Blob objects in the Object Database.

Status

Phase 9 introduced repository state inspection:

mini-git status

The implementation compares the Index against the Working Tree.

It can identify:

  • Modified tracked files
  • Deleted tracked files
  • Untracked files
  • Nested untracked files
  • Clean Working Tree state
  • Internal .mini-git/ files that must not appear as untracked files

The current status model is:

              Repository

                   │

                   ▼

                 Index

                   │

                   │ compare

                   ▼

              Working Tree

                   │

        ┌──────────┼──────────┐

        ▼          ▼          ▼

    Modified    Deleted    Untracked

For a tracked file:

Index object ID

       │

       │ compare

       ▼

Current file contents

       │

       ▼

Blob serialization

       │

       ▼

SHA-256

       │

       ▼

Current object ID

If the current object ID differs from the Index entry, the file is reported as modified.

If the file no longer exists, it is reported as deleted.

Files present in the Working Tree but absent from the Index are reported as untracked.

The .mini-git/ directory is excluded from recursive untracked-file detection.

Commits

Phase 10 connects the existing Index, Tree, Commit, and Object Database components into the first complete repository snapshot workflow.

The implemented command is:

mini-git commit -m "message"

The commit pipeline is:

Working Tree

      │

      │ mini-git add

      ▼

    Index

      │

      │ mini-git commit

      ▼

 TreeBuilder

      │

      ▼

     Tree

      │

      ▼

    Commit

      │

      ▼

Object Database

      │

      ▼

 Commit Object ID

The Phase 10 implementation establishes the connection between staged files and repository-level commits.

A commit is created from the currently staged state.

For example:

mini-git add main.cpp

mini-git commit -m "Update main file"

The commit process creates and stores the corresponding Tree and Commit objects.

A commit represents a snapshot of the staged repository state together with commit metadata and its parent relationship.

The initial commit has no parent.

Subsequent commits can reference the previous commit as their parent.

Conceptually:

Commit C
   │
   │ parent
   ▼
Commit B
   │
   │ parent
   ▼
Commit A

The resulting structure forms the foundation of Mini Git's commit history.

Phase 10 also establishes the basis for future HEAD-aware repository state and history inspection.

Commit Testing

The commit workflow has been manually tested using isolated test repositories.

Example workflow:

mini-git add main.cpp

mini-git commit -m "Update main file"

and:

printf 'A\n' > a.txt

mini-git add a.txt

mini-git commit -m "Add a"

These tests verify that staged files can progress through:

Working Tree
      ↓
Index
      ↓
Tree
      ↓
Commit
      ↓
Object Database

Testing

The current test suite covers:

  • Hash unit tests
  • Object serialization tests
  • FileReader tests
  • Blob integration tests
  • Object database tests
  • TreeBuilder tests
  • Index tests
  • Status tests
  • Known SHA-256 test vectors
  • Determinism tests
  • Different-input tests
  • Binary-data tests
  • Missing-file error tests
  • Object storage tests
  • Object retrieval tests
  • Object existence tests
  • Duplicate-object tests
  • Tree serialization tests
  • Deterministic Tree ordering tests
  • Recursive Tree construction tests
  • Empty-directory tests
  • .mini-git exclusion tests
  • Index insertion tests
  • Index update tests
  • Multiple Index entries
  • Index persistence tests
  • Index update persistence tests
  • Clean Working Tree tests
  • Modified-file tests
  • Deleted-file tests
  • Untracked-file tests
  • Nested untracked-file tests
  • .mini-git status exclusion tests
  • CTest integration

Run the complete test suite with:

ctest --test-dir build --output-on-failure

Not Yet Implemented

The following major subsystems are planned for future phases:

  • Repository discovery
  • mini-git add .
  • File deletion staging
  • Full HEAD-aware mini-git status
  • Staged-vs-HEAD status
  • Complete reference management
  • mini-git log
  • Branch management
  • Checkout
  • Diff
  • Merge
  • Conflict handling
  • Tags
  • Repository integrity checking
  • Garbage collection concepts
  • Performance benchmarking
  • Extensive repository-level integration testing
  • Advanced index formats
  • Full Git-compatible object formats

Educational Features

Mini Git is designed to go beyond simply copying Git commands.

A major goal of the project is to make the internal mechanisms of version control understandable and observable.

Planned educational commands include:

mini-git inspect

mini-git graph

mini-git explain

mini-git stats

mini-git fsck

These commands are intended to expose the internal architecture of Mini Git.

For example:

mini-git explain add main.cpp

could eventually explain the internal process:

Working Tree

     │

     ▼

Read file

     │

     ▼

Create Blob

     │

     ▼

Calculate SHA-256

     │

     ▼

Store Object

     │

     ▼

Update Index

Similarly:

mini-git explain commit -m "message"

could eventually show:

Index

  │

  ▼

Build Tree

  │

  ▼

Store Tree

  │

  ▼

Create Commit

  │

  ▼

Store Commit

  │

  ▼

Update Reference

This educational layer is an important part of the project's purpose.

The goal is not only to reproduce commands, but to expose the systems concepts underneath them.


Architecture

Mini Git is being developed as a layered version-control system.

The current architecture is:

                         mini-git CLI

                              │

                              ▼

                       Command Layer

                              │

             ┌────────────────┼────────────────┐
             ▼                ▼                ▼
        Working Tree        Index          Repository
             │                │                │
             ▼                │                ▼
        FileReader            │         Object Database
             │                │                │
             ▼                │                ▼
           Blob               │             Objects
             │                │          ┌────┼────┐
             │                │          ▼    ▼    ▼
             └────────────────┴──────── Blob Tree Commit

The intended complete version-control workflow is:

Working Tree

      │

      │ add

      ▼

    Index

      │

      │ commit

      ▼

    Tree

      │

      ▼

   Commit

      │

      ▼

  Reference

      │

      ▼

    HEAD

The currently implemented commit pipeline reaches:

Working Tree

      │

      │ add

      ▼

    Index

      │

      │ commit

      ▼

 TreeBuilder

      │

      ▼

     Tree

      │

      ▼

   Commit

      │

      ▼

Object Database

Reference and HEAD integration will continue to be developed in later phases.


Core Concepts

Working Tree

The Working Tree is the collection of files and directories currently present on disk.

Working Tree

├── main.cpp
├── README.md
└── src/
    └── App.cpp

It represents the current state of the project files.

The Working Tree is the source of file contents when files are staged or inspected by status.


Index

The Index is the staging area between the Working Tree and the repository.

The workflow is:

Working Tree

      │

      │ mini-git add

      ▼

    Index

      │

      │ commit

      ▼

     Tree

      │

      ▼

    Commit

Mini Git's Index stores simplified entries:

path → Blob object ID

For example:

main.cpp → abc123...

README.md → def456...

The Index is persisted at:

.mini-git/index

The Index is mutable.

Re-staging the same path replaces its staged object ID rather than creating a duplicate Index entry.


Status

The status subsystem inspects the relationship between the Index and the current Working Tree.

Currently:

Index

  │

  │ compare

  ▼

Working Tree

Modified Files

A tracked file is modified when its current contents produce a different Blob object ID from the ID stored in the Index.

Indexed Blob ID

      │

      │ !=

      ▼

Current Blob ID

Deleted Files

A tracked file is deleted when it exists in the Index but no longer exists in the Working Tree.

Untracked Files

A file is untracked when it exists in the Working Tree but has no corresponding Index entry.

The status scanner recursively searches directories so nested untracked files can also be detected.

The .mini-git/ directory is excluded from this scan because repository metadata is not part of the Working Tree.

Current Limitation

Full Git-like status requires three states:

HEAD

 │

 ▼

Index

 │

 ▼

Working Tree

The current status subsystem primarily compares:

Index ↔ Working Tree

HEAD-aware staged and unstaged comparisons will be developed as reference and history functionality is completed.


Objects

Mini Git represents repository data using three primary object types:

Object

├── Blob
├── Tree
└── Commit

Blob

A Blob represents file contents.

File

 │

 ▼

FileReader

 │

 ▼

File Contents

 │

 ▼

Blob

A Blob does not need to know the filename associated with its contents.

Identical file contents can therefore correspond to the same object.

Mini Git currently serializes a Blob as:

blob <size>\0<content>

The serialized representation is subsequently hashed to produce the object's identifier.


Tree

A Tree represents directory structure.

Tree

├── main.cpp  → Blob
├── README.md → Blob
└── src/      → Tree
                  │
                  └── App.cpp → Blob

Trees connect filenames and directory structure to object identifiers.

Trees can recursively contain other Trees, allowing complete directory hierarchies to be represented.


Commit

A Commit represents a repository snapshot together with metadata and history.

A simplified commit contains:

Commit

├── tree
├── parent
├── author
└── message

Commits can reference previous commits:

Commit C

   │

   ▼

Commit B

   │

   ▼

Commit A

This forms the foundation of the commit history graph.

Phase 10 connects these Commit objects to the Index and Tree-building pipeline.


Commit Pipeline

The Phase 10 commit architecture is:

                 Working Tree
                       │
                       │
                       ▼
                     Index
                       │
                       │ commit
                       ▼
                  TreeBuilder
                       │
                       ▼
                      Tree
                       │
                       ▼
                    Commit
                       │
                       ▼
                Object Database

The Index represents the desired snapshot.

TreeBuilder converts the staged state into a Tree representation.

The Tree becomes the root snapshot referenced by the Commit.

The Commit then contains metadata such as:

  • Tree object ID
  • Parent commit information
  • Author information
  • Commit message

The resulting Commit is stored in the Object Database.


File Reading

Mini Git separates filesystem operations from object representation.

FileReader is responsible for reading the exact bytes of a file.

Files are opened in binary mode so that arbitrary binary data can be represented without text-mode transformations.

Conceptually:

Filesystem

    │

    ▼

FileReader

    │

    ▼

Raw File Contents

    │

    ▼

Blob

This separation allows the Blob object to remain focused on representing file contents rather than performing filesystem operations itself.


Trees and Directory Representation

Trees provide the connection between individual objects and complete directory structures.

The current workflow is:

Directory

    │

    ▼

TreeBuilder

    │

    ├── File
    │    │
    │    ▼
    │  FileReader
    │    │
    │    ▼
    │  Blob
    │
    └── Directory
         │
         ▼
       TreeBuilder
         │
         ▼
        Tree

For example:

project/

├── main.cpp
├── README.md
└── src/

    ├── App.cpp
    └── Utils.cpp

is represented conceptually as:

Root Tree

├── main.cpp  → Blob ID
├── README.md → Blob ID
└── src       → Tree ID
                  ├── App.cpp   → Blob ID
                  └── Utils.cpp → Blob ID

Tree serialization is deterministic.

Entries are sorted by name before serialization so that equivalent directory contents produce the same serialized Tree regardless of filesystem traversal order.

Empty directories are represented by empty Tree objects in Mini Git.

This is an educational simplification; real Git does not normally track empty directories as independent repository objects.


Hashing

Mini Git uses SHA-256 to generate deterministic object identifiers.

The hashing layer is isolated behind the Hash abstraction.

The current hashing pipeline is:

Data

 │

 ▼

Hash::sha256()

 │

 ▼

OpenSSL EVP

 │

 ▼

SHA-256

 │

 ▼

64-character hexadecimal string

SHA-256 produces:

256 bits

   ↓

32 bytes

   ↓

64 hexadecimal characters

For example:

2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

The resulting hash can serve as an object identifier.


Content-Addressable Storage

Mini Git is designed around content-addressable storage.

The basic concept is:

Object Content

      │

      ▼

   SHA-256

      │

      ▼

  Object ID

      │

      ▼

Object Database

The same serialized content produces the same object identifier.

Therefore:

same content

     ↓

same serialization

     ↓

same SHA-256

     ↓

same object ID

Mini Git uses the object ID as the key for persistent object storage:

Object Content

      │

      ▼

   SHA-256

      │

      ▼

  Object ID

      │

      ▼

.mini-git/objects/<object-id>

This provides the foundation for:

  • Object identity
  • Persistence
  • Deduplication
  • Immutable object storage

Object Database

The Object Database is responsible for persistent storage and retrieval of serialized repository objects.

Its responsibilities include:

  • Storing serialized objects
  • Generating object identifiers
  • Checking whether objects already exist
  • Reading objects by object identifier
  • Avoiding duplicate object storage

The current implementation stores each object as a file named after its object ID:

.mini-git/

└── objects/

    ├── <object-id-1>
    ├── <object-id-2>
    └── <object-id-3>

The Object Database is intentionally separated from the Repository abstraction.

Repository manages repository-level information such as the location of .mini-git.

ObjectDatabase manages the contents of .mini-git/objects.

This separation allows object storage to be tested independently.


Index / Staging Area

The Index is the bridge between the Working Tree and commits.

Its simplified representation is:

IndexEntry

├── path
└── object_id

For example:

main.cpp → abc123...

README.md → def456...

src/App.cpp → ghi789...

When a file is staged:

mini-git add main.cpp

Mini Git performs:

main.cpp

    │

    ▼

FileReader

    │

    ▼

Blob

    │

    ▼

ObjectDatabase

    │

    ▼

Blob ID

    │

    ▼

Index

    │

    ▼

.mini-git/index

The Index is stored at:

.mini-git/index

The current educational format is:

path<TAB>object-id

For example:

main.cpp    abc123...

README.md   def456...

The Index can be saved and loaded so that staged state survives program termination.

The Index does not duplicate Blob contents.

It references objects already stored in the Object Database.


Repository Structure

A Mini Git repository contains a hidden .mini-git directory:

project/

├── .mini-git/

│   ├── objects/

│   ├── refs/

│   │   └── heads/

│   ├── HEAD
│   └── index
│
├── source files...
└── other project files...

The .mini-git directory contains repository metadata and internal version-control information.

Project files remain in the Working Tree.

The index file is created when staging information is first persisted.


Repository Initialization

A repository can currently be initialized with:

mini-git init

This creates:

.mini-git/

├── objects/

├── refs/

│   └── heads/

└── HEAD

The initial HEAD contains:

ref: refs/heads/main

This means that HEAD symbolically refers to the main branch.

The repository can subsequently receive staged files and commits.

The objects/ directory is initially empty.

Objects are added when commands such as hash-object, add, and commit store serialized objects in the repository.

The Index is created separately when staging information is saved.


Manual Blob Testing

The implementation includes a hash-file command for inspecting the file-to-Blob-to-hash pipeline:

./build/mini-git hash-file hello.txt

For example:

printf 'Hello Mini Git!' > hello.txt

./build/mini-git hash-file hello.txt

Running the command multiple times without changing the file should produce the same object identifier.

Changing the file contents should produce a different identifier.

The hash-file command is retained as an educational/debugging interface.

It calculates the object identifier without relying on persistent object storage.


Hash an Object

Mini Git can create a Blob from a real file, calculate its object identifier, and persist the serialized object in the repository's Object Database:

./build/mini-git hash-object hello.txt

The command performs:

File

 │

 ▼

Blob::from_file()

 │

 ▼

Blob

 │

 ▼

ObjectDatabase::store()

 │

 ▼

SHA-256

 │

 ▼

Object ID

 │

 ▼

.mini-git/objects/<object-id>

Running the command again without changing the file produces the same object identifier.

Changing the file contents produces a different identifier while preserving the previously stored object.


Stage a File

Mini Git's staging command is:

./build/mini-git add hello.txt

The command performs:

hello.txt

    │

    ▼

FileReader

    │

    ▼

Blob

    │

    ▼

ObjectDatabase

    │

    ▼

Blob ID

    │

    ▼

Index

    │

    ▼

.mini-git/index

The Index contains the path and corresponding Blob object ID.

For example:

hello.txt    7c8f...

If the same file is staged again after being modified, its existing Index entry is updated rather than duplicated.

The Object Database may contain both the old and new Blob objects because objects are immutable and content-addressed.


Check Repository Status

Mini Git can inspect the relationship between staged files and the Working Tree:

./build/mini-git status

For example:

./build/mini-git add main.cpp

./build/mini-git status

The Working Tree should initially be clean:

On branch main

Working tree clean.

If the file is modified afterward:

On branch main

Changes not staged for commit:

  modified: main.cpp

If a tracked file is deleted:

On branch main

Deleted files:

  deleted: main.cpp

If a new file is created without staging it:

On branch main

Untracked files:

  notes.txt

Nested untracked files are also detected:

Untracked files:

  src/notes.txt

The internal .mini-git/ directory is ignored by the untracked-file scanner.


Create a Commit

Mini Git's commit command is:

./build/mini-git commit -m "Initial commit"

A typical workflow is:

./build/mini-git add main.cpp

./build/mini-git commit -m "Add main file"

The commit pipeline is:

Working Tree

      │

      │ add

      ▼

    Index

      │

      │ commit

      ▼

 TreeBuilder

      │

      ▼

     Tree

      │

      ▼

    Commit

      │

      ▼

Object Database

The staged Index represents the snapshot that should be committed.

TreeBuilder constructs the Tree representation.

The Tree object is stored in the Object Database.

A Commit object is then created referencing the Tree.

The Commit is also stored in the Object Database.

Subsequent commits can reference previous commits as parents.

Conceptually:

Commit C
   │
   │ parent
   ▼
Commit B
   │
   │ parent
   ▼
Commit A

This establishes the foundation for future history traversal.


Commit Workflow Example

A complete basic workflow currently looks like:

mkdir mini-git-test

cd mini-git-test

/path/to/mini-git/build/mini-git init

printf 'Hello\n' > main.cpp

/path/to/mini-git/build/mini-git add main.cpp

/path/to/mini-git/build/mini-git commit -m "Initial commit"

After modifying a file:

printf 'Updated\n' > main.cpp

/path/to/mini-git/build/mini-git add main.cpp

/path/to/mini-git/build/mini-git commit -m "Update main file"

The repository now contains multiple Commit objects connected through parent relationships.


Build

Mini Git uses CMake as its build system.

From the project root:

cmake -S . -B build

cmake --build build

The main executable is generated at:

build/mini-git

macOS / Homebrew OpenSSL

The project uses OpenSSL for SHA-256 hashing.

On systems where CMake does not automatically locate the Homebrew installation, configure the project with:

cmake -S . -B build \
  -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@3

Then build:

cmake --build build

Run

Run Mini Git with:

./build/mini-git

Expected output:

Mini Git

Check Version

./build/mini-git --version

Expected output:

mini-git version 0.1.0

Initialize a Test Repository

Create a separate directory for testing:

mkdir mini-git-test

cd mini-git-test

Then run Mini Git:

/path/to/mini-git/build/mini-git init

The result should be:

mini-git-test/

└── .mini-git/

    ├── objects/

    ├── refs/

    │   └── heads/

    └── HEAD

The HEAD file should contain:

ref: refs/heads/main

Running init again should report that a repository already exists.


Testing

Mini Git uses automated tests to validate individual components and their interactions.

The current test suite includes:

tests/

├── HashTests.cpp

├── ObjectTests.cpp

├── FileReaderTests.cpp

├── BlobTests.cpp

├── TreeBuilderTests.cpp

├── IndexTests.cpp

└── StatusTests.cpp

The hash tests verify:

  • Known SHA-256 values
  • Deterministic hashing
  • Different inputs producing different hashes
  • Binary data handling

The object tests verify:

  • Blob serialization
  • Tree serialization
  • Deterministic Tree ordering
  • Commit serialization
  • Initial commits without parents
  • Object database storage
  • Object existence checking
  • Object retrieval
  • Duplicate-object detection

The FileReader tests verify:

  • Text file reading
  • Binary file reading
  • Preservation of null bytes
  • Missing-file error handling

The Blob tests verify:

  • Creating Blobs from real files
  • Correct Blob serialization
  • Binary-file Blob handling

The TreeBuilder tests verify:

  • Building Trees from directories
  • Converting files into Blobs
  • Recursive nested directory handling
  • Empty directory handling
  • Tree persistence
  • .mini-git exclusion

The Index tests verify:

  • Adding entries
  • Detecting staged paths
  • Updating existing entries
  • Preventing duplicate paths
  • Multiple staged entries
  • Index persistence
  • Loading staged entries
  • Persistence of updated entries

The Status tests verify:

  • Clean Working Tree detection
  • Modified tracked files
  • Deleted tracked files
  • Untracked files
  • Nested untracked files
  • .mini-git exclusion
  • Recursive directory scanning

Repository-level commit tests and broader integration tests will continue to expand as later phases are implemented.

CTest is used to execute the complete automated test suite.

Run:

ctest --test-dir build --output-on-failure

Development Workflow

Mini Git itself is version-controlled using Git.

The development workflow is:

Modify Code

    ↓

Build

    ↓

Run Tests

    ↓

Inspect Behavior

    ↓

Update Documentation

    ↓

Commit Changes

    ↓

Push to GitHub

Git manages the source code of Mini Git.

Mini Git is separately used to experiment with the concepts that Git itself implements.

This creates an interesting development relationship:

Git

 │

 │ manages Mini Git source code

 ▼

Mini Git

 │

 │ experiments with version control concepts

 ▼

Test Repositories

Design Philosophy

Mini Git is intentionally built incrementally.

Instead of immediately implementing a large collection of commands, the project first establishes the internal mechanisms required to support those commands.

The fundamental version-control workflow is:

Working Tree

      │

      │ add

      ▼

    Index

      │

      │ commit

      ▼

     Tree

      │

      ▼

   Commit

      │

      ▼

   Branch

      │

      ▼

    HEAD

The object model underneath this workflow is:

Files

  │

  ▼

Blobs

  │

  ▼

Trees

  │

  ▼

Commits

  │

  ▼

References

  │

  ▼

HEAD

Each subsystem is implemented and tested before more complex functionality is built on top of it.


Why Build Mini Git?

Git is commonly used as a command-line tool without requiring users to understand its internal implementation.

Building a simplified version from scratch provides an opportunity to understand:

  • How files become versioned objects
  • How content-addressable storage works
  • How cryptographic hashes identify data
  • How objects can be reused
  • How directory structures are represented
  • How staging works
  • How the Index references immutable objects
  • How repository status can be determined
  • How commits reference previous commits
  • How branches are represented
  • How HEAD works
  • How commit history forms a graph
  • How version-control operations manipulate repository state
  • How a systems-oriented C++ application can be designed and tested

Mini Git is therefore both a software-engineering project and a systems-programming learning project.


Technology

Language

C++20

Build System

CMake

Cryptography

OpenSSL 3

The project currently uses OpenSSL's EVP interface for SHA-256 hashing.

Development Tools

  • Git
  • GitHub
  • CMake
  • C++ standard library
  • Unix / Linux concepts

Standard Library Features

The project currently uses and plans to use facilities including:

  • std::filesystem
  • std::string
  • std::vector
  • File streams
  • Error handling facilities
  • Other C++ standard-library components as required

Project Structure

The current source tree is:

mini-git/

├── CMakeLists.txt

├── README.md

├── LICENSE

├── .gitignore

│

├── include/

│   ├── Repository.hpp
│   ├── Hash.hpp
│   ├── Object.hpp
│   ├── Blob.hpp
│   ├── Tree.hpp
│   ├── Commit.hpp
│   ├── FileReader.hpp
│   ├── ObjectDatabase.hpp
│   ├── TreeBuilder.hpp
│   ├── Index.hpp
│   └── Status.hpp

│

├── src/

│   ├── main.cpp
│   ├── Repository.cpp
│   ├── Hash.cpp
│   ├── Blob.cpp
│   ├── Tree.cpp
│   ├── Commit.cpp
│   ├── FileReader.cpp
│   ├── ObjectDatabase.cpp
│   ├── TreeBuilder.cpp
│   ├── Index.cpp
│   └── Status.cpp

│

├── tests/

│   ├── HashTests.cpp
│   ├── ObjectTests.cpp
│   ├── FileReaderTests.cpp
│   ├── BlobTests.cpp
│   ├── TreeBuilderTests.cpp
│   ├── IndexTests.cpp
│   └── StatusTests.cpp

│

└── docs/

    └── architecture.md

As the project grows, additional modules and dedicated test files will be introduced when their responsibilities become necessary.


Design Principles

Separation of Concerns

Each component should have a clear responsibility.

For example:

Hash

    → generates object identifiers

Object

    → defines the common object interface

FileReader

    → reads raw file contents

Blob

    → represents file contents

Tree

    → represents directory structure

TreeBuilder

    → converts filesystem directories into Trees and Blobs

Commit

    → represents snapshots and history

ObjectDatabase

    → stores and retrieves objects

Repository

    → manages repository-level state

Index

    → manages staged file state

Status

    → compares repository staging state with the Working Tree

Components should not unnecessarily take responsibility for unrelated operations.

This separation becomes increasingly important as commands such as branch, checkout, diff, and merge are added.


Standard C++

Mini Git primarily uses the C++ standard library.

External dependencies are introduced only when they provide a meaningful advantage.

OpenSSL is currently used for cryptographic hashing rather than implementing SHA-256 manually.


RAII

Resources should be managed through C++ lifetime semantics wherever practical.

This includes:

  • File streams
  • Memory
  • Locks
  • Other resources introduced later

The codebase will be progressively reviewed for safer and clearer resource management.


Const Correctness

Functions that do not modify an object should be marked const where appropriate.

For example:

std::string serialize() const;

This communicates that serialization should not modify the object.


Error Handling

Operations that can fail should detect and report errors clearly.

Examples include:

  • Repository already exists
  • Repository does not exist
  • File cannot be opened
  • Object does not exist
  • Invalid object
  • Invalid reference
  • Invalid command
  • Invalid filesystem path
  • Invalid Index data
  • Invalid commit operation

The CLI should provide useful error messages instead of silently failing.


Limitations

Mini Git is intentionally much smaller than Git.

It does not attempt to reproduce every Git feature.

Full compatibility with real Git is not the primary objective.

The priorities are:

Understanding

     +

Correct Implementation

     +

Clean Architecture

     +

Testing

     +

Documentation

The project uses simplified internal representations where appropriate for educational purposes.

For example, Mini Git currently uses:

  • SHA-256 rather than Git's historical default SHA-1
  • A simplified object serialization format
  • A flat object-storage layout
  • A simplified text-based Index format
  • A simplified Tree representation
  • A simplified Commit representation
  • Simplified repository status semantics

These differences are intentional and will be documented rather than hidden.

The current Index format also has limitations around filenames containing whitespace because the first implementation uses a simple text representation.

A more robust path encoding or binary Index format can be introduced later if needed.

The current add implementation is also still limited to individual files:

mini-git add <file>

Directory-wide staging with:

mini-git add .

has not yet been implemented.

The current status implementation primarily compares the Index against the Working Tree.

Full HEAD-aware status will be introduced as commit references and repository state management mature.


Roadmap

Phase 0 — Git Concepts

Understand the fundamental concepts behind version control and Git.


Phase 1 — Project Setup

Establish the C++20 project, CMake, Git workflow, testing structure, and documentation.


Phase 2 — Repository Initialization

Implement:

mini-git init

Create the initial .mini-git repository structure and HEAD.


Phase 3 — Hashing

Implement deterministic cryptographic hashing using SHA-256 and OpenSSL.


Phase 4 — Object Model

Implement the foundational object types:

  • Blob
  • Tree
  • Commit

and their serialization interfaces.


Phase 5 — Blob Objects

Connect Blobs to real file contents and establish the first complete object-identity workflow.

File

 ↓

FileReader

 ↓

Blob

 ↓

Serialization

 ↓

SHA-256

 ↓

Object ID

Phase 6 — Object Database

Implement persistent object storage and retrieval.

Implemented:

File

 ↓

Blob

 ↓

Serialization

 ↓

SHA-256

 ↓

Object ID

 ↓

Object Database

 ↓

.mini-git/objects/<object-id>

The Object Database supports:

  • Storing objects
  • Reading objects
  • Checking object existence
  • Reusing existing objects with the same object ID

The hash-object command provides a command-line interface for creating and storing Blob objects from files.


Phase 7 — Trees

Construct Tree objects from repository directory structures.

Implemented:

  • File-to-Blob conversion
  • Directory-to-Tree conversion
  • Recursive Tree construction
  • Nested directories
  • Empty directories
  • Deterministic Tree serialization
  • .mini-git exclusion
  • Persistent Tree storage

Phase 8 — Index / Staging Area

Implement the staging area between the Working Tree and the commit system.

Implemented:

  • Index
  • IndexEntry
  • Staged path → Blob object ID mapping
  • Index updates
  • Duplicate path prevention
  • Index persistence
  • Index loading
  • .mini-git/index
  • mini-git add <file>

The staging workflow is:

Working Tree

      │

      │ mini-git add <file>

      ▼

    Blob

      │

      ▼

Object Database

      │

      ▼

   Blob ID

      │

      ▼

    Index

Phase 9 — Status

Implement:

mini-git status

Implemented:

  • Modified tracked files
  • Deleted tracked files
  • Untracked files
  • Nested untracked files
  • Recursive Working Tree scanning
  • .mini-git exclusion
  • Clean Working Tree detection
  • Automated Status tests

Current comparison:

Index ↔ Working Tree

The complete three-state model will eventually be:

HEAD

 │

 ▼

Index

 │

 ▼

Working Tree

Phase 10 — Commits

Implemented.

Connect the Index, Trees, Commit objects, and Object Database to implement:

mini-git commit -m "message"

The commit pipeline is:

Index

  ↓

TreeBuilder

  ↓

Tree

  ↓

Commit

  ↓

Object Database

Phase 10 establishes:

  • Creating commits from staged state
  • Building a repository Tree from staged information
  • Creating Commit objects
  • Connecting Commit objects to Trees
  • Storing Trees in the Object Database
  • Storing Commit objects in the Object Database
  • Commit parent relationships
  • Initial commits without parents
  • Basic commit workflow testing

This is the first phase where Mini Git can turn staged repository state into a persistent repository snapshot.


Phase 11 — Log

Implement commit history inspection:

mini-git log

Planned functionality:

  • Read the current commit
  • Traverse parent commits
  • Display commit IDs
  • Display commit messages
  • Display author information
  • Display commit order
  • Provide readable history output

Phase 12 — References and HEAD

Implement repository references and HEAD management.

Planned functionality:

  • Reading references
  • Writing references
  • Resolving HEAD
  • Updating branch references
  • Connecting commits to branch pointers

Phase 13 — Branches

Implement branch creation and management.

Planned functionality:

mini-git branch
mini-git branch <name>

Branches will become references pointing to commits.


Phase 14 — Checkout

Implement switching between repository states.

Planned functionality:

  • Checkout branches
  • Restore files from Trees
  • Update HEAD
  • Update Working Tree
  • Update Index

Phase 15 — Diff

Compare repository states and Working Tree changes.

Planned functionality:

  • Working Tree vs Index
  • Index vs HEAD
  • Commit vs commit
  • File-level differences
  • Human-readable diff output

Phase 16 — Merge

Implement simplified merging.

Planned functionality:

  • Fast-forward merging
  • Basic three-way merge
  • Merge-base discovery
  • Combining repository histories

Phase 17 — Conflict Handling

Detect and represent merge conflicts.

Planned functionality:

  • Conflict detection
  • Conflict reporting
  • Conflict markers
  • Conflict resolution workflow

Phase 18 — Tags

Implement lightweight tags.

Planned functionality:

  • Create tags
  • Resolve tags
  • Inspect tagged commits

Phase 19 — Repository Maintenance

Explore repository maintenance concepts.

Planned functionality:

  • Object reachability
  • Reachable vs unreachable objects
  • Unused object detection
  • Garbage-collection concepts
  • Repository maintenance analysis

Phase 20 — Testing

Expand testing coverage.

Planned testing includes:

  • Unit tests
  • Integration tests
  • Repository-level tests
  • Edge-case tests
  • Failure tests
  • Multi-commit workflows
  • Branch tests
  • Checkout tests
  • Diff tests
  • Merge tests
  • Conflict tests

Phase 21 — Robustness

Improve validation, error handling, and failure recovery.

Focus areas:

  • Invalid repositories
  • Corrupt objects
  • Invalid references
  • Invalid commit data
  • Filesystem failures
  • Interrupted operations
  • Better CLI error messages
  • Defensive validation

Phase 22 — Performance

Benchmark important repository operations and identify performance bottlenecks.

Potential benchmarks include:

  • Hashing
  • Blob creation
  • Object storage
  • Tree construction
  • Status scanning
  • Commit creation
  • History traversal

Phase 23 — Refactoring

Review the architecture and modern C++ practices.

Focus areas:

  • RAII
  • Const correctness
  • Ownership
  • Interfaces
  • Testability
  • Separation of concerns
  • Error handling
  • Maintainability
  • Modern C++20 practices

Phase 24 — Documentation

Document:

  • Architecture
  • Internal object formats
  • Index format
  • Repository layout
  • Commit format
  • Reference management
  • Design decisions
  • Architectural tradeoffs
  • Simplifications compared with Git
  • Implementation details

Phase 25 — Git Comparison

Compare Mini Git with real Git feature-by-feature.

The comparison will identify:

  • Similarities
  • Differences
  • Simplifications
  • Architectural choices
  • Object-format differences
  • Storage differences
  • Index differences
  • Reference differences
  • History differences
  • Limitations

The purpose is educational rather than compatibility-focused.


Phase 26 — Educational / Intelligence Layer

Build the unique educational features that distinguish Mini Git from a basic Git clone.

Planned commands include:

mini-git inspect

mini-git explain

mini-git graph

mini-git stats

mini-git fsck

inspect

Expose internal repository objects and metadata.

Example:

mini-git inspect <object-id>

Potential output:

Object ID: abc123...

Type: Blob

Size: 42 bytes

Stored at:
.mini-git/objects/abc123...

explain

Explain what Mini Git is doing internally.

Example:

mini-git explain commit -m "Initial commit"

Potential output:

1. Read Index
2. Build Tree
3. Store Tree
4. Create Commit
5. Store Commit
6. Update reference

graph

Visualize the commit history as an ASCII graph.

Example:

* Commit C
|
* Commit B
|
* Commit A

Eventually this can become a richer representation of branches and merges.

stats

Expose repository statistics.

Potential statistics include:

  • Number of objects
  • Number of Blobs
  • Number of Trees
  • Number of Commits
  • Repository object storage size
  • Number of tracked files
  • Number of branches
  • Commit counts
  • Object reuse

fsck

Inspect repository integrity.

Potential checks include:

  • Missing objects
  • Invalid object IDs
  • Invalid Trees
  • Invalid Commits
  • Broken parent references
  • Broken Tree references
  • Invalid references
  • Unreachable objects

These features are intended to make the internals of Mini Git observable and understandable.


Phase 27 — Portfolio

Prepare the project for professional presentation.

Deliverables:

  • GitHub repository
  • High-quality README
  • Architecture documentation
  • Technical documentation
  • Resume description
  • LinkedIn description
  • Interview explanation
  • Technical discussion points
  • Demonstration workflow
  • Project screenshots / terminal demonstrations
  • Clean commit history
  • Professional repository organization

Project Status

Current phase: Phase 10 — Commits

The core file-to-object pipeline is implemented:

                    Working Tree

                         │

                         ▼

                    FileReader

                         │

                         ▼

                       Blob

                         │

                         ▼

                      SHA-256

                         │

                         ▼

                     Object ID

                         │

                         ▼

                  Object Database

The staging system is implemented:

Working Tree

      │

      │ add

      ▼

    Index

      │

      ▼

.mini-git/index

The status subsystem can compare:

                 Index

                   │

                   │ compare

                   ▼

             Working Tree

                   │

        ┌──────────┼──────────┐

        ▼          ▼          ▼

    Modified    Deleted    Untracked

The object hierarchy is implemented:

Filesystem

    │

    ├── Files

    │     ↓

    │   Blobs

    │

    └── Directories

          ↓

        Trees

          ↓

        Commits

The Phase 10 commit pipeline is now implemented:

Working Tree

      │

      │ add

      ▼

    Index

      │

      │ commit

      ▼

 TreeBuilder

      │

      ▼

     Tree

      │

      ▼

   Commit

      │

      ▼

Object Database

Mini Git can now turn staged files into persistent repository commits.

The next major subsystem is:

Phase 11 — Log

which will traverse the commit parent chain and expose the repository's history.


Final Architecture Goal

The long-term architecture is:

                         mini-git CLI
                              │
                              ▼
                       Command Layer
                              │
             ┌────────────────┼────────────────┐
             │                │                │
             ▼                ▼                ▼
       Working Tree        Index          Repository
             │                │                │
             ▼                │                ▼
        FileReader            │         References
             │                │                │
             ▼                │                ▼
           Blob               │              HEAD
             │                │                │
             └──────────┬─────┘                │
                        │                      │
                        ▼                      │
                  Object Database ◄────────────┘
                        │
             ┌──────────┼──────────┐
             ▼          ▼          ▼
           Blob       Tree       Commit
                                    │
                                    ▼
                                History
                                    │
                           ┌────────┼────────┐
                           ▼        ▼        ▼
                        Branch    Merge     Tags

The educational layer will eventually sit above the repository architecture:

                         mini-git CLI
                              │
          ┌───────────────────┼───────────────────┐
          │                   │                   │
          ▼                   ▼                   ▼
     Normal Commands     Educational Tools    Diagnostics
          │                   │                   │
          │                   ├── inspect          ├── fsck
          │                   ├── explain          └── stats
          │                   └── graph
          │
          ▼
                 Version Control Engine

This is intended to make Mini Git not merely a small Git imitation, but a systems-programming project for understanding how version control works internally.


License

This project is provided under the terms of the license included in the repository.

About

A Git-inspired version control system built from scratch in C++ to explore Git internals, content-addressable storage, object databases, and commit graphs.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages