Skip to content
Rahul Nair edited this page Mar 29, 2026 · 5 revisions

Gitnook Wiki

This is the source for the gitnook GitHub Wiki. Each top-level section corresponds to a wiki page.


Table of Contents


Home

gitnook gives you lightweight, local-only version control contexts inside any existing git repo. Files tracked by a gitnook have their own independent commit history, are automatically excluded from the outer repo via .git/info/exclude, and are never pushed to your team's remote.

Why gitnook?

When you work inside a project repo, you often have files you want to version locally but should never be committed to the shared history:

  • .env.local or credential files that contain machine-specific secrets
  • Personal scratch notes or a TODO.md that lives alongside the code
  • Local config overrides that differ from the team defaults

Your options without gitnook are all awkward: add to .gitignore (excluded but unversioned), commit to the repo (now everyone sees it), or maintain a separate repo elsewhere (context-switching just to track files that live here).

gitnook is the fourth option: version those files right where they live, privately, without touching the outer repo or .gitignore.


Installation

From crates.io

cargo install gitnook

From source

git clone https://github.com/your-username/gitnook
cd gitnook
cargo build --release

The compiled binary is at target/release/gitnook. Add it to your PATH.

Requirements

  • Rust 1.82 or later
  • libgit2 (bundled via the git2 crate - no separate install needed)

Getting Started

All gitnook commands must be run from inside an existing git repository.

1. Initialise a gitnook

cd my-project          # must be inside a git repo
gitnook init            # creates a gitnook named "default"
gitnook init secrets    # or give it a descriptive name

On first init, gitnook:

  • Creates .gitnook/<name>/ as a bare git repository
  • Creates .gitnook/config.toml recording the new gitnook and setting it as active
  • Adds .gitnook/ to .git/info/exclude so the outer git never sees any of this

2. Track a file

gitnook add .env.local

This does two things atomically:

  1. Stages .env.local in the active gitnook's index
  2. Appends .env.local to .git/info/exclude

From this point on, git status and git add . in the outer repo will never touch .env.local.

To target a specific gitnook instead of the active one:

gitnook add .env.local --to secrets

3. Commit a snapshot

gitnook commit -m "initial tracked state"

gitnook reads your user.name and user.email from the outer repo's git config and uses them as the commit author. If those are not set, it falls back to "gitnook user" <gitnook@local>.

4. Check status

gitnook status          # all gitnooks
gitnook status secrets  # one gitnook

Example output:

[secrets]   modified: .env.local
[notes]     clean
[scratch]   1 new file: todo.md

Status categories:

  • new file - staged in the index but not yet committed
  • modified - committed previously, but the on-disk content has changed since
  • clean - no differences between the index and the working tree

5. Inspect a diff

gitnook diff           # active gitnook
gitnook diff secrets   # named gitnook

Example output:

diff --gitnook a/.env.local b/.env.local
--- a/.env.local
+++ b/.env.local
@@ -1,3 +1,3 @@
 DB_HOST=localhost
-DB_PASS=old_password
+DB_PASS=new_password
 PORT=5432

6. View history

gitnook log             # active gitnook
gitnook log secrets     # named gitnook

Example output:

commit a1b2c3d
Author: Rahul <rahul@example.com>
Date:   Thu Mar 20 14:22:00 2025

    add local db credentials

Command Reference

gitnook init [name]

Creates a new gitnook. Defaults to "default" if no name is given. The first gitnook created in a repo automatically becomes the active gitnook.

gitnook init
gitnook init secrets
gitnook init personal-notes

Errors:

  • gitnook '<name>' already exists - run gitnook list to see all gitnooks

gitnook add <files>... [--to <name>]

Stages one or more files in the target gitnook and excludes them from the outer git. If --to is omitted, files go to the active gitnook.

gitnook add .env.local
gitnook add notes.md todo.md
gitnook add config/local.yml --to scratch

Warnings and errors:

  • If a file is already tracked by the outer git, gitnook prints a warning and tells you to run git rm --cached <file> to fully remove it from outer git's history.
  • If a file is already tracked by a different gitnook, the command errors. A file can only belong to one gitnook.
  • Re-adding a file to the same gitnook re-stages its current content (useful to stage modifications before a commit).

gitnook remove <file> [--to <name>]

Removes a file from a gitnook's index and deletes its entry from .git/info/exclude. The file becomes visible to the outer git again as an untracked file.

gitnook remove .env.local
gitnook remove notes.md --to personal

Notes:

  • The file is not deleted from disk.
  • The commit history in the gitnook is not affected - past commits that included the file remain intact.

gitnook commit -m <message> [--to <name>]

Creates a commit in the target gitnook from its current index.

gitnook commit -m "update token"
gitnook commit -m "checkpoint before refactor" --to scratch

Output: [<name>] <short-sha> <message>


gitnook status [name]

Shows working-directory status for all gitnooks (no argument) or a single named gitnook.

gitnook status
gitnook status secrets

gitnook diff [name]

Shows the working-tree diff between the current on-disk state of tracked files and their last committed content in the gitnook. Uses unified diff format with 3 lines of context.

gitnook diff
gitnook diff secrets
  • Files staged but never committed appear with --- /dev/null (entire content shown as additions).
  • Files unchanged since the last commit are omitted.
  • Prints No changes. if nothing differs.

gitnook log [name]

Shows commit history for the active gitnook or a named one, in reverse chronological order.

gitnook log
gitnook log personal

If the gitnook has no commits yet: No commits yet in gitnook '<name>'


gitnook list

Lists all gitnooks in the current repo, with file counts and an active marker.

gitnook list

Example output:

* secrets    (active)   3 files tracked
  personal              1 file tracked
  scratch               2 files tracked

gitnook switch <name>

Changes the active gitnook. The active gitnook is the default target for add, commit, status, diff, and log when --to is not specified.

gitnook switch personal

The change is persisted to .gitnook/config.toml.


gitnook destroy <name>

Permanently deletes a gitnook and cleans up all of its state:

  1. Removes every tracked file's path from .git/info/exclude (those files become visible to the outer git again as untracked)
  2. Deletes the .gitnook/<name>/ bare repo directory
  3. Removes the gitnook from .gitnook/config.toml; if it was the active gitnook, the active is reassigned to the next remaining one

If it is the last gitnook in the repo, destroy also removes the .gitnook/config.toml, the .gitnook/ directory itself, and the .gitnook/ entry from .git/info/exclude - leaving no trace.

gitnook destroy scratch
gitnook destroy secrets

This operation is irreversible. The commit history stored in the bare repo is deleted. The tracked files themselves are not deleted from disk.

Errors:

  • gitnook '<name>' does not exist - run gitnook list to see all gitnooks

Working With Multiple Gitnooks

A repo can contain any number of named gitnooks. Each has its own independent index, commit history, and tracked file list.

Example: secrets + scratch

# Set up two gitnooks
gitnook init secrets
gitnook init scratch

# Add files to each
gitnook add .env.local   --to secrets
gitnook add .env.test    --to secrets
gitnook add scratch.md   --to scratch

# Commit independently
gitnook commit -m "local credentials" --to secrets
gitnook commit -m "initial scratch"   --to scratch

# Check the full picture
gitnook list
# * secrets   (active)   2 files tracked
#   scratch              1 file tracked

gitnook status
# [scratch]   clean
# [secrets]   clean

Switching the active gitnook

gitnook switch scratch
gitnook add new-idea.md             # goes to scratch, no --to needed
gitnook commit -m "new idea"

The --to flag

--to overrides the active gitnook for a single command without permanently switching. Useful when you want to stay active on one gitnook but occasionally commit to another.

gitnook add token.txt --to secrets   # does not change the active gitnook

How It Works

Storage layout

my-project/
├── .git/
│   └── info/
│       └── exclude          ← gitnook-managed exclusions
├── .gitnook/
│   ├── config.toml          ← global config: active gitnook + registry
│   ├── secrets/             ← bare git repo
│   │   ├── HEAD
│   │   ├── config
│   │   ├── objects/
│   │   └── refs/
│   └── scratch/             ← another bare git repo
│       └── ...
└── src/

Bare repos via libgit2

Each gitnook directory is a valid bare git repository created with git2::Repository::init_bare(). gitnook never shells out to git - all operations (staging, committing, diffing, log walking) go through the libgit2 C library via the git2 Rust crate.

Exclusion via .git/info/exclude

.git/info/exclude is git's local-only ignore file - it is never committed and never pushed. gitnook uses it exclusively (never .gitignore) so that exclusions are completely invisible to the team. The file is created if it does not exist. All entries written by gitnook are plain file paths, one per line.

config.toml

.gitnook/config.toml is a TOML file that records which gitnooks exist and which one is active:

active = "secrets"

[gitnooks.secrets]
created = "2025-01-15T10:00:00Z"

[gitnooks.scratch]
created = "2025-01-16T08:30:00Z"

This file is excluded from the outer git (via .gitnook/ in exclude) and lives entirely on your machine.


Edge Cases and Gotchas

File already tracked by outer git

If you run gitnook add on a file that is already in the outer git's index (i.e., already committed to the team repo), gitnook will print a warning:

Warning: .env.local is currently tracked by git. Run: git rm --cached .env.local

gitnook still adds the file to the gitnook index and exclude file, but the outer git will continue to track the old committed version until you run git rm --cached. After that, the file disappears from the outer git history going forward (existing commits are unaffected).

Re-staging a modified file

gitnook add on a file that is already in the same gitnook re-stages the current on-disk content. This is intentional - it is how you stage modifications before a commit.

echo "new secret" >> .env.local
gitnook add .env.local            # re-stage the modification
gitnook commit -m "rotate token"

File outside the git repo

gitnook resolves all file paths relative to the git root. If you pass a path that is outside the root, the command errors: '<path>' is outside the git repo.

Running outside a git repo

All gitnook commands require an outer git repo. Running gitnook outside one gives:

Error: Not inside a git repository

Running before gitnook init

If you run any command (other than init) without having initialised at least one gitnook:

Error: No gitnooks found. Run 'gitnook init' first.

Limitations

Limitation Detail
Local only Gitnooks are never pushed. No remote, clone, or fetch support in v1.
No branching Each gitnook has a single linear history on main.
One file per gitnook A file can only be tracked by one gitnook at a time.

Roadmap

Feature Description
gitnook push Push a gitnook as a git bundle or to a bare remote for backup or selective sharing
gitnook branch / gitnook checkout Create and switch branches within a gitnook
Shell completions Tab completion for commands and gitnook names (bash, zsh, fish)
gitnook stash Stash uncommitted index state within a gitnook

Contributing

Contributions are welcome. The project is structured as a standard Rust binary crate:

src/
├── main.rs       ← CLI entry point and command dispatch
├── cli.rs        ← clap Cli struct and Commands enum
├── repo.rs       ← outer git root detection
├── config.rs     ← .gitnook/config.toml read/write
├── exclude.rs    ← .git/info/exclude read/write
├── gitnook.rs    ← core operations
└── error.rs      ← thiserror error types
tests/
└── integration.rs  ← end-to-end integration tests

Running tests

cargo test                         # all tests
cargo test --test integration      # integration tests only
cargo test --lib                   # unit tests only

Key invariants to preserve

  • Never write to .gitignore - all exclusions go to .git/info/exclude
  • .gitnook/ must always be in .git/info/exclude after init
  • Never shell out to git - use the git2 crate for all git operations
  • A file can only belong to one gitnook at a time
  • All errors go to stderr; all normal output goes to stdout