Your work is real. Your contribution graph should show it.
If you commit to private/org repos all day but your GitHub profile looks empty, greens fixes that. It mirrors commit timestamps (and optionally PRs, reviews, issues) to a public repo without exposing any code.
brew install yuvrajangadsingh/greens/greensThen just run greens. Setup wizard runs on first use.
Manual install (without Homebrew)
git clone https://github.com/yuvrajangadsingh/greens.git
cd greens
bash setup.sh- Scans your work repos (never modifies them)
- Extracts commit timestamps for your email(s) across all branches
- Optionally fetches PR/review/issue timestamps via GitHub API
- Creates empty commits with matching timestamps in a mirror repo
- Pushes to your public mirror
No code is exposed. The mirror contains empty commits with only timestamps.
Works with any git remote. Your source repos can be on GitHub, GitLab, Bitbucket, or self-hosted. greens scans the local clone, not the remote. The mirror destination is GitHub (GitLab/Bitbucket mirror support is planned).
greens # sync (runs setup on first use)
greens sync # same as above
greens init # run setup wizard (alias for --setup)
greens --status # show config and sync status
greens --setup # reconfigure
greens --resync # wipe and re-sync from scratch
greens --reset # remove everythingAfter syncing, your mirror repo shows:
Total Commits: 888 | Active Days: 158 | Repos Tracked: 11
backend-api 325 ███████░░░░░░░░░░░░░ 36%
auth-service 270 ██████░░░░░░░░░░░░░░ 30%
data-pipeline 246 █████░░░░░░░░░░░░░░░ 27%
| Activity | Tracked? |
|---|---|
| Commits | Yes (always) |
| PRs opened | Yes (with gh CLI) |
| PR reviews | Yes (with gh CLI) |
| Issues opened | Yes (with gh CLI) |
Set GITHUB_USERNAME and authenticate gh CLI to enable API features.
How it works under the hood
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Your Work Repos │ │ Safe Cache │ │ Public Mirror │
│ (never touched) │ │ (bare clones) │ │ (empty commits) │
├─────────────────────┤ ├─────────────────────┤ ├─────────────────────┤
│ backend-api/ │────▶│ .cache/backend.git │ │ │
│ auth-service/ │────▶│ .cache/auth.git │────▶│ commit: 2024-01-15 │
│ data-pipeline/ │────▶│ .cache/data.git │ │ commit: 2024-01-16 │
└─────────────────────┘ └─────────────────────┘ │ commit: 2024-01-17 │
└─────────────────────┘
+
┌─────────────────────┐
│ GitHub API │
│ (optional) │
├─────────────────────┤
│ PRs opened │
│ Reviews submitted │────▶ More timestamps
│ Issues created │
└─────────────────────┘
Configuration reference
| Variable | Required | Default | Description |
|---|---|---|---|
WORK_DIR |
Yes | $HOME/work |
Directory containing your work repos |
MIRROR_DIR |
Yes | ~/.contrib-mirror/mirror |
Your public mirror repo (local clone) |
EMAILS |
Yes | - | Comma-separated git emails to match (exact match) |
REMOTE_PREFIX |
Yes | - | Only sync repos with origins starting with this |
MIRROR_EMAIL |
Yes | - | Personal GitHub email for mirror commits |
SINCE |
No | 2024-01-01 |
Only sync activity after this date |
GITHUB_USERNAME |
No | - | Work GitHub username (enables API features) |
GITHUB_TOKEN |
No | - | Work account PAT (alternative to multi-account gh CLI) |
GITHUB_ORG |
No | (auto) | GitHub org name (auto-detected from REMOTE_PREFIX) |
ACTIVITY_TYPES |
No | commits,prs,reviews,issues |
What to track |
COPY_MESSAGES |
No | 0 |
Set to 1 to copy commit messages (not just timestamps) |
FORCE |
No | 0 |
Set to 1 to bypass daily limit |
Auth methods for multi-account setups
If your work GitHub account differs from your personal one:
| Method | Best for | Setup |
|---|---|---|
| Personal Access Token | HTTPS users, simplest | Create PAT with repo scope, set GITHUB_TOKEN |
| Multi-account gh CLI | SSH users with multiple accounts | gh auth login both accounts, set GITHUB_USERNAME |
| Single account | Default gh account has org access |
Just set GITHUB_USERNAME |
Works with both SSH and HTTPS repo access.
Is any code exposed?
No. Only timestamps are mirrored. The mirror repo contains empty commits with no content. If you enable COPY_MESSAGES=1, commit messages will be visible but no code is ever exposed.
Will this affect my private repos?
No. The script creates bare caches and never modifies your working directories.
Does it check all branches or just main?
All branches. Scans across every branch using git log --all. Commits aren't double-counted after merge. For squash merges, old branch commits are pruned once the remote branch is deleted.
Can the mirror repo be private?
Yes. Enable "Include private contributions on my profile" in GitHub Settings > Profile so the green squares show to visitors.
Can I backfill old contributions?
Yes. Set SINCE to an earlier date and run FORCE=1 greens.
Troubleshooting
| Problem | Solution |
|---|---|
| "No matching repos found" | Check WORK_DIR and REMOTE_PREFIX match your repos |
| "clone failed" | Check SSH access: ssh -T git@github.com |
| "gh CLI not authenticated" | Run gh auth login |
| Empty contribution graph | Wait 24h for GitHub to update, or check mirror repo has commits |
| Wrong timestamps | Check EMAILS matches your git config |
| Mirror has wrong commits | Run greens --resync to wipe and re-sync |
| "Already synced today" | Use FORCE=1 greens to override daily limit |
MIT