Skip to main content

🔐 trackignore

Version License Git

Version control for your private files in public repositories

Work freely with private docs • Push safely to public repos • Never leak sensitive content


🎯 The Problem

You're building an open source project on GitHub, but you have files you want to keep private:

my-awesome-project/
├── src/                    ✅ Public code
├── docs/                   ✅ Public docs
└── __PRIVATE__/           
    ├── notes.md            ⚠️ Private notes, planning documents,
    ├── AGENTS.md           ⚠️ maybe just something you're
    └── embarrassing-ideas/ ⚠️ a little embarassed about!

The Dilemma:

  • 🚫 .gitignore them → You lose version control
  • 🚫 Commit them → They appear in your public GitHub history
  • ✅ trackignore → Version control locally, never publish to GitHub, all in the same repo

🎬 How It Works

Workflow Diagram


✨ Key Features

🛡️ Safety First

  • Pre-push hook prevents accidental leaks
  • Blocks pushes to public remotes
  • Configurable remote whitelist

🧹 Clean History

  • Uses git-filter-repo for complete removal
  • No trace of private files in public repo
  • Fresh, clean commits every time

🔄 Dual Remotes

  • Public remote: cleaned, safe code
  • Private remote: full history (optional)
  • Subtree support for syncing private docs

⚡ Simple Workflow

  • Work locally as normal
  • One repo, no sub-modules, easy config
  • Guaranteed privacy for selected files

🚀 Quick Start

1. Install (pipx recommended)

pipx install trackignore

pipx installs trackignore in an isolated environment together with the required pathspec and git-filter-repo packages—no extra setup per project.

# Upgrade or remove later
pipx upgrade trackignore
pipx uninstall trackignore

2. Initialize your repository

cd my-awesome-project
trackignore init --public-remote origin --private-remote origin-private

This command:

  • creates a .trackignore file seeded with __PRIVATE__/
  • installs a pre-push hook that prompts (or auto-runs) trackignore push
  • writes .trackignore.d/config.sh with your preferred remotes

3. Add or confirm remotes

git remote add origin git@github.com:username/public-repo.git               # sanitized history
git remote add origin-private git@github.com:username/private-repo.git    # optional private mirror

4. Day-to-day workflow

# Work normally with public and private files
vim src/main.py
vim __PRIVATE__/planning.md
git add .
git commit -m "Add feature X"

# Publish a cleaned history to the public remote
trackignore push --remote origin --branches main

# (Optional) sync the private-only subtree to your private remote
trackignore sync --remote origin-private --branch main --path __PRIVATE__

📋 Commands Reference

Command Description What it does
trackignore init Bootstrap Seeds .trackignore, installs hook, and writes config
trackignore push Publish Clones, runs git-filter-repo, and force-pushes sanitized branches
trackignore sync Sync private Uses git subtree push for a protected directory (e.g., __PRIVATE__/)
trackignore cleanup --dry-run Audit history Detects committed secrets and prints the cleanup plan before rewriting
trackignore config-check --json Inspect Lists normalized patterns plus any loader warnings

🔧 Configuration

Custom Private Directory

Edit scripts/publish.sh:

PRIVATE_DIR="my-private-stuff"  # default: __PRIVATE__

Custom Remote Pattern

Edit .githooks/pre-push:

ALLOW_REMOTES_REGEX="^origin-private$|^backup-"  # Allow multiple patterns

Publish Specific Branches

# Publish single branch
PUBLIC_BRANCHES="release/v2" make publish

# Publish multiple branches
PUBLIC_BRANCHES="main develop release/v1" make publish

🎓 How It Works (Deep Dive)

1. Pre-Push Hook 🛡️

Installed in .git/hooks/pre-push, this hook:

  • Intercepts all git push commands
  • Checks if you're pushing to a public remote
  • Blocks the push if __PRIVATE__/ exists in the branch
  • Only allows pushes to remotes matching ALLOW_REMOTES_REGEX
# Example: trying to push to public remote
git push origin main
# ❌ Blocked! Use 'make publish-main' instead

2. Publish Script 🧹

The scripts/publish.sh script does the heavy lifting:

  1. Creates temporary clone → /tmp/repo-publish-<random>/
  2. Runs git-filter-repo → Removes all __PRIVATE__/ from history
  3. Checks out branch → e.g., main
  4. Force pushes to public → origin
  5. Cleans up → Removes temporary directory
# What happens under the hood
git clone . /tmp/repo-publish-abc123
cd /tmp/repo-publish-abc123
git-filter-repo --path __PRIVATE__ --invert-paths --force
git push --force origin main

3. Subtree Sync (Optional) 🌳

For syncing __PRIVATE__/ to a separate private repo:

# First time setup
git subtree split --prefix=__PRIVATE__ -b private-branch
git push origin-private private-branch:main

# Subsequent syncs
make sync-private

⚠️ Important Notes

  • Backup first: Test this workflow on a non-critical project first
  • Force push warning: The publish script uses --force to rewrite history
  • Not a submodule: __PRIVATE__/ must be a regular directory, not a Git submodule
  • git-filter-repo: Must be installed (officially recommended tool by git docs)
  • One-way sync: Changes in public remote won't sync back to private files

🔒 Security Considerations

What This Protects Against

✅ Accidental git push of private files
✅ Private files appearing in public commit history
✅ Leaking sensitive data to public repositories

What This Does NOT Protect Against

❌ Files already pushed before setup (see cleanup below)
❌ Local repository compromise
❌ Intentional bypass of the hook (e.g., git push --no-verify)

Cleaning Existing History

If you've already pushed __PRIVATE__/ to your public repo:

# ⚠️ WARNING: This rewrites history and breaks clones!

# 1. Backup your repo
git clone your-repo your-repo-backup

# 2. Remove private files from all history
git filter-repo --path __PRIVATE__ --invert-paths --force

# 3. Force push to public remote (coordinate with team!)
git push origin --force --all
git push origin --force --tags

🤔 FAQ

Why not just use .gitignore?

.gitignore prevents files from being staged, but you lose version control. This solution lets you version control private files locally while keeping them out of public repos, and avoids alternative solutions (submodules, symlinks, etc.) which may confuse you, your IDE, or your coding assistant - all complexity is abstracted away into a few easily configurable scripts.

Can I use multiple private directories?

Yes! Edit PRIVATE_DIR in the scripts to support patterns, or run filter-repo multiple times with different paths.

What if I forget and push directly?

You can't. The pre-push hook will block it. (If you bypass with --no-verify, you'll need to force-push a cleaned version to fix it.)

Does this work with GitHub Actions?

Yes! You can automate the publish script in CI/CD. Just ensure git-filter-repo is installed in the runner.

Does this work with git worktrees?

Yes! Create separate worktrees for main, feature branches, or private development:

git worktree add ../proj-main main
git worktree add ../proj-dev dev-private

Only one worktree should be used to publish:

cd ../proj-main
make publish-main
Performance with large repos?

git-filter-repo is fast, but creating temporary clones takes time. For very large repos, consider using shallow clones or selective branch publishing.


🛠️ Troubleshooting

Hook not working?

# Check if hook is executable
ls -la .git/hooks/pre-push

# Make it executable
chmod +x .git/hooks/pre-push

# Verify hook is installed
cat .git/hooks/pre-push

git-filter-repo not found?

# Verify installation
which git-filter-repo

# Install via pip
pip install git-filter-repo

# Or download directly
wget https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo
chmod +x git-filter-repo
sudo mv git-filter-repo /usr/local/bin/

Publish fails with "ref already exists"?

# The script uses --force, but if you need to manually fix:
cd /tmp/repo-publish-*/
git push origin main --force

📚 Additional Resources


🤝 Contributing

Contributions welcome! Here's how:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-idea
  3. Make your changes
  4. Test thoroughly
  5. Submit a pull request

Please open an issue first to discuss major changes.


📜 License

MIT License - see LICENSE file for details.


🙏 Acknowledgments

  • Thanks to newren for the amazing git-filter-repo tool
  • Inspired by various discussions about managing private files in public repos
  • Built out of necessity and a healthy dose of paranoia

⭐ Star this repo if you find it useful!

Made with ❤️ and a healthy dose of privacy

Report Bug • Request Feature

Release files for trackignore 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for trackignore 0.1.2
File Size Uploaded
trackignore-0.1.2.tar.gz 20.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for trackignore 0.1.2
File Interpreter ABI Platform
trackignore-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 27.1 kB

Release files / trackignore-0.1.2.tar.gz

Download URL trackignore-0.1.2.tar.gz
Size 20.1 kB
Tags Source
SHA-256 checksum
How to use checksums
791f1b195669123bc0e1a4b5dfb24d7713d3215bfff90068238f18743c9db898
BLAKE2b-256 checksum
How to use checksums
09eac1229526814b9867226425bbd1741241d29d943654f0f8f1361daf98a737
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2025.

Transparency log

Release files / trackignore-0.1.2-py3-none-any.whl

Download URL trackignore-0.1.2-py3-none-any.whl
Size 7.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bee994f9012e4cdfe9caba36150efdc6c4a8c73ca634a328100d3cfaad0fe5cc
BLAKE2b-256 checksum
How to use checksums
34b4bb165ad856e720d78a7c57a0c916b25cbade879f170fd24aa32aadeef677
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page