Git as an Application State Engine

Git as an Application State Engine: Stark monochrome P4 paper white vector CRT macro showing Git commit DAG lineage branching into parallel tracks and merging

Living Document Notice
Published 2026-09-14. The evolving architecture, revisions, and connected notes for this dispatch live in the Stax Digital Garden.

Git as an Application State Engine

Summary

Personal knowledge management applications often implement bespoke undo-redo stacks and fragile version history engines. These custom state mechanisms store document mutations in ad-hoc JSON transaction logs or SQLite change tables. When applications crash mid-transaction or users attempt to revert nested file renames, bespoke history engines risk silent data corruption and orphaned delta chains.

The Git object model provides a battle-tested, content-addressable storage substrate designed for distributed revision tracking. By representing note changes as immutable blobs, directory states as trees, and edits as signed commits, FreeNext uses the Git Directed Acyclic Graph (DAG) directly as an internal application state engine.

The Content-Addressable Storage Graph

In FreeNext, note mutations do not overwrite existing files in place. Instead, the runtime writes new content objects keyed by their SHA-256 digest into the local object database.

       +-------------------------------------------------------+
       |                     Commit Node                       |
       |  commit_id: a4f1...                                   |
       |  parent: 8b2e...                                      |
       |  tree: e90c...                                        |
       +-------------------------------------------------------+
                                   |
                                   v
       +-------------------------------------------------------+
       |                      Tree Node                        |
       |  e90c... -> 100644 blob 3f1a... "notes/architecture.md"|
       |             100644 blob 7c8d... "notes/storage.md"     |
       +-------------------------------------------------------+
                      /                        \
                     v                          v
    +-------------------------+      +-------------------------+
    |        Blob Node        |      |        Blob Node        |
    | SHA: 3f1a...            |      | SHA: 7c8d...            |
    | [Document Markdown Text]|      | [Document Markdown Text]|
    +-------------------------+      +-------------------------+

Because identical file contents yield identical cryptographic hashes, automatic deduplication occurs across repeated file snapshots and structural reorganizations.

Comparison: Custom Delta Logs vs. Git Object Storage

Adopting the Git object model introduces strict immutability guarantees while eliminating the need for bespoke synchronization protocols.

Attribute Bespoke Delta Logs Git Object Engine
Object Identity Synthetic auto-increment integer Cryptographic content hash (SHA-256)
History Verification Weak parity checks or checksums Merkle tree validation across all nodes
Branching and Merging Custom conflict heuristics Standard three-way merge algorithms
Portability Bound to specific application database Inspectable using standard git CLI tools
Compaction Complex custom vacuum scripts Packfile delta compression (git gc)
Tamper Evidence Low; logs easily altered directly Cryptographically immutable history graph

The primary engineering overhead lies in object traversal speed. Traversing thousands of loose object files on disk incurs filesystem overhead, which FreeNext mitigates through periodic packfile generation and in-memory hash indexes.

Representing Mutations via Rust State Machine

FreeNext implements its state engine using a modular Rust crate that compiles to both native targets and WebAssembly. The snippet below shows the core object hashing and commit generation pipeline:

use sha2::{Digest, Sha256};
use std::collections::BTreeMap;

#[derive(Debug, Clone, PartialEq)]
pub struct GitBlob {
    pub hash: String,
    pub content: Vec<u8>,
}

#[derive(Debug, Clone)]
pub struct GitTreeEntry {
    pub mode: String, // e.g. "100644"
    pub name: String,
    pub hash: String,
}

#[derive(Debug, Clone)]
pub struct GitCommit {
    pub hash: String,
    pub parent_hash: Option<String>,
    pub tree_hash: String,
    pub author: String,
    pub timestamp: i64,
    pub message: String,
}

pub struct StateEngine {
    blobs: BTreeMap<String, Vec<u8>>,
    trees: BTreeMap<String, Vec<GitTreeEntry>>,
    commits: BTreeMap<String, GitCommit>,
}

impl StateEngine {
    pub fn new() -> Self {
        Self {
            blobs: BTreeMap::new(),
            trees: BTreeMap::new(),
            commits: BTreeMap::new(),
        }
    }

    pub fn write_blob(&mut self, content: &[u8]) -> String {
        let mut hasher = Sha256::new();
        hasher.update(format!("blob {}\0", content.len()).as_bytes());
        hasher.update(content);
        let hash = format!("{:x}", hasher.finalize());

        self.blobs.insert(hash.clone(), content.to_vec());
        hash
    }

    pub fn commit_snapshot(
        &mut self,
        parent_hash: Option<String>,
        entries: Vec<GitTreeEntry>,
        author: &str,
        message: &str,
    ) -> GitCommit {
        let mut tree_hasher = Sha256::new();
        for entry in &entries {
            tree_hasher.update(format!("{} {}\0{}", entry.mode, entry.name, entry.hash).as_bytes());
        }
        let tree_hash = format!("{:x}", tree_hasher.finalize());
        self.trees.insert(tree_hash.clone(), entries);

        let timestamp = chrono::Utc::now().timestamp();
        let mut commit_hasher = Sha256::new();
        commit_hasher.update(format!("tree {}\n", tree_hash).as_bytes());
        if let Some(ref parent) = parent_hash {
            commit_hasher.update(format!("parent {}\n", parent).as_bytes());
        }
        commit_hasher.update(format!("author {} {}\n\n{}", author, timestamp, message).as_bytes());
        let commit_hash = format!("{:x}", commit_hasher.finalize());

        let commit = GitCommit {
            hash: commit_hash.clone(),
            parent_hash,
            tree_hash,
            author: author.to_string(),
            timestamp,
            message: message.to_string(),
        };

        self.commits.insert(commit_hash, commit.clone());
        commit
    }
}

Using Git primitives transforms document versioning into a deterministic state machine, ensuring that every editing session remains auditable and recoverable.

← Back to FreeNext.App - Blog